diff --git a/frontend/src/assets/theme.css b/frontend/src/assets/theme.css index 3a000e9..c4d7d9d 100644 --- a/frontend/src/assets/theme.css +++ b/frontend/src/assets/theme.css @@ -12,6 +12,13 @@ file used to read, and it is deliberate: the light palette was never specified by any rule, so it is recorded as a departure rather than as the default. + The -fg tokens are a badge's TEXT colour, added because the ladder used its + raw hue as text on a 12% tint of the same hue — measured 1.60-2.97:1 on the + dark palette against the kit's AA floor of 4.5. Each is the hue mixed toward + --fs-text-primary until it clears 4.5:1 worst-case over surface-raised and + surface-hover in BOTH modes. Mixing toward that token is what makes one + declaration cover both: it inverts, so the text follows the mode. + Only 12 tokens differ between modes. Everything else — spacing, type, motion, radius, and every derived colour — is stated once, because a value built with var() resolves where it is USED, not where it is written. @@ -78,10 +85,13 @@ /* priority */ --fs-priority-low: var(--fs-info); --fs-priority-low-bg: color-mix(in srgb, var(--fs-priority-low) 12%, transparent); + --fs-priority-low-fg: color-mix(in srgb, var(--fs-priority-low) 45%, var(--fs-text-primary)); /* Badge TEXT for low priority — the readable partner of the -bg tint */ --fs-priority-medium: var(--fs-warning); --fs-priority-medium-bg: color-mix(in srgb, var(--fs-priority-medium) 12%, transparent); + --fs-priority-medium-fg: color-mix(in srgb, var(--fs-priority-medium) 55%, var(--fs-text-primary)); /* Badge TEXT for medium priority */ --fs-priority-high: var(--fs-error); --fs-priority-high-bg: color-mix(in srgb, var(--fs-priority-high) 12%, transparent); + --fs-priority-high-fg: color-mix(in srgb, var(--fs-priority-high) 55%, var(--fs-text-primary)); /* Badge TEXT for high priority */ /* radius */ --fs-radius-sm: 4px; /* pills, tags, code spans */ @@ -116,12 +126,16 @@ /* status */ --fs-status-todo: var(--fs-border-color); --fs-status-todo-bg: color-mix(in srgb, var(--fs-status-todo) 12%, transparent); + --fs-status-todo-fg: color-mix(in srgb, var(--fs-status-todo) 40%, var(--fs-text-primary)); /* Badge TEXT for a not-started task */ --fs-status-in-progress: var(--fs-accent); --fs-status-in-progress-bg: color-mix(in srgb, var(--fs-status-in-progress) 12%, transparent); + --fs-status-in-progress-fg: color-mix(in srgb, var(--fs-status-in-progress) 45%, var(--fs-text-primary)); /* Badge TEXT for a task underway */ --fs-status-done: var(--fs-success); --fs-status-done-bg: color-mix(in srgb, var(--fs-status-done) 12%, transparent); + --fs-status-done-fg: color-mix(in srgb, var(--fs-status-done) 50%, var(--fs-text-primary)); /* Badge TEXT for a completed task */ --fs-overdue: var(--fs-error); --fs-status-cancelled: var(--fs-text-tertiary); /* set aside, not failed */ + --fs-status-cancelled-fg: color-mix(in srgb, var(--fs-status-cancelled) 60%, var(--fs-text-primary)); /* Badge TEXT for a cancelled task */ /* surface */ --fs-surface-page: #14171A; /* page bg, deepest surface */ diff --git a/frontend/src/components/KindBadge.vue b/frontend/src/components/KindBadge.vue new file mode 100644 index 0000000..3622a96 --- /dev/null +++ b/frontend/src/components/KindBadge.vue @@ -0,0 +1,79 @@ + + + + {{ LABELS[props.kind] }} + + + diff --git a/frontend/src/components/PriorityBadge.vue b/frontend/src/components/PriorityBadge.vue index 22d732f..a370c85 100644 --- a/frontend/src/components/PriorityBadge.vue +++ b/frontend/src/components/PriorityBadge.vue @@ -3,6 +3,8 @@ import type { TaskPriority } from "@/types/task"; const props = defineProps<{ priority: TaskPriority; + /** Dense surfaces — see StatusBadge. */ + compact?: boolean; }>(); const labels: Record = { @@ -16,7 +18,7 @@ const labels: Record = { {{ labels[props.priority] }} @@ -28,20 +30,28 @@ const labels: Record = { padding: 0.15rem 0.5rem; border-radius: 12px; font-size: 0.75rem; - font-weight: 600; + /* 500 is the heaviest the house style goes — 400 and 500 only. */ + font-weight: 500; text-transform: uppercase; letter-spacing: 0.025em; } +.compact { + padding: 1px 7px; + border-radius: 8px; + font-size: 0.7rem; + text-transform: none; + letter-spacing: normal; +} .priority-low { background: var(--fs-priority-low-bg); - color: var(--fs-priority-low); + color: var(--fs-priority-low-fg); } .priority-medium { background: var(--fs-priority-medium-bg); - color: var(--fs-priority-medium); + color: var(--fs-priority-medium-fg); } .priority-high { background: var(--fs-priority-high-bg); - color: var(--fs-priority-high); + color: var(--fs-priority-high-fg); } diff --git a/frontend/src/components/ProjectDesignTab.vue b/frontend/src/components/ProjectDesignTab.vue index fddba0b..fa00762 100644 --- a/frontend/src/components/ProjectDesignTab.vue +++ b/frontend/src/components/ProjectDesignTab.vue @@ -153,7 +153,7 @@ watch(() => [props.projectId, props.designSystemId], run); } .pdt-clean { - color: var(--fs-status-done); + color: var(--fs-status-done-fg); } .pdt-summary { @@ -206,12 +206,12 @@ watch(() => [props.projectId, props.designSystemId], run); .pdt-tag.unknown { background: var(--fs-priority-high-bg); - color: var(--fs-priority-high); + color: var(--fs-priority-high-fg); } .pdt-tag.local { background: var(--fs-priority-medium-bg); - color: var(--fs-priority-medium); + color: var(--fs-priority-medium-fg); } .pdt-tag.superseded { diff --git a/frontend/src/components/ProjectStatusBadge.vue b/frontend/src/components/ProjectStatusBadge.vue new file mode 100644 index 0000000..4e66087 --- /dev/null +++ b/frontend/src/components/ProjectStatusBadge.vue @@ -0,0 +1,71 @@ + + + + + {{ label(props.status) }} + + + + diff --git a/frontend/src/components/StatusBadge.vue b/frontend/src/components/StatusBadge.vue index db36296..5037f3b 100644 --- a/frontend/src/components/StatusBadge.vue +++ b/frontend/src/components/StatusBadge.vue @@ -4,13 +4,16 @@ import type { TaskStatus } from "@/types/task"; const props = defineProps<{ status: TaskStatus; clickable?: boolean; + /** Dense surfaces — smaller, unshouted. The canon (#2960) names compact a + VARIANT of this component rather than a reason to re-spell it. */ + compact?: boolean; }>(); defineEmits<{ click: [] }>(); const labels: Record = { todo: "Todo", - in_progress: "In Progress", + in_progress: "In progress", done: "Done", cancelled: "Cancelled", }; @@ -18,7 +21,7 @@ const labels: Record = { = { padding: 0.15rem 0.5rem; border-radius: 12px; font-size: 0.75rem; - font-weight: 600; + /* 500 is the heaviest the house style goes — 400 and 500 only. */ + font-weight: 500; text-transform: uppercase; letter-spacing: 0.025em; } +/* Text comes from the -fg tokens, which are the hue mixed toward + --fs-text-primary until they clear AA. The old spelling darkened the hue + with `#000 15%` — a light-mode instinct that made these WORSE on the dark + palette, where the surface is already near-black, and a literal besides. */ .status-todo { - background: color-mix(in srgb, var(--fs-status-todo-bg) 78%, var(--fs-status-todo) 22%); - color: color-mix(in srgb, var(--fs-status-todo) 85%, #000 15%); + background: var(--fs-status-todo-bg); + color: var(--fs-status-todo-fg); } .status-in_progress { - background: color-mix(in srgb, var(--fs-status-in-progress-bg) 78%, var(--fs-status-in-progress) 22%); - color: color-mix(in srgb, var(--fs-status-in-progress) 85%, #000 15%); + background: var(--fs-status-in-progress-bg); + color: var(--fs-status-in-progress-fg); } .status-done { - background: color-mix(in srgb, var(--fs-status-done-bg) 78%, var(--fs-status-done) 22%); - color: color-mix(in srgb, var(--fs-status-done) 85%, #000 15%); + background: var(--fs-status-done-bg); + color: var(--fs-status-done-fg); } .status-cancelled { - background: color-mix(in srgb, var(--fs-surface-raised) 78%, var(--fs-text-tertiary) 22%); - color: var(--fs-text-tertiary); + background: var(--fs-status-todo-bg); + color: var(--fs-status-cancelled-fg); +} +.compact { + padding: 1px 7px; + border-radius: 8px; + font-size: 0.7rem; + text-transform: none; + letter-spacing: normal; } .clickable { cursor: pointer; diff --git a/frontend/src/components/SystemsSection.vue b/frontend/src/components/SystemsSection.vue index fbc7e77..6bfbbf3 100644 --- a/frontend/src/components/SystemsSection.vue +++ b/frontend/src/components/SystemsSection.vue @@ -554,8 +554,8 @@ async function confirmDelete() { /* The two bases must never look alike — one is mechanical, the other is the reviewer's judgment, and that difference is the whole decision. */ .area-basis { font-size: 0.68rem; border-radius: var(--fs-radius-sm); padding: 0.05rem 0.4rem; } -.area-basis--exact { background: var(--fs-status-done-bg); color: var(--fs-status-done); } -.area-basis--overlap { background: var(--fs-priority-medium-bg); color: var(--fs-priority-medium); } +.area-basis--exact { background: var(--fs-status-done-bg); color: var(--fs-status-done-fg); } +.area-basis--overlap { background: var(--fs-priority-medium-bg); color: var(--fs-priority-medium-fg); } .area-offer { display: flex; diff --git a/frontend/src/components/TaskCard.vue b/frontend/src/components/TaskCard.vue deleted file mode 100644 index 52e4238..0000000 --- a/frontend/src/components/TaskCard.vue +++ /dev/null @@ -1,238 +0,0 @@ - - - - - - - - - - {{ task.title || "Untitled" }} - {{ projectTitle }} - - - - - {{ task.due_date }} - - - - - - - - - {{ task.title || "Untitled" }} - - - - - Due: {{ task.due_date }} - - - {{ relativeTime(task.updated_at) }} - - - - - - - diff --git a/frontend/src/components/WorkspaceTaskPanel.vue b/frontend/src/components/WorkspaceTaskPanel.vue index 09ad42e..f8b101e 100644 --- a/frontend/src/components/WorkspaceTaskPanel.vue +++ b/frontend/src/components/WorkspaceTaskPanel.vue @@ -4,6 +4,8 @@ import { RouterLink } from "vue-router"; import { apiGet, apiPatch, apiPost, apiDelete } from "@/api/client"; import { useToastStore } from "@/stores/toast"; import TaskLogSection from "@/components/TaskLogSection.vue"; +import KindBadge from "@/components/KindBadge.vue"; +import type { TaskKind } from "@/types/note"; import { renderMarkdown } from "@/utils/markdown"; import { Trash2, X } from "lucide-vue-next"; import { relativeTimeOrDate } from "@/composables/useRelativeTime"; @@ -28,6 +30,7 @@ interface Task { due_date: string | null; updated_at: string; body?: string; + task_kind?: TaskKind; } const tasks = ref([]); @@ -242,6 +245,7 @@ defineExpose({ reload: loadAll }); {{ STATUS_ICON[task.status] ?? '○' }} {{ task.title }} + {{ task.due_date }} {{ relativeTimeOrDate(task.updated_at) }} @@ -267,6 +271,7 @@ defineExpose({ reload: loadAll }); {{ STATUS_ICON[task.status] ?? '○' }} {{ task.title }} + {{ task.due_date }} {{ relativeTimeOrDate(task.updated_at) }} @@ -281,7 +286,7 @@ defineExpose({ reload: loadAll }); Edit ↗ - + {{ STATUS_ICON[activeTask.status] ?? "○" }} {{ activeTask.status.replace("_", " ") }} @@ -496,7 +501,10 @@ defineExpose({ reload: loadAll }); flex-shrink: 0; } -.status-badge { +/* An interactive CYCLER, not a chip: it is clickable, outlined and + transparent. It shared a name with the task chip and was never the same + shape (#3132). */ +.status-cycler { padding: 0.2rem 0.55rem; border-radius: 12px; font-size: 0.75rem; @@ -508,8 +516,8 @@ defineExpose({ reload: loadAll }); user-select: none; margin-left: auto; } -.status-badge.status-in_progress { border-color: var(--fs-accent); color: var(--fs-accent); background: color-mix(in srgb, var(--fs-accent) 10%, transparent); } -.status-badge.status-done { border-color: var(--fs-success); color: var(--fs-success); background: color-mix(in srgb, var(--fs-success) 10%, transparent); } +.status-cycler.status-in_progress { border-color: var(--fs-accent); color: var(--fs-accent); background: color-mix(in srgb, var(--fs-accent) 10%, transparent); } +.status-cycler.status-done { border-color: var(--fs-success); color: var(--fs-success); background: color-mix(in srgb, var(--fs-success) 10%, transparent); } .btn-edit-task { margin-left: 0.25rem; } .btn-edit-task:hover { text-decoration: underline; } diff --git a/frontend/src/views/DashboardView.vue b/frontend/src/views/DashboardView.vue index 5ee73e8..da19833 100644 --- a/frontend/src/views/DashboardView.vue +++ b/frontend/src/views/DashboardView.vue @@ -1,9 +1,11 @@