Twelve commits, CI green on 5f8b824. Every button in the app changes appearance. This is a deliberate redesign, not a silent refactor.
What to look at, in order
Any page with buttons, light AND dark. Geometry, colour, weight and size have all moved.
The auth screens — Login / Register / Invite / Forgot / Reset. Their Submit button was violet and full-width; it is now moss green. Biggest single visual change.
The Design page (/design) — renders the real button classes and the three sizes as live specimens.
Dense surfaces — the workspace note/task panels, task logs, share dialog. These are where the inline size matters; if a card's rhythm looks off, that is the thing to report.
Trash, Settings → registration toggle, User management — each had a bug corrected (details below).
The visible changes, and why
Buttons follow the design system now (operator's call): 8px radius and a 12px label, replacing the app's 4px / ~14.4px. Rounder, smaller text.
Action buttons stopped wearing the accent. 21 of them filled with Scribe's violet — every auth Submit, plus Invite, Add, Confirm, Restore, Generate. The house style reserves the accent for identity and active state; action colours are universal so a Save button looks the same in every family app. Operator's reasoning: the violet-for-Scribe-actions treatment was a deliberate early choice made when far more of the UI was user-facing, and that premise has expired.
The accent now appears only on wordmark, active nav/tab/page, selection, tag pills, progress fills and brand-moment CTAs — which is what makes it read as identity rather than decoration.
Eleven buttons used font-weight: 600, which the system does not define (two weights: 400, 500).
Three sizes, because measurement showed the app has three kinds of button — page action, row action, and an affordance living inside a card. Their paddings clustered rather than spread. Forcing all ~100 one-offs to the single house size would have broken every card layout.
Bugs fixed on the way
ProjectListView had no disabled button style at all — disabled buttons rendered as enabled.
.btn-confirm-delete used the ERROR colour in UserManagement, destructive in Settings. Those are deliberately different: an error is a failure that happened, a destructive action is one about to.
.btn-danger-outline was defined twice inside one file, ~1,200 lines apart, at two paddings.
TrashView's hovers fell back to Tailwind indigo and red — off-palette literals.
SnippetEditorView's .btn-secondary was a ghost in disguise; two views tinted ghost labels with the accent on hover.
Five dead classes removed, one whose feature a comment in the same file records as deleted in Phase 8.
Also here
The write-path hook now asks the repo, not just the record. Scribe had never read a line of the codebase, so a helper nobody thought to record was invisible to prior-art recall — which is how .btn-primary reached four diverged copies. The hook runs on the developer's machine inside the repo, so it can simply look. Verified catching the real case with Scribe unconfigured. Plugin 0.1.22.
Numbers
button rules 125 -> 57 files 32 -> 16
net -1,257 lines across 33 files
Most of the 57 remaining are the shared alias block, one-line residuals, or genuinely different components wearing a btn- prefix.
Twelve commits, CI green on `5f8b824`. **Every button in the app changes appearance.** This is a deliberate redesign, not a silent refactor.
## What to look at, in order
1. **Any page with buttons, light AND dark.** Geometry, colour, weight and size have all moved.
2. **The auth screens** — Login / Register / Invite / Forgot / Reset. Their Submit button was violet and full-width; it is now moss green. Biggest single visual change.
3. **The Design page** (`/design`) — renders the real button classes and the three sizes as live specimens.
4. **Dense surfaces** — the workspace note/task panels, task logs, share dialog. These are where the inline size matters; if a card's rhythm looks off, that is the thing to report.
5. **Trash, Settings → registration toggle, User management** — each had a bug corrected (details below).
## The visible changes, and why
**Buttons follow the design system now** (operator's call): 8px radius and a 12px label, replacing the app's 4px / ~14.4px. Rounder, smaller text.
**Action buttons stopped wearing the accent.** 21 of them filled with Scribe's violet — every auth Submit, plus Invite, Add, Confirm, Restore, Generate. The house style reserves the accent for identity and active state; action colours are universal so a Save button looks the same in every family app. Operator's reasoning: the violet-for-Scribe-actions treatment was a deliberate early choice made when far more of the UI was user-facing, and that premise has expired.
The accent now appears **only** on wordmark, active nav/tab/page, selection, tag pills, progress fills and brand-moment CTAs — which is what makes it read as identity rather than decoration.
**Eleven buttons used `font-weight: 600`**, which the system does not define (two weights: 400, 500).
**Three sizes**, because measurement showed the app has three kinds of button — page action, row action, and an affordance living inside a card. Their paddings clustered rather than spread. Forcing all ~100 one-offs to the single house size would have broken every card layout.
## Bugs fixed on the way
- **ProjectListView had no disabled button style at all** — disabled buttons rendered as enabled.
- **`.btn-confirm-delete` used the ERROR colour in UserManagement**, destructive in Settings. Those are deliberately different: an error is a failure that happened, a destructive action is one about to.
- **`.btn-danger-outline` was defined twice inside one file**, ~1,200 lines apart, at two paddings.
- **TrashView's hovers fell back to Tailwind indigo and red** — off-palette literals.
- **SnippetEditorView's `.btn-secondary` was a ghost in disguise**; two views tinted ghost labels with the accent on hover.
- **Five dead classes removed**, one whose feature a comment in the same file records as deleted in Phase 8.
## Also here
**The write-path hook now asks the repo, not just the record.** Scribe had never read a line of the codebase, so a helper nobody thought to record was invisible to prior-art recall — which is how `.btn-primary` reached four diverged copies. The hook runs on the developer's machine inside the repo, so it can simply look. Verified catching the real case with Scribe unconfigured. Plugin 0.1.22.
## Numbers
button rules 125 -> 57 files 32 -> 16
net -1,257 lines across 33 files
Most of the 57 remaining are the shared alias block, one-line residuals, or genuinely different components wearing a `btn-` prefix.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
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
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
`.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
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
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
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
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
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
f491b6d — it survived that pass because it was a hover, not a fill.
.btn-tag-suggestion and .btn-chip-link stay bespoke, deliberately. They are
tag-shaped rather than button-shaped, and the house style does put the accent on
tags — so they are not drift, they are a different component wearing a btn-
prefix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
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
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
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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Twelve commits, CI green on
5f8b824. Every button in the app changes appearance. This is a deliberate redesign, not a silent refactor.What to look at, in order
/design) — renders the real button classes and the three sizes as live specimens.The visible changes, and why
Buttons follow the design system now (operator's call): 8px radius and a 12px label, replacing the app's 4px / ~14.4px. Rounder, smaller text.
Action buttons stopped wearing the accent. 21 of them filled with Scribe's violet — every auth Submit, plus Invite, Add, Confirm, Restore, Generate. The house style reserves the accent for identity and active state; action colours are universal so a Save button looks the same in every family app. Operator's reasoning: the violet-for-Scribe-actions treatment was a deliberate early choice made when far more of the UI was user-facing, and that premise has expired.
The accent now appears only on wordmark, active nav/tab/page, selection, tag pills, progress fills and brand-moment CTAs — which is what makes it read as identity rather than decoration.
Eleven buttons used
font-weight: 600, which the system does not define (two weights: 400, 500).Three sizes, because measurement showed the app has three kinds of button — page action, row action, and an affordance living inside a card. Their paddings clustered rather than spread. Forcing all ~100 one-offs to the single house size would have broken every card layout.
Bugs fixed on the way
.btn-confirm-deleteused the ERROR colour in UserManagement, destructive in Settings. Those are deliberately different: an error is a failure that happened, a destructive action is one about to..btn-danger-outlinewas defined twice inside one file, ~1,200 lines apart, at two paddings..btn-secondarywas a ghost in disguise; two views tinted ghost labels with the accent on hover.Also here
The write-path hook now asks the repo, not just the record. Scribe had never read a line of the codebase, so a helper nobody thought to record was invisible to prior-art recall — which is how
.btn-primaryreached four diverged copies. The hook runs on the developer's machine inside the repo, so it can simply look. Verified catching the real case with Scribe unconfigured. Plugin 0.1.22.Numbers
Most of the 57 remaining are the shared alias block, one-line residuals, or genuinely different components wearing a
btn-prefix.🤖 Generated with Claude Code
https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
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 inf491b6d— it survived that pass because it was a hover, not a fill. .btn-tag-suggestion and .btn-chip-link stay bespoke, deliberately. They are tag-shaped rather than button-shaped, and the house style does put the accent on tags — so they are not drift, they are a different component wearing a btn- prefix. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs