Files
FabledScribe/frontend/src/components/ProjectStatusBadge.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

72 lines
2.7 KiB
Vue

<script setup lang="ts">
/**
* A PROJECT's lifecycle state as a pill — active, paused, completed, archived.
*
* Deliberately not StatusBadge. That component is typed to TaskStatus and
* speaks a different vocabulary; these two only ever shared a CSS class name,
* which is what made them look like one shape that had drifted (#3132).
*
* Extracted because ProjectView and ProjectListView really were spelling the
* same pill twice, with the differences you get from two hands rather than
* two intentions: 0.68rem against 0.7rem, a 14% tint against 15%, one with a
* border and one without.
*/
const props = defineProps<{ status: string }>();
const LABELS: Record<string, string> = {
active: "Active",
paused: "Paused",
completed: "Completed",
archived: "Archived",
};
const label = (s: string) => LABELS[s] ?? s;
</script>
<template>
<span :class="['project-status', `project-status--${props.status}`]">
{{ label(props.status) }}
</span>
</template>
<style scoped>
.project-status {
font-size: 0.7rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.04em;
padding: 0.15rem 0.5rem;
border-radius: var(--fs-radius-pill);
flex-shrink: 0;
white-space: nowrap;
}
/* Text is the hue mixed toward --fs-text-primary, not the raw hue. Both old
spellings painted the hue on a 15% tint of itself, which measured 1.61-2.39:1
against AA's 4.5 — the same defect the status and priority ladders had, and
invisible to the token checker because the background was an inline
color-mix rather than a `-bg` token. The checker was widened alongside this.
Measured worst-case over raised and hover in both modes: active 4.82:1,
paused 4.63:1, completed 4.78:1, archived 4.84:1.
No new design tokens: four values used by one component are the kind of
growth Scribe's own design-system note warns about ("if this system grows
past a handful of tokens, that is worth noticing rather than
accommodating"). The derivation is stated once, here. */
.project-status--active {
background: color-mix(in srgb, var(--fs-success) 15%, transparent);
color: color-mix(in srgb, var(--fs-success) 45%, var(--fs-text-primary));
}
.project-status--paused {
background: color-mix(in srgb, var(--fs-warning) 15%, transparent);
color: color-mix(in srgb, var(--fs-warning) 55%, var(--fs-text-primary));
}
.project-status--completed {
background: color-mix(in srgb, var(--fs-accent) 15%, transparent);
color: color-mix(in srgb, var(--fs-accent) 45%, var(--fs-text-primary));
}
.project-status--archived {
background: color-mix(in srgb, var(--fs-text-tertiary) 15%, transparent);
color: color-mix(in srgb, var(--fs-text-tertiary) 55%, var(--fs-text-primary));
}
</style>