Buttons: one definition, aligned to the design system — plus local prior-art recall #94
Merged
bvandeusen
merged 12 commits from 2026-08-02 18:50:04 -04:00
dev into main
12
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5f8b824523 |
refactor(ui): the remaining views migrate; 2 dead classes, 2 off-palette hovers
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 30s
CI & Build / TypeScript typecheck (push) Successful in 45s
CI & Build / integration (push) Successful in 1m50s
CI & Build / Python tests (push) Successful in 2m13s
CI & Build / Build & push image (push) Successful in 42s
Twelve more files onto the shared buttons. What the pass turned up: DEAD, verified not merely unnamed: - .btn-reconsolidate (TaskEditorView). A comment eleven hundred lines up in the same file says the feature was removed in Phase 8. The CSS outlived it. - .btn-remove-slot (SettingsView), style rules only, no template anywhere. OFF-PALETTE, the #2319 shape: TrashView's restore and purge hovers used `var(--color-primary, #6366f1)` and `var(--color-danger, #ef4444)` — Tailwind indigo and Tailwind red, from no palette in this system. The fallback is what renders if the token is ever absent, and it renders something plausible forever. Now the action and destructive colours, no fallback. A REAL BREAKAGE MY OWN CHECK COULD NOT SEE, worth recording. Deleting a rule whose selector was part of a comma-separated group left the leading selectors behind: .btn-log-edit, <nothing> .log-textarea { … } which silently swallows the next rule. Brace counting passed — there are no braces in a dangling fragment. Found by scanning for selector lines ending in `,` not followed by another selector; three instances across two files, one of them interleaved with comments so the first sweep missed it. The sweep is now part of the verification, not a one-off. Kept bespoke, deliberately: .btn-pin/.btn-unpin (pill-shaped history badges), .btn-add-share and .btn-new-note (gradient CTAs — brand moments, which the house style does sanction), .btn-icon/.btn-bell (icon buttons, a different component), .btn-add-system/.btn-add-milestone (dashed "add" affordances). These are not drift; they are other things wearing a btn- prefix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
3fc693443e |
refactor(ui): the settings family migrates; two colour bugs and a dead class
CI & Build / Python lint (push) Successful in 7s
CI & Build / Plugin hooks (push) Successful in 36s
CI & Build / TypeScript typecheck (push) Successful in 49s
CI & Build / integration (push) Successful in 2m16s
CI & Build / Python tests (push) Successful in 2m51s
CI & Build / Build & push image (push) Successful in 1m15s
SettingsView and UserManagementView had near-identical button vocabularies — btn-delete, btn-cancel-delete, btn-confirm-delete, btn-toggle/-open/-close — defined separately in each. Parallel duplication (#2278's shape), and it had already diverged twice: - .btn-confirm-delete used --color-danger in UserManagement and --color-action-destructive in Settings. Those are different colours on purpose: the house style keeps error (something went wrong) distinct from destructive (something is about to). A delete confirmation is destructive. UserManagement was showing the error colour for a button nothing had failed in yet. - .btn-remove-slot's hover reached for --color-danger for the same reason, and is the same correction. It turned out to be dead anyway — style rules only, no template reference anywhere in the app — so it is gone. .btn-danger-outline was defined TWICE inside SettingsView, at 0.4rem 0.9rem and 0.45rem 1rem. One file, one class, two geometries, ~1200 lines apart. That is the clearest single argument for this whole task that I have found: the drift does not need two files, only enough distance that nobody sees both at once. The registration toggle keeps .btn-toggle-close, and only that. It is bound dynamically (:class="registrationOpen ? … : …"), so a name-based scan reads it as unused — checked before deleting. .btn-toggle-open went, because btn-primary now says the same thing; the close state stays because it must NOT read as the primary action it sits on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
3d6931b838 |
refactor(ui): editor-shared buttons alias onto the variants; 3 dead classes go
CI & Build / Python lint (push) Successful in 7s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Successful in 32s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / Python tests (push) Successful in 1m51s
CI & Build / Build & push image (push) Canceled after 38s
editor-shared.css defined thirteen button rules with hand-written geometry. Ten are now thin aliases onto the shared variants — same class names, because these are used across six views and pointing a name somewhere is cheaper than rewriting every call site (the .btn-small precedent). Three were DEAD: .btn-assist-toggle, .btn-close-assist and .btn-toggle-view had no template reference and no dynamic binding anywhere in the app. Verified before deleting rather than assumed from the name — a class with no user is indistinguishable from one bound dynamically until you look. Named honestly in the file: CSS has no @extend, so each alias carries the variant's declarations rather than inheriting them. That is duplication this migration cannot remove. But it is duplication of a REFERENCE — var(--color- action-primary) — not of a value, so a palette change still moves everything at once, which is the property that actually mattered. Also gone: eight hardcoded geometries (0.4rem 1rem, 0.85rem, and so on) that now come from --fs-space and --fs-size tokens. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
c7cf07824a |
refactor(ui): the two workspace panels migrate onto the shared buttons
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / TypeScript typecheck (push) Successful in 38s
CI & Build / Python tests (push) Canceled after 1m35s
CI & Build / integration (push) Canceled after 1m35s
CI & Build / Build & push image (push) Canceled after 0s
Eighteen bespoke button rules across the two densest components, replaced by
composition in the template. Net -279 lines.
These files are where the size taxonomy earns itself: almost every button here
is an inline affordance — a dismiss ×, a confirm tick, an add-chip — sitting
inside a card or a line of text. Forcing them to the standard 8/16px would have
broken the layouts, which is why the previous commit measured the clusters
before assuming a button is a button.
What the migration left behind is the useful signal. Each residual rule is now
one line stating only what the shared classes genuinely cannot:
.btn-add { font-size: 1rem; } a '+' glyph, not a label
.btn-search-clear { padding: 0; flex-shrink: 0; } sits in the field
.btn-suggest-tags { flex-shrink: 0; align-self: center; }
.btn-delete-task { margin-left: 0.25rem; }
Four residuals were deleted rather than kept, because the shared sheet already
said the same thing: a disabled opacity, two hover colours, and a danger-outline
hover fill. Keeping them would have recreated the drift in miniature.
Two accent hovers went with them. .btn-suggest-tags tinted its border and label
with the accent on hover, which is the same house-style violation corrected in
|
||
|
|
67fdf7c55b |
refactor(ui): auth views migrate onto the shared buttons; two variants added
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 42s
CI & Build / integration (push) Successful in 1m39s
CI & Build / Python tests (push) Successful in 2m4s
CI & Build / Build & push image (push) Canceled after 0s
The five auth views each defined .btn-submit identically — full-width, filled, 0.6rem — and LoginView additionally defined .btn-oauth. Those rules are now gone entirely rather than tokenised: the template composes `btn-primary btn-block` and `btn-ghost btn-block`, and there is nothing left per-file to drift. That is the difference between this and the earlier chunks. Consolidating the core four moved geometry into one place but left every semantic name defining its own; this removes the definition. Two variants added, both earned rather than invented: - .btn-text — no fill, no border. The most common shape in the dense surfaces (dismiss, cancel-beside-confirm, clear-search) where a border would draw a box around something that should read as an action on the adjacent text. Distinct from ghost, which IS a box. - .btn-danger-outline — already existed independently in three views before this sheet, which is what makes it a variant and not a one-off. It is what a delete looks like when it must not shout. .btn-block composes with a variant rather than being one, because width is orthogonal to appearance. Also corrected the sheet's own header, which claimed "no template changes" — true when it was written, false as of this commit. It now states the actual model: a button is variant + size, composed in the template. Semantic per-view names are named as the thing that drifted, and why: a name says what a button is FOR and nothing about what it should look like, so two buttons doing the same job in two views had no reason to match, and didn't. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
37616682f0 |
feat(ui): three button sizes, because the app has three kinds of button
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 10s
CI & Build / integration (push) Successful in 31s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 1m4s
CI & Build / Build & push image (push) Successful in 45s
Measured before deciding: across the ~100 bespoke button rules, vertical padding does not spread — it clusters. ~27 at 0.4–0.45rem, ~28 at 0.25–0.3rem, ~23 at 0.1–0.15rem. Those are three different components that happen to share a name prefix: a page action, a row action, and an affordance that lives inside a card. Collapsing them to the single size the shared sheet had would have visibly broken every card layout, which is why the one-off migration stopped here for a decision rather than proceeding on the assumption that a button is a button. default 8px 16px page action — what the house style specifies .btn-compact 4px 12px toolbar, table row, list item controls .btn-inline 2px 4px dismiss ×, confirm tick, add-chip .btn-small and .btn-sm already sat at the compact step, so they are kept as aliases for it — no template churn, and the two spellings stop being a third thing that might drift. .btn-inline is deliberately below the spacing scale's first step on the vertical axis: 4px of padding on an 11px label already exceeds the line box these sit in. Stated in the file so it reads as a measured exception rather than someone ignoring the scale. DesignView renders all three as real specimens. A size scale described in prose is one nobody can check; rendered from the actual classes, it cannot claim something the app does not do. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
97b93bcaea |
refactor(ui): buttons stop using weights the system doesn't have
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Successful in 21s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / Python tests (push) Successful in 1m10s
CI & Build / Build & push image (push) Successful in 36s
Eleven buttons set font-weight 600. The house style permits exactly two weights, 400 and 500, and says so explicitly — 600 and 700 are not part of the system. Every auth Submit, plus Invite, Toggle, Confirm-delete, Add-share, the OAuth button and the assist Reject. Now var(--fs-weight-medium), which is 500. Buttons get very slightly lighter. Small on its own, but it is the third kind of drift the same five auth views have now produced: geometry that differed per file, an accent fill the style forbids, and a weight the system does not define. None of the three was a deliberate choice — each is what happens when a button is written by copying the nearest existing one. Weight declarations only. No geometry, no colour, no templates. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
f491b6d7b9 |
refactor(ui): action buttons stop wearing the accent
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 7s
CI & Build / integration (push) Successful in 18s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 46s
CI & Build / Build & push image (push) Successful in 47s
21 buttons filled with --color-primary, which resolves to Scribe's violet accent: every auth Submit (Login, Register, Invite, Forgot, Reset), plus Invite, Add, Confirm, Restore, Generate, Log-save, Subtask-confirm, Toggle-open, the modal primary, the version-restore, the inline-assist button, the milestone-plan actions, the task-advance hover, and both empty-state CTAs. The house style is explicit that the accent never appears on an action button: action colours are universal across the family precisely so a Save button looks identical in every app, while the accent carries identity. Doing both makes the accent mean two things and neither clearly. Operator's call, and the reasoning is worth keeping: the violet-on-Scribe- actions treatment was a deliberate early choice to give the web UI its own personality, made when much more of the app was user-facing. That is no longer true, so consistency is now worth more than the distinction it was buying. Found in two passes, which is the part worth noting. The first scan looked for `.btn-*` and found 13. Seven more were the same thing under different names — .modal-btn-primary, .vh-btn-restore, .inline-assist-btn, .empty-action, .task-advance-btn — plus .ms-plan-actions .btn-primary, a compound override flagged in the previous commit. Searching by naming convention finds what was named consistently, which is never the whole set. Deliberately NOT changed: progress-bar fills, active tab / page / selection states, tag-pill hover, the duration badge, the skip link, the assist pulse. Those are identity and active-state, which is exactly where the accent belongs. After this the accent appears only there, which is what makes it read as identity rather than as decoration. Colour swaps only — 25 lines changed, no geometry, no structure, no templates. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
1a959b1db0 |
refactor(ui): one button definition, aligned to the design system
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / TypeScript typecheck (push) Successful in 36s
CI & Build / integration (push) Successful in 39s
CI & Build / Python tests (push) Successful in 1m12s
CI & Build / Build & push image (push) Successful in 1m12s
`.btn-primary` was defined five times in five scoped stylesheets and all five had drifted: three paddings, three font sizes, three disabled opacities — and ProjectListView had no disabled style at all, so a disabled button there looked enabled. Nothing detected any of it. A scoped duplicate is not a rule violation, not a broken reference, and not a recorded snippet, so no existing check could see it (#2273). assets/components.css is now the single definition of the core four — primary, secondary, ghost, danger — plus the small modifier, in design-system tokens throughout. Operator's call to align to the system rather than to the majority of current values, so buttons move to the 8px radius and 12px label the system specifies, from the app's 4px/14.4px. Class names are unchanged, so there are no template edits: the existing surface is repurposed, not rebuilt. STAGING PROPERTY that makes this safe to land ahead of the rest: a Vue `<style scoped>` rule compiles to `.btn-primary[data-v-…]` (specificity 0,2,0) and beats a plain global selector (0,1,0). So the shared sheet changes nothing for a view still carrying its own copy, and every intermediate state of the remaining migration is coherent rather than half-applied. Two divergences corrected on the way, both worth naming: - SnippetEditorView's `.btn-secondary` was a GHOST in disguise — outline styling under the secondary name, while the house style says secondary is filled bronze. It now looks like what it is called. - SnippetDetailView and SnippetListView tinted a ghost button's label with the ACCENT on hover. The house style reserves the accent for identity and active state, never general chrome. DesignView reported "no shared button exists" as an honest gap and declined to draw a look-alike. That gap is closed, so it now renders the app's real classes — the specimens cannot drift from the app without drifting the app. Net -177 lines. Follows: the ~20 semantic one-offs (.btn-save, .btn-delete, .btn-toggle …) and the compound overrides in ProjectView, one of which puts the accent on a primary action button. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
6d01788326 |
test(plugin): pin both halves of the local prior-art arm
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / integration (push) Successful in 28s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 54s
CI & Build / Build & push image (push) Successful in 21s
CI caught the previous commit: the fail-open contract asserts a hook stays SILENT with no working instance, and the new local arm deliberately speaks there. The invariant is right and the hook is right — the smoke event was wrong. It used `def f`, which this repo really does define, so the hook found something and "silent" was asserting the wrong thing. Fixed by asserting the two properties separately: - SILENT for a symbol that genuinely does not exist. - SPEAKING, with NO credentials, for one that does. That is the point of the arm — the other arms ask Scribe what was RECORDED; this one asks the repo what EXISTS, which needs no instance. Were it to start depending on configuration it would stop covering the case it was built for, and only this assertion would notice. Second trap, hit while fixing the first: spelling the absent symbol out in full wrote `def <name>(` into check_plugin.py, so the smoke event DEFINED the very symbol it claimed was missing, and the hook found it again. The name is now assembled from fragments so the contiguous string never appears in the source. scribe_prior_art.sh joins scribe_session_context.sh as a hook that legitimately produces output without credentials — for the same reason, that it carries something needing neither network nor config. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
17d59fa3e0 |
feat(prior-art): ask the repo, not just the record, before writing a definition
CI & Build / Plugin hooks (push) Failing after 6s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python lint (push) Successful in 2s
CI & Build / integration (push) Successful in 31s
CI & Build / Python tests (push) Successful in 56s
CI & Build / Build & push image (push) Successful in 21s
Scribe has never read a line of the codebase. Every Drafter surface recalls from the RECORD — things someone deliberately recorded — so a helper nobody thought to record is invisible to all of them. That is how `.btn-primary` came to be defined four times, in four scoped stylesheets, already diverged: it was never a snippet, so no threshold and no query rewrite could ever have surfaced it (#2280). The write-path hook already runs on the developer's machine, inside the repo, holding the code about to be written. It can simply look. No index, no storage, no staleness story, no server round-trip. Verified against this repo with Scribe unconfigured: .btn-primary is already defined in 4 other file(s): DesignSystemsView.vue ProjectListView.vue SettingsView.vue SnippetEditorView.vue Three properties it needs, all checked by hand: - DEFINITION-shaped patterns only. Grepping bare occurrences would match every call site and bury the real finding, and a hint that is mostly noise is one people learn to skip — worse than none. A payload containing only calls to embed_note() stays silent; one containing `def embed_note` does not. - The target file is excluded, so editing the file that already defines something doesn't report it against itself. - It runs when Scribe is UNCONFIGURED, and a failed request no longer discards it. The remote arms answer "what was recorded"; this one answers "what exists", and that question needs no instance to produce an answer. `curl || exit 0` became `curl || true` for the same reason. Plugin 0.1.21 -> 0.1.22. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
6a6a388ecd |
docs: Fabled-Git, not Forgejo, in ci-requirements
The instance has run Gitea since the migration. Prose only — no workflow or path change. Scribe issue #2272. |