DRY refactoring pass: shared mixins, route helpers, composables, CSS

Backend:
- models/base.py: TimestampMixin + CreatedAtMixin; applied to all 10+ models
- routes/utils.py: not_found() + parse_iso_date() helpers; used across all route files
- routes/milestones.py: _milestone_dict() helper replaces 5 repeated to_dict + progress blocks

Frontend:
- 5 new composables: useAutoSave, useEditorGuards, useTagSuggestions,
  useFloatingAssist, useListKeyboardNavigation
- ConfirmDialog.vue: reusable confirm modal replacing inline <teleport> blocks
- editor-shared.css: sidebar CSS consolidated from both editor views
- viewer-shared.css: context-bar/breadcrumb CSS consolidated from both viewer views
- NoteEditorView + TaskEditorView: ~120 lines each replaced with composable calls;
  duplicate scoped sidebar CSS removed
- NotesListView + TasksListView: inline keyboard-nav replaced with composable
- NoteViewerView + TaskViewerView: duplicate context-bar CSS removed

No behaviour changes. Net: -634 lines, +237 lines across 31 files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 15:26:34 -05:00
parent 48f070f773
commit 16ecd6bbeb
32 changed files with 563 additions and 634 deletions
+3
View File
@@ -16,6 +16,9 @@ class Base(DeclarativeBase):
pass
from fabledassistant.models.base import CreatedAtMixin, TimestampMixin # noqa: E402, F401
from fabledassistant.models.note import Note, TaskPriority, TaskStatus # noqa: E402, F401
from fabledassistant.models.conversation import Conversation, Message # noqa: E402, F401
from fabledassistant.models.setting import Setting # noqa: E402, F401
+21
View File
@@ -0,0 +1,21 @@
from datetime import datetime, timezone
from sqlalchemy import DateTime
from sqlalchemy.orm import Mapped, mapped_column
class TimestampMixin:
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
class CreatedAtMixin:
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
+4 -16
View File
@@ -1,14 +1,13 @@
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, Index, Integer, Text
from sqlalchemy import ForeignKey, Index, Integer, Text
from sqlalchemy import inspect
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column, relationship
from fabledassistant.models import Base
from fabledassistant.models.base import CreatedAtMixin, TimestampMixin
class Conversation(Base):
class Conversation(Base, TimestampMixin):
__tablename__ = "conversations"
id: Mapped[int] = mapped_column(primary_key=True)
@@ -17,14 +16,6 @@ class Conversation(Base):
)
title: Mapped[str] = mapped_column(Text, default="")
model: Mapped[str] = mapped_column(Text, default="")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
messages: Mapped[list["Message"]] = relationship(
back_populates="conversation",
@@ -53,7 +44,7 @@ class Conversation(Base):
}
class Message(Base):
class Message(Base, CreatedAtMixin):
__tablename__ = "messages"
id: Mapped[int] = mapped_column(primary_key=True)
@@ -67,9 +58,6 @@ class Message(Base):
Integer, ForeignKey("notes.id", ondelete="SET NULL"), nullable=True
)
tool_calls: Mapped[list | None] = mapped_column(JSONB, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
conversation: Mapped["Conversation"] = relationship(back_populates="messages")
+3 -12
View File
@@ -1,12 +1,11 @@
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, Integer, Text
from sqlalchemy import ForeignKey, Integer, Text
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from fabledassistant.models.base import TimestampMixin
class Milestone(Base):
class Milestone(Base, TimestampMixin):
__tablename__ = "milestones"
id: Mapped[int] = mapped_column(primary_key=True)
@@ -16,14 +15,6 @@ class Milestone(Base):
description: Mapped[str | None] = mapped_column(Text, nullable=True)
status: Mapped[str] = mapped_column(Text, default="active")
order_index: Mapped[int] = mapped_column(Integer, default=0)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
def to_dict(self) -> dict:
return {
+4 -11
View File
@@ -1,11 +1,12 @@
import enum
from datetime import date, datetime, timezone
from datetime import date
from sqlalchemy import Date, DateTime, ForeignKey, Index, Integer, Text
from sqlalchemy import Date, ForeignKey, Index, Integer, Text
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from fabledassistant.models.base import TimestampMixin
class TaskStatus(str, enum.Enum):
@@ -21,7 +22,7 @@ class TaskPriority(str, enum.Enum):
high = "high"
class Note(Base):
class Note(Base, TimestampMixin):
__tablename__ = "notes"
id: Mapped[int] = mapped_column(primary_key=True)
@@ -43,14 +44,6 @@ class Note(Base):
status: Mapped[str | None] = mapped_column(Text, nullable=True)
priority: Mapped[str | None] = mapped_column(Text, nullable=True)
due_date: Mapped[date | None] = mapped_column(Date, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
__table_args__ = (
Index("ix_notes_tags", "tags", postgresql_using="gin"),
+3 -12
View File
@@ -1,12 +1,11 @@
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, Integer, Text
from sqlalchemy import ForeignKey, Integer, Text
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from fabledassistant.models.base import TimestampMixin
class NoteDraft(Base):
class NoteDraft(Base, TimestampMixin):
__tablename__ = "note_drafts"
id: Mapped[int] = mapped_column(primary_key=True)
@@ -16,14 +15,6 @@ class NoteDraft(Base):
original_body: Mapped[str] = mapped_column(Text)
instruction: Mapped[str] = mapped_column(Text, default="")
scope: Mapped[str] = mapped_column(Text, default="document")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
def to_dict(self) -> dict:
return {
+3 -7
View File
@@ -1,12 +1,11 @@
from datetime import datetime, timezone
from sqlalchemy import ARRAY, DateTime, ForeignKey, Integer, Text
from sqlalchemy import ARRAY, ForeignKey, Integer, Text
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from fabledassistant.models.base import CreatedAtMixin
class NoteVersion(Base):
class NoteVersion(Base, CreatedAtMixin):
__tablename__ = "note_versions"
id: Mapped[int] = mapped_column(primary_key=True)
@@ -15,9 +14,6 @@ class NoteVersion(Base):
body: Mapped[str] = mapped_column(Text)
title: Mapped[str] = mapped_column(Text, default="")
tags: Mapped[list[str]] = mapped_column(ARRAY(Text), default=list)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
def to_dict(self, include_body: bool = True) -> dict:
d: dict = {
+3 -5
View File
@@ -1,8 +1,8 @@
import enum
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, Integer, Text
from sqlalchemy import ForeignKey, Integer, Text
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from fabledassistant.models.base import TimestampMixin
class ProjectStatus(str, enum.Enum):
@@ -11,7 +11,7 @@ class ProjectStatus(str, enum.Enum):
archived = "archived"
class Project(Base):
class Project(Base, TimestampMixin):
__tablename__ = "projects"
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=True)
@@ -20,8 +20,6 @@ class Project(Base):
goal: Mapped[str] = mapped_column(Text, default="")
status: Mapped[str] = mapped_column(Text, default="active")
color: Mapped[str | None] = mapped_column(Text, nullable=True) # hex color
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
def to_dict(self) -> dict:
return {
@@ -1,10 +1,11 @@
from datetime import datetime, timezone
from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, Integer, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from fabledassistant.models.base import CreatedAtMixin
class PushSubscription(Base):
class PushSubscription(Base, CreatedAtMixin):
__tablename__ = "push_subscriptions"
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id", ondelete="CASCADE"))
@@ -12,7 +13,6 @@ class PushSubscription(Base):
p256dh: Mapped[str] = mapped_column(Text)
auth: Mapped[str] = mapped_column(Text)
user_agent: Mapped[str | None] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
last_used: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
__table_args__ = (
+3 -12
View File
@@ -1,12 +1,11 @@
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, Integer, Text
from sqlalchemy import ForeignKey, Integer, Text
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from fabledassistant.models.base import TimestampMixin
class TaskLog(Base):
class TaskLog(Base, TimestampMixin):
__tablename__ = "task_logs"
id: Mapped[int] = mapped_column(primary_key=True)
@@ -14,14 +13,6 @@ class TaskLog(Base):
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id", ondelete="CASCADE"))
content: Mapped[str] = mapped_column(Text)
duration_minutes: Mapped[int | None] = mapped_column(Integer, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
def to_dict(self) -> dict:
return {
+3 -7
View File
@@ -1,12 +1,11 @@
from datetime import datetime, timezone
from sqlalchemy import DateTime, Index, Text
from sqlalchemy import Index, Text
from sqlalchemy.orm import Mapped, mapped_column
from fabledassistant.models import Base
from fabledassistant.models.base import CreatedAtMixin
class User(Base):
class User(Base, CreatedAtMixin):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
@@ -15,9 +14,6 @@ class User(Base):
password_hash: Mapped[str | None] = mapped_column(Text, nullable=True)
oauth_sub: Mapped[str | None] = mapped_column(Text, unique=True, nullable=True)
role: Mapped[str] = mapped_column(Text, nullable=False, default="user")
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
__table_args__ = (
Index("ix_users_username", "username"),