From f321c2bf70d0645b188d0988b7efe737c9580ec0 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Mon, 20 Jul 2026 13:11:50 -0400 Subject: [PATCH] labels: per-label color Give labels a color (migration 0011 adds labels.color, server_default 'default' so existing labels keep the neutral chip). The PATCH endpoint now updates name and/or color; note serialization carries each label's color. Frontend: a swatch picker per label in the Edit-labels modal, colored chips on cards + in the editor (LABEL_CHIP_CLASSES), and a color dot on each sidebar label. Reuses the note color vocabulary. (Graph node coloring rides this in the graph-liveliness task.) Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm --- alembic/versions/0011_label_color.py | 22 +++++++++++++++ frontend/src/components/AppShell.vue | 11 +++++++- frontend/src/components/LabelsModal.vue | 37 +++++++++++++++++++++++-- frontend/src/components/NoteCard.vue | 9 ++++-- frontend/src/components/NoteEditor.vue | 9 ++++-- frontend/src/notes/colors.ts | 14 ++++++++++ frontend/src/stores/labels.ts | 9 +++++- frontend/src/stores/notes.ts | 1 + src/thoughtsync/labels.py | 36 ++++++++++++++++-------- src/thoughtsync/models/label.py | 1 + src/thoughtsync/notes.py | 6 ++-- 11 files changed, 133 insertions(+), 22 deletions(-) create mode 100644 alembic/versions/0011_label_color.py diff --git a/alembic/versions/0011_label_color.py b/alembic/versions/0011_label_color.py new file mode 100644 index 0000000..d841519 --- /dev/null +++ b/alembic/versions/0011_label_color.py @@ -0,0 +1,22 @@ +"""labels.color + +Revision ID: 0011 +Revises: 0010 +Create Date: 2026-07-20 +""" +from alembic import op +import sqlalchemy as sa + +revision = "0011" +down_revision = "0010" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # server_default backfills existing labels to the neutral "default" chip. + op.add_column("labels", sa.Column("color", sa.Text(), nullable=False, server_default="default")) + + +def downgrade() -> None: + op.drop_column("labels", "color") diff --git a/frontend/src/components/AppShell.vue b/frontend/src/components/AppShell.vue index 88b319f..cc49789 100644 --- a/frontend/src/components/AppShell.vue +++ b/frontend/src/components/AppShell.vue @@ -8,6 +8,7 @@ import { useUiStore } from "../stores/ui"; import CommandPalette from "./CommandPalette.vue"; import Icon from "./Icon.vue"; import LabelsModal from "./LabelsModal.vue"; +import { NOTE_SWATCH_CLASSES, type NoteColor } from "../notes/colors"; const route = useRoute(); const router = useRouter(); @@ -127,6 +128,10 @@ onBeforeUnmount(() => window.removeEventListener("keydown", onKeydown)); const currentLabelId = computed(() => (route.name === "label" ? String(route.params.id) : null)); +function labelDot(color: string): string { + return NOTE_SWATCH_CLASSES[color as NoteColor] ?? NOTE_SWATCH_CLASSES.default; +} + function onSearch(value: string) { searchText.value = value; clearTimeout(searchTimer); @@ -226,7 +231,11 @@ async function signOut() { class="nav-link" :class="currentLabelId === lb.id ? 'nav-link-active' : ''" > - {{ lb.name }} + + {{ lb.name }} diff --git a/frontend/src/components/LabelsModal.vue b/frontend/src/components/LabelsModal.vue index 4433f4b..1233247 100644 --- a/frontend/src/components/LabelsModal.vue +++ b/frontend/src/components/LabelsModal.vue @@ -1,11 +1,13 @@