Files
FabledScribe/frontend/src/components/StatusBadge.vue
T
bvandeusenandClaude Opus 5 ce1376edc9
CI & Build / Python lint (push) Successful in 5s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / integration (push) Successful in 33s
CI & Build / TypeScript typecheck (push) Successful in 40s
CI & Build / Python tests (push) Successful in 1m6s
CI & Build / Build & push image (push) Successful in 39s
refactor(ui): the badge layer gets one owner per shape (#3132 items 1-3)
ITEM 1 — the dead canon. StatusBadge is recorded canon (#2960) and its only
consumer, TaskCard, has been unreachable since 2026-04-08, when
TasksListView was deleted in favour of the Knowledge view. Four and a half
months of a canon that rendered nowhere, which is worse than no canon: a
session pulls #2960, builds from it, and matches a component nobody has
seen. TaskCard is deleted (rule 22), and the canon is made real by adoption
rather than by being left as a museum piece.

ITEM 2 — MY OWN ISSUE OVERSTATED THIS, and the correction is the finding.
"Three scoped re-spellings" assumed one shape spelled thrice. Reading them:

  KnowledgeView   a task-status chip, just smaller     -> a real duplicate
  WorkspaceTaskPanel  a CLICKABLE cycler: pointer,
                  outlined, transparent background     -> a control, not a chip
  ProjectView     PROJECT lifecycle (active/paused/
                  completed/archived)                  -> a different vocabulary

Only the first was ever a duplicate. The others shared a class NAME and
nothing else — which is exactly what would make a future consolidation merge
three unrelated things. So: KnowledgeView adopts StatusBadge/PriorityBadge
via the `compact` variant the canon already anticipated ("interactive/compact
re-spellings are variants of it"); the cycler becomes `.status-cycler`; and
project status becomes its own vocabulary.

And there was a FOURTH, in ProjectListView — the genuine duplicate of
ProjectView's project pill, differing by the amounts two hands differ by:
0.68rem vs 0.7rem, a 14% tint vs 15%, one bordered and one not. Both now use
one ProjectStatusBadge. `statusLabel` went with its only caller.

ITEM 3 — weight. StatusBadge and PriorityBadge used font-weight 600; the
house style allows 400 and 500 only. Also "In Progress" -> "In progress",
which was invisible under `text-transform: uppercase` and becomes visible the
moment the compact variant turns that off.

THE GUARD MISSED FOUR LIVE SITES, which is the part worth keeping. The
project pills painted a hue on an inline `color-mix` tint of itself —
measured 1.61-2.39:1 — and the checker only knew the `--fs-X-bg` token form.
Widened, it finds 48 across the app, 26 of them --fs-accent.

That backlog is not this task, so the check now splits: it GATES the token
form, which is clean, and REPORTS the inline form with a count and its worst
offenders. A gate nobody can satisfy today gets switched off, and then it
guards nothing. Gate re-verified by reintroducing a defect — exit 1 with it,
exit 0 without.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 20:41:58 -04:00

78 lines
1.9 KiB
Vue

<script setup lang="ts">
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<TaskStatus, string> = {
todo: "Todo",
in_progress: "In progress",
done: "Done",
cancelled: "Cancelled",
};
</script>
<template>
<span
:class="['status-badge', `status-${props.status}`, { clickable, compact }]"
@click="clickable ? $emit('click') : undefined"
:role="clickable ? 'button' : undefined"
:tabindex="clickable ? 0 : undefined"
>
{{ labels[props.status] }}
</span>
</template>
<style scoped>
.status-badge {
display: inline-block;
padding: 0.15rem 0.5rem;
border-radius: 12px;
font-size: 0.75rem;
/* 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: var(--fs-status-todo-bg);
color: var(--fs-status-todo-fg);
}
.status-in_progress {
background: var(--fs-status-in-progress-bg);
color: var(--fs-status-in-progress-fg);
}
.status-done {
background: var(--fs-status-done-bg);
color: var(--fs-status-done-fg);
}
.status-cancelled {
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;
}
.clickable:hover {
filter: brightness(0.9);
}
</style>