The workflow
I wanted unread threads to stand out in the sidebar, so I made a custom palette (Settings → Appearance → Palette → Create / bb theme set) intending to recolor the threads-list unread marker to a bright accent. There is no theme token for the marker, so a palette cannot target it: the only levers are overriding --muted-foreground (which recolors every secondary-text surface in the app) or writing CSS against compiled Tailwind utility class names, which silently breaks whenever the implementation classes change.
What happens today
The unread marker's color is a hardcoded alpha step on the secondary-text token, in both consumers:
- Web:
SIDEBAR_SUCCESS_STATUS_DOT_CLASS paints bg-muted-foreground/60 —
|
export const SIDEBAR_SUCCESS_STATUS_DOT_CLASS = |
|
"size-[5px] rounded-full bg-muted-foreground/60 max-md:pointer-coarse:size-1.5"; |
- Mobile: the unread-success case in
ThreadStatusGlyph paints the same literal class —
|
className="h-2 w-2 rounded-full bg-muted-foreground/60" |
The working custom-palette workaround (fragile by construction, keyed to compiled class names):
span.size-\[5px\].rounded-full.bg-muted-foreground\/60 {
background-color: oklch(0.6 0.2 145) !important;
}
What you would expect
A dedicated --unread-dot role token, following the same pattern as --version-upgrade/--surface-attention: defined in both mode blocks of theme.css as color-mix(in oklab, var(--muted-foreground) 60%, transparent) (the exact mix the current utility compiles to, so default rendering and palettes that re-anchor --muted-foreground are pixel-identical), bridged as --color-unread-dot, with both consumers on bg-unread-dot. A palette then recolors the marker with one line and nothing else moves.
Prototype (reviewed and tested, rebased on 31d66d9d4): ben-vargas@c635944 (branch feat/unread-dot-theme-token). 7 files, +44/−2:
theme.css token + Tailwind bridge; theme.test.ts guards (bridge registered; both modes pin the exact 60% oklab derivation, as the visual-parity invariant).
- Mobile lockstep:
--color-unread-dot in global.css, theme.native.ts regenerated via theme:generate (unreadDot on every palette × mode; regeneration reproduces the committed file byte-identically), mobile glyph consumer updated.
bb-cli/references/theming.md token-table row.
- Verified:
turbo run test --filter=@bb/app --filter=@bb/mobile passes (including generate-native-theme.test.ts / theme-vars.test.ts); typecheck for @bb/app, @bb/mobile, @bb/server clean.
Happy to open the PR if this gets sign-off.
Context and alternatives
The sibling working-status glyphs have the identical gap, but I deliberately left them out of the prototype because the token shape there is a design decision I'd rather leave to you. SIDEBAR_WORKING_STATUS_COLOR_CLASS (text-muted-foreground/50,
|
export const SIDEBAR_WORKING_STATUS_COLOR_CLASS = "text-muted-foreground/50"; |
) is shared by seven indicator kinds — the runtime spinner plus the shine-animated workflow / background-agent / background-command / plan-mode / goal / working-draft glyphs. A single family-wide
--working-status token preserves the current one-tier semantics but recolors all seven together; per-kind tokens allow finer theming but split a deliberately unified tier. If you have a preference, I'm glad to do a follow-up matching this prototype's pattern.
Ruled out: overriding --muted-foreground in a palette (recolors all secondary text); scoping the override to the marker via its wrapper ([data-sidebar-thread-trailing-indicator]) still keys on implementation DOM rather than a token contract.
Checks
AGENT GENERATED
The workflow
I wanted unread threads to stand out in the sidebar, so I made a custom palette (Settings → Appearance → Palette → Create /
bb theme set) intending to recolor the threads-list unread marker to a bright accent. There is no theme token for the marker, so a palette cannot target it: the only levers are overriding--muted-foreground(which recolors every secondary-text surface in the app) or writing CSS against compiled Tailwind utility class names, which silently breaks whenever the implementation classes change.What happens today
The unread marker's color is a hardcoded alpha step on the secondary-text token, in both consumers:
SIDEBAR_SUCCESS_STATUS_DOT_CLASSpaintsbg-muted-foreground/60—bb/apps/app/src/components/sidebar/sidebarRowClasses.ts
Lines 27 to 28 in 31d66d9
ThreadStatusGlyphpaints the same literal class —bb/apps/mobile/src/screens/sidebar/ThreadStatusGlyph.tsx
Line 61 in 31d66d9
The working custom-palette workaround (fragile by construction, keyed to compiled class names):
What you would expect
A dedicated
--unread-dotrole token, following the same pattern as--version-upgrade/--surface-attention: defined in both mode blocks oftheme.cssascolor-mix(in oklab, var(--muted-foreground) 60%, transparent)(the exact mix the current utility compiles to, so default rendering and palettes that re-anchor--muted-foregroundare pixel-identical), bridged as--color-unread-dot, with both consumers onbg-unread-dot. A palette then recolors the marker with one line and nothing else moves.Prototype (reviewed and tested, rebased on
31d66d9d4): ben-vargas@c635944 (branchfeat/unread-dot-theme-token). 7 files, +44/−2:theme.csstoken + Tailwind bridge;theme.test.tsguards (bridge registered; both modes pin the exact 60% oklab derivation, as the visual-parity invariant).--color-unread-dotinglobal.css,theme.native.tsregenerated viatheme:generate(unreadDoton every palette × mode; regeneration reproduces the committed file byte-identically), mobile glyph consumer updated.bb-cli/references/theming.mdtoken-table row.turbo run test --filter=@bb/app --filter=@bb/mobilepasses (includinggenerate-native-theme.test.ts/theme-vars.test.ts); typecheck for@bb/app,@bb/mobile,@bb/serverclean.Happy to open the PR if this gets sign-off.
Context and alternatives
The sibling working-status glyphs have the identical gap, but I deliberately left them out of the prototype because the token shape there is a design decision I'd rather leave to you.
SIDEBAR_WORKING_STATUS_COLOR_CLASS(text-muted-foreground/50,bb/apps/app/src/components/sidebar/sidebarRowClasses.ts
Line 23 in 31d66d9
--working-statustoken preserves the current one-tier semantics but recolors all seven together; per-kind tokens allow finer theming but split a deliberately unified tier. If you have a preference, I'm glad to do a follow-up matching this prototype's pattern.Ruled out: overriding
--muted-foregroundin a palette (recolors all secondary text); scoping the override to the marker via its wrapper ([data-sidebar-thread-trailing-indicator]) still keys on implementation DOM rather than a token contract.Checks
> AGENT GENERATED.