Skip to content

feat(ui): full-intensity accent sidebar by default (OB-377)#104

Merged
eliotlim merged 5 commits into
spike/ob-375-colour-manifestfrom
feat/ob-377-sidebar-full-accent
Jul 5, 2026
Merged

feat(ui): full-intensity accent sidebar by default (OB-377)#104
eliotlim merged 5 commits into
spike/ob-375-colour-manifestfrom
feat/ob-377-sidebar-full-accent

Conversation

@eliotlim

@eliotlim eliotlim commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Problem

The sidebar only ever showed a pale hue-tint of the accent. Owner wanted the accent available at FULL intensity — as an option, with tint staying the default, and the full-accent state must be legible (the first pass shipped low-contrast warm-tan text on the accent surface). Board: OB-377.

Solution

New "Sidebar" appearance control — Tinted (default) / Full accent:

  • Tinted reproduces the existing sheet computation (default users unchanged; interfaceIntensity drives tint saturation as before).
  • Full accent renders the accent surface (veil hover/active/press, flipped light foregrounds, charcoal panel for gray accents, neutral desk).

Root-cause contrast fix: Tailwind utilities read the --color-* intermediates, which resolve their nested var(--muted-foreground) at :root — so the earlier subtree remap of the base token never reached de-emphasized text. The gated [data-sidebar='accent'] .ob-accent-chrome now overrides the --color-* tokens, so every sidebar text/icon resolves to the light sheet foreground. A committed real-DOM contrast guard measures computed colours composited over effective backgrounds (text ≥4.5:1, icons ≥3:1) across default/ocean/graphite × light/dark — replacing the idealized token sweep that missed the bug.

Key files: themes.ts, index.css, sidebarStyles.ts, AppearanceSettings.tsx, tree.tsx. New deps: none.

Before / After — the two modes of the new control

Mode Light Dark
Tinted (default) tinted light
familiar pale look, blue CTA
tinted dark
Full accent (option) accent light
top nav / headers / card now crisp white
accent dark
Accent range (light) graphite accent
charcoal panel (gray accent)
ocean accent
bold accent

Test procedure

  • pnpm verify green (sdk 152 · ui 863 · server 586 · e2e 251+39) + the committed web accent-contrast guard 6/6 (real-DOM measured).
  • Settings → Appearance → Sidebar → Full accent to try the option.

Operational notes

Footer: verify green + real-DOM contrast guard 6/6. Design gate: the contrast concern is now covered by the committed measured guard (replaces the idealized sweep); owner-reviewed visually — formal Devon re-gate available on request.

🤖 Generated with Claude Code

https://claude.ai/code/session_01X78XTAemKrFw1uJpDpCEaG

eliotlim and others added 3 commits July 5, 2026 21:08
…utral desk (OB-377)

composeAppearance now derives sheet-1/2 from each theme's primary per the
colour-consistency manifest §2.1–2.4: light = primary verbatim with the
audited per-theme sheet-darken (default 49→44, forest 38→31, teal 38→30)
and ink-flip (sunset + three pastels) escape hatches; dark = a deep accent
shade (clamp(0.7·S,12,60), L 24/28.5) with a 0 0% 93% foreground. Sheet
foregrounds stop tracking --foreground and a --sheet-veil overlay pole is
composed per theme. Adds a neutral --desk token, repoints .ob-desk and the
present backdrop onto it, and reworks sidebarStyles hover/active/press to
veil washes. Full accent lands at interfaceIntensity ≥ 2 (the existing
default) so the rollout needs no migration; levels 0/1 keep the flat/soft
panel. index.css first-paint tokens track the composed default.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X78XTAemKrFw1uJpDpCEaG
…B-377)

The full-accent sheets broke the sidebar's neutral text/icon/hover tokens.
Adds a scoped .ob-accent-chrome remap (muted/accent foreground → the full
sheet foreground per manifest §2.2 'de-emphasis via size/weight only';
hover/active → --sheet-veil washes) on the three accent-chrome surfaces:
the sidebar Drawer, the settings nav rail, and the titlebar (--foreground
left intact so the active titlebar tab's light chip keeps its ink). Body
rows/labels drop their alpha tiers (which fail 4.5:1 on the saturated
sheet) onto the full sheet foreground — Tree primitive, Favorites, flat
page rows, section labels — and the Tree drop indicators repaint with the
sheet foreground so they read on the accent.

Content surfaces that only borrowed --sheet-1 as a subtle raised card move
to the neutral bg-muted (AgentPanel, ImportDialog drop zones/options,
review comment/suggestion cards); the floating workspace switcher reverts
to the neutral popover default. Portaled menus/dialogs sit outside the
remapped subtrees, so they stay neutral.

Tests: encodes the §2.2 audited table for the archetypes + a WCAG sweep
asserting fg-on-sheet ≥ 4.5:1 for all 17 themes × 2 sheets × 2 schemes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X78XTAemKrFw1uJpDpCEaG
- Tree row icons/chevron: fg@70% → 75% on the sheet (manifest §2.2 sanctions
  75% for non-text icons; rose row icons were ~2.86:1 < 3:1, now ~3.12).
- Unify dark-scheme sidebar hover: .ob-accent-chrome now sets the audited
  8/13 % veil alphas in dark (matching SIDEBAR_*), so the page tree's shared
  bg-hover/bg-hover-strong washes read at the same strength as favorites.
- OnboardingNudge sign-in CTA → secondary variant: a primary-blue fill barely
  separated from the full-accent sidebar sheet; a light neutral chip reads.
- Note the now-unconsumed --sheet-2 painted token (kept, not dead-stripped).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X78XTAemKrFw1uJpDpCEaG
@vercel

vercel Bot commented Jul 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
app.book.pub Ready Ready Preview, Comment Jul 5, 2026 2:41pm

Request Review

eliotlim and others added 2 commits July 5, 2026 22:34
…egible (OB-377)

Owner re-scope: the pale accent *tint* is the DEFAULT again; the OB-377 full
accent is an opt-in. Adds `sidebar: 'tinted' | 'accent'` to AppearanceOptions
(default 'tinted'; normalizeAppearance keeps existing users tinted, drops an
invalid value) + a Segmented 'Sidebar' control in Appearance settings.
composeAppearance's 'tinted' branch reproduces the pre-OB-377 sheet computation
(interfaceIntensity drives its saturation again); 'accent' is the audited full
surface. applyAppearance writes `data-sidebar` on <html> to gate the CSS.

Fixes the real contrast bug: `.ob-accent-chrome` remapped the base tokens
(`--muted-foreground`), but `text-muted-foreground`/`bg-hover` resolve the
Tailwind `--color-*` intermediates, which are declared at :root and substitute
their nested var() there — so the subtree override never reached them, leaving
the top nav / section headers / nudge body in a low-contrast muted hue. Now the
gated `[data-sidebar='accent'] .ob-accent-chrome` overrides the `--color-*`
tokens, so every de-emphasised sidebar element resolves to the light sheet
foreground. sidebarStyles route through `bg-hover`/`bg-hover-strong` (primary
tint when tinted, veil wash when accent). OnboardingNudge CTA is mode-aware
(primary on the pale tint, secondary chip on the accent sheet).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X78XTAemKrFw1uJpDpCEaG
Verifies the OPT-IN full-accent sidebar on the real DOM (getComputedStyle
colour composited over its effective background → WCAG contrast), not idealized
token math. Audits every visible sidebar text/icon — top nav, section headers,
favourites, page tree + chevrons/icons, the OnboardingNudge card, workspace
switcher — across default / ocean / graphite accents in light AND dark; asserts
text >= 4.5:1, icons >= 3:1. The aria-hidden account monogram (a decorative
identity badge with its own hue background) is excluded. Measured worst cases:
text 4.58 (default light) .. 7.23, icon 4.58 .. 10.4 — all pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X78XTAemKrFw1uJpDpCEaG
@eliotlim eliotlim marked this pull request as ready for review July 5, 2026 14:40
@eliotlim eliotlim merged commit 2c47474 into spike/ob-375-colour-manifest Jul 5, 2026
7 checks passed
eliotlim added a commit that referenced this pull request Jul 6, 2026
…data colours (integration) (#107)

* docs(design): OB-375 colour-consistency manifest — unified data palette + sidebar full-accent spec

Spike deliverable: canonical 12-token data palette (Pastel/Vivid/Muted x
fill/chip roles) unifying SELECT_COLORS, SWATCH_HEX, CHART_PALETTE, kit
PALETTE and the status trio; sidebar full-intensity accent sheet model
with per-theme contrast overrides, veil hover/active/press treatment,
interfaceIntensity 0-3 mapping and neutral --desk token; complete
computed WCAG audit (68/68 sheet pairs, 102/102 wash states, all chip
pairs >=4.5:1) with the generator script committed alongside; storage/
API sketch for AppearanceOptions.dataColors + sdk dataColors module.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X78XTAemKrFw1uJpDpCEaG

* feat(ui): full-intensity accent sidebar by default (OB-377) (#104)

* feat(ui): full-accent sidebar sheets — §2 derivation, veil hovers, neutral desk (OB-377)

composeAppearance now derives sheet-1/2 from each theme's primary per the
colour-consistency manifest §2.1–2.4: light = primary verbatim with the
audited per-theme sheet-darken (default 49→44, forest 38→31, teal 38→30)
and ink-flip (sunset + three pastels) escape hatches; dark = a deep accent
shade (clamp(0.7·S,12,60), L 24/28.5) with a 0 0% 93% foreground. Sheet
foregrounds stop tracking --foreground and a --sheet-veil overlay pole is
composed per theme. Adds a neutral --desk token, repoints .ob-desk and the
present backdrop onto it, and reworks sidebarStyles hover/active/press to
veil washes. Full accent lands at interfaceIntensity ≥ 2 (the existing
default) so the rollout needs no migration; levels 0/1 keep the flat/soft
panel. index.css first-paint tokens track the composed default.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X78XTAemKrFw1uJpDpCEaG

* feat(ui): read every sidebar-chrome consumer on the accent surface (OB-377)

The full-accent sheets broke the sidebar's neutral text/icon/hover tokens.
Adds a scoped .ob-accent-chrome remap (muted/accent foreground → the full
sheet foreground per manifest §2.2 'de-emphasis via size/weight only';
hover/active → --sheet-veil washes) on the three accent-chrome surfaces:
the sidebar Drawer, the settings nav rail, and the titlebar (--foreground
left intact so the active titlebar tab's light chip keeps its ink). Body
rows/labels drop their alpha tiers (which fail 4.5:1 on the saturated
sheet) onto the full sheet foreground — Tree primitive, Favorites, flat
page rows, section labels — and the Tree drop indicators repaint with the
sheet foreground so they read on the accent.

Content surfaces that only borrowed --sheet-1 as a subtle raised card move
to the neutral bg-muted (AgentPanel, ImportDialog drop zones/options,
review comment/suggestion cards); the floating workspace switcher reverts
to the neutral popover default. Portaled menus/dialogs sit outside the
remapped subtrees, so they stay neutral.

Tests: encodes the §2.2 audited table for the archetypes + a WCAG sweep
asserting fg-on-sheet ≥ 4.5:1 for all 17 themes × 2 sheets × 2 schemes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X78XTAemKrFw1uJpDpCEaG

* fix(ui): review nits on the full-accent sidebar (OB-377)

- Tree row icons/chevron: fg@70% → 75% on the sheet (manifest §2.2 sanctions
  75% for non-text icons; rose row icons were ~2.86:1 < 3:1, now ~3.12).
- Unify dark-scheme sidebar hover: .ob-accent-chrome now sets the audited
  8/13 % veil alphas in dark (matching SIDEBAR_*), so the page tree's shared
  bg-hover/bg-hover-strong washes read at the same strength as favorites.
- OnboardingNudge sign-in CTA → secondary variant: a primary-blue fill barely
  separated from the full-accent sidebar sheet; a light neutral chip reads.
- Note the now-unconsumed --sheet-2 painted token (kept, not dead-stripped).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X78XTAemKrFw1uJpDpCEaG

* fix(ui): default the sidebar to tinted; make the full-accent option legible (OB-377)

Owner re-scope: the pale accent *tint* is the DEFAULT again; the OB-377 full
accent is an opt-in. Adds `sidebar: 'tinted' | 'accent'` to AppearanceOptions
(default 'tinted'; normalizeAppearance keeps existing users tinted, drops an
invalid value) + a Segmented 'Sidebar' control in Appearance settings.
composeAppearance's 'tinted' branch reproduces the pre-OB-377 sheet computation
(interfaceIntensity drives its saturation again); 'accent' is the audited full
surface. applyAppearance writes `data-sidebar` on <html> to gate the CSS.

Fixes the real contrast bug: `.ob-accent-chrome` remapped the base tokens
(`--muted-foreground`), but `text-muted-foreground`/`bg-hover` resolve the
Tailwind `--color-*` intermediates, which are declared at :root and substitute
their nested var() there — so the subtree override never reached them, leaving
the top nav / section headers / nudge body in a low-contrast muted hue. Now the
gated `[data-sidebar='accent'] .ob-accent-chrome` overrides the `--color-*`
tokens, so every de-emphasised sidebar element resolves to the light sheet
foreground. sidebarStyles route through `bg-hover`/`bg-hover-strong` (primary
tint when tinted, veil wash when accent). OnboardingNudge CTA is mode-aware
(primary on the pale tint, secondary chip on the accent sheet).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X78XTAemKrFw1uJpDpCEaG

* test(web): real-DOM contrast guard for the accent sidebar (OB-377)

Verifies the OPT-IN full-accent sidebar on the real DOM (getComputedStyle
colour composited over its effective background → WCAG contrast), not idealized
token math. Audits every visible sidebar text/icon — top nav, section headers,
favourites, page tree + chevrons/icons, the OnboardingNudge card, workspace
switcher — across default / ocean / graphite accents in light AND dark; asserts
text >= 4.5:1, icons >= 3:1. The aria-hidden account monogram (a decorative
identity badge with its own hue background) is excluded. Measured worst cases:
text 4.58 (default light) .. 7.23, icon 4.58 .. 10.4 — all pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X78XTAemKrFw1uJpDpCEaG

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(ui,sdk): unify data colours into one canonical source, pastel by default (OB-378) (#105)

* wip(ob-378): partial data-colour unify — dead-worker snapshot, NOT reviewed

* feat(ui,sdk): unify all data colours onto the canonical palette (OB-378)

Finish wiring every data-colour consumer onto the single canonical source
(`@book.dev/sdk` dataColors), replacing the five legacy hard-coded lists.
The scheme is fixed to Pastel (the signed-off default); the scheme param is
plumbed for the "Data colours" appearance control (OB-379).

- Runtime: `applyDataColors` emits the palette as CSS vars (chip bg/fg flip
  by `.dark`), wired from ThemeProvider. Chips, swatch dots, timeline bars and
  card tints read the vars via `dataColorVars` helpers (with default-scheme
  fallbacks for provider-less viewer mounts).
- Charts + map: SVG presentation attributes / Leaflet can't resolve `var()`,
  so kit `PALETTE`, db `chartColor` and map pins resolve to concrete canonical
  hex; kit + db charts now share one `SERIES_ORDER` (blue-first, 12 series).
- Status lights: index.css + the toHtml duplicate both take the pastel status
  fills, a 25%-alpha ring and the pastel/muted light-mode hairline (§1.2).
- Export self-containment: tags inline the pastel chip colours; the kit chart
  runtime prepends `KIT_PALETTE` from `SERIES_ORDER` (one source, no drift).
- Legacy lists gone: SWATCH_HEX, db CHART_PALETTE, COLOR_CLASSES, kit PALETTE
  literal, the baked KIT_PALETTE, and the index.css/toHtml status hexes.

Tests: sdk dataColors unit test; export palette-parity assertions (status
CSS, drawn chart fills, prepended runtime palette, db tag chip colours).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X78XTAemKrFw1uJpDpCEaG

* fix(ui,sdk): address OB-378 review nits — calendar tint, viewer stroke, palette guard

- CalendarView event pill: `${accent}24` (hex-alpha concat) is invalid once
  `accent` is a `var(--data-…)` string, silently dropping the bg tint. Swap
  for `color-mix(in srgb, ${accent} 14%, transparent)`, which composes with
  var(). (Quinn, only such concat in the tree.)
- Viewer bundle is provider-less, so `--data-stroke` was undefined and the
  `.obe-kit-light` literal fallback baked a light-mode rgba hairline into dark
  hosts. Call `applyDataColors()` at viewer load so the var is defined
  (:root rgba, .dark transparent) — no reliance on the literal. (Devon.)
- dataColors.test.ts now re-derives all 180 values (36 fills + 144 chips) by
  porting the ob-375-palette-audit.mjs colour math (browser-safe, no node
  builtins) and diffing against the module — a real drift guard, not a
  spot-check. (both reviewers.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X78XTAemKrFw1uJpDpCEaG

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(sdk,ui): swap default Pastel scheme to the owner-selected soft pastel

Replace the Pastel scheme values (only) with the softer, lighter set the owner
picked from rendered candidates. Vivid/Muted are untouched (they render with the
appearance control, OB-379). Everything else from the reviewed work stays: the
var/hex split, export self-containment, the CalendarView color-mix fix, the
viewer applyDataColors, and the derivation-based palette guard.

- New per-token pastel: owner light fill/chipBg/chipFg + a distinct dark
  dot/series fill (`fillDark`); DataColor gains `fillDark` (equals `fill` for the
  mode-invariant vivid/muted + pastel extension tokens). `--data-<token>` now
  flips to `fillDark` in `.dark`, so dots/swatches/status honour the dark fill.
- SERIES_ORDER is the 9 select tokens (blue-first); teal/cyan/indigo drop out of
  the rendered cycle (kept as unrendered extension tokens for type completeness).
- Status trio → green / yellow / red: at these low saturations orange and red
  are near-identical, so warn maps to yellow for a glanceable traffic light.
- Dark chips are derived (deep muted on-hue bg + light on-hue fg, all ≥4.5:1);
  the guard re-derives them + the owner light table + the ob-375 vivid/muted, and
  asserts chipFg contrast (light+dark) and status hue-distinctness.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X78XTAemKrFw1uJpDpCEaG

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant