Repository navigation
feat(web): Lazurio shell takes T3's sidebar colours - #38
Merged
Merged
Conversation
The Lazurio shell (rail, column head and Environment list) now paints itself in the colours of the T3 theme it sits in, following root decision 0187: index.html sets the shell's twelve colour roles from T3's own sidebar tokens, so the shell follows every T3 theme, light and dark, and a theme switch at runtime. - The roles name T3's tokens (--sidebar, the --contrast-* foregrounds and border T3 actually paints with, the row hover/selected/active and control surfaces, --popover and --ring); no colour is copied. - They are declared on :root and [data-app-sidebar], where T3 recomputes its sidebar palette, and <lazurio-rail> carries data-app-sidebar. In the default dark theme T3's sidebar is #000 while the document's --sidebar is a shade lighter, so without the shared scope the rail would stand apart from the sidebar. - The rail wears T3's surface grain; without it the rail sits 1-3 of 255 brightness levels off the sidebar and the edge shows. T3's own border between its sidebar and the main area is unchanged. - Without the shell nothing changes: the roles are inert custom properties and the undefined rail has no box. The release contract test keeps the mapping, the tokens it names in index.css, the sidebar palette scope, and checks that no web source but AppSidebarLayout uses data-app-sidebar (a script lookup would now find the rail first). The runbook describes the colours. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
agentrozjedemeai
approved these changes
Oct 4, 2026
agentrozjedemeai
left a comment
Collaborator
There was a problem hiding this comment.
Exact-head QA on 3a76bc2. Reviewed the sidebar-scope token mapping, grain and upstream rebuild contract; node contract suite passes 14/14 locally and CI is green. Rebase merge preserves the distribution patch stack.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Root decision 0187 (2026-10-04, being written): the Lazurio shell's elements,
<lazurio-rail>,<lazurio-column-head>and the Environment list, take the colours of the app they sit in through colour roles. These are CSS custom properties that the host sets on its document root, and they inherit into the shell's shadow roots. In Chat, that app is T3 Code. The rail and T3's sidebar beside it must read as one surface, with no line between them. For now each app keeps its own theme picker and palettes; a later mechanism will unify themes across apps and is not part of this PR. The Platform shell library is learning the roles in parallel (Lazurio/LazurioPlatform, branchagent/DEV-6639-shell-colour-roles). Until a Launchpad with that library is out, the roles are set but nothing reads them.What changes
apps/web/index.html(already inallowed_upstream_changes):Twelve roles from T3's own tokens. The roles are only references, with no copied colours, so they follow every T3 theme, light and dark, and a theme switch at runtime. Foregrounds and the border use the
--contrast-*variants because those are what T3 actually paints with (text-sidebar-foreground→--contrast-sidebar-foreground), and they respect Settings → Appearance contrast.--lazurio-surface--sidebar--lazurio-ink--contrast-sidebar-foreground--lazurio-ink-muted--contrast-sidebar-muted-foreground--lazurio-line--contrast-sidebar-border--lazurio-line-strongcolor-mix(in oklab, var(--contrast-sidebar-foreground) 24%, var(--sidebar))--lazurio-hover--sidebar-row-hover--lazurio-selected--sidebar-row-selected--lazurio-control--sidebar-control-surface--lazurio-raised--sidebar-row-active--lazurio-overlay--popover--lazurio-overlay-ink--contrast-popover-foreground--lazurio-focus--ringThese are the wireframe's mapping (HumanAndMachine-ai/prototypes-lazurio#13) with the fork's real token names. The
--app-theme-*values feed these tokens when a theme is selected.Declared on
:root, [data-app-sidebar], and the rail carriesdata-app-sidebar. T3 gives its sidebar its own palette under[data-app-sidebar]and recomputes contrast there (index.css: "Recompute contrast wherever a subtree owns its own semantic color palette"). In the default dark theme, T3's sidebar is#000while the document's--sidebariscolor-mix(neutral-950 97%, white), about#111. Roles on:rootalone would put a lighter rail beside a black sidebar. Declaring them in both scopes makes the column head resolve against the sidebar's values. Putting the rail in that scope does the same for the rail.The rail wears T3's surface grain (
--surface-grain, the same utility T3 uses on the sidebar and body). This is an outer-document rule on the host, so it overrides onlybackground-imageof the shell's:hostbackground and keeps its colour. Without the grain the rail sits 1.2–3.3 of 255 brightness levels off the sidebar, exactly at the boundary column (measured below).scripts/lazurio-release-contract.test.mjs: a new test checks:index.css, along with the[data-app-sidebar]palette block, its contrast recomputation anddata-app-sidebar=""inAppSidebarLayout.tsx;.ts/.tsxunderapps/web/srcother thanAppSidebarLayout.tsxmentionsdata-app-sidebar. With the rail in that scope, a future upstreamquerySelector("[data-app-sidebar]")would find the rail first. My own browser probe hit exactly this, so a rebuild that introduces such a lookup should fail and get reviewed.I checked that the test fails when a mapping is changed and when a source file starts querying the attribute. The existing slot test now expects
<lazurio-rail data-app-sidebar>.lazurio-fork-ci.yml: one comment line on the existing allowlist reason. No new allowlist entries.docs/operations/lazurio-fork-release.md: the "Lazurio shell" section describes the colours, why the rail shares the sidebar scope, and the grain.What does not change
#roothas one.<lazurio-rail>has no box: width 0,#rootpadding 0, sidebar at x=0. A pixel diff of the client without/.lazurio/shell.js, between this branch andmain'sindex.html, found 0 differing pixels in default light and default dark (1280×800).apps/.feat(web): Lazurio shell slotand folds into it at the next rebuild on a new upstream tag. Say so if you'd rather list it separately.Verification
CI steps run locally on this head (Node 24.16, pnpm 11.10):
vp fmt --check: clean (4182 files).vp run --filter @t3tools/web typecheckandvp run --filter t3 typecheck: exit 0.apps/web:vp test run src/lazurio: 10/10 pass.node --test scripts/lazurio-release-contract.test.mjs: 14/14 pass.v0.0.45(6c8fed35): no unexpected upstream changes, no merge commits.vp lint scripts/lazurio-release-contract.test.mjs: clean.vp buildof@t3tools/web: the builtindex.htmlkeeps<lazurio-rail data-app-sidebar>and the minified role and grain rules.apps/server: the config/auth/label tests pass (117/117).src/server.test.tsis flaky on this machine regardless of this change: on unmodified1e4cf23dit went 1 pass and 2 fails (1–2 tests) over three runs; with this change, 1 pass and 2 fails (2–5 tests). The failures are a test HTTP client getting a plainokfrom/api/auth/browser-session, most likely port contention with other local servers. This PR touches no server code, andmain's CI is green. CI decides.cli-archivesjob.In a real browser (Chrome via Playwright): this branch's dev server (
vp run dev, worktree-local state), with/.lazurio/shell.jsanswered by a stub. The stub's rail and column head paint themselves only with the roles, with a magenta fallback for any role that fails to resolve. Light and dark come from T3's own preference keys, set before each reload.oklch(0.985 0 none),rgb(0, 0, 0),oklch(0.936464 0.014601 163.554),oklch(0.309925 0.032827 160.944),oklch(0.185778 0.019368 322.159)).documentElementthe roles equal T3's tokens, too. The only difference is the default dark--lazurio-surfaceon the document,color(srgb 0.068 …)(the document's--sidebar), against the sidebar's#000. That gap is the reason for the shared scope.t3-roles-<theme>.png,…-seam.png,t3-roles-live-switch-grove-dark.png,t3-roles-no-shell-default-light.png).Risks and follow-ups
[data-app-sidebar]. The rules that apply to it set only custom properties and, in themed mode,border-color. The current Platform rail has no border, so nothing shows. If the shell ever draws a rail border, T3's sidebar border colour would win over the shell's:hostcolour. The contract test covers script lookups (see above).🤖 Generated with Claude Code
The PR appears safe to merge; no actionable issue was established in the changed code.
Summary
The PR maps twelve Lazurio shell color roles to T3 tokens, places the rail in the sidebar palette scope, and applies T3’s surface grain to the rail.
Diagram
%%{init: {'theme': 'neutral'}}%% flowchart LR A[T3 theme and contrast tokens] --> B["Root and sidebar palette scopes"] B --> C["Lazurio color roles"] C --> D["Rail shadow content"] C --> E["Column head"] A --> F["Surface grain"] F --> DReviews (1) · Last reviewed commit: "feat(web): Lazurio shell takes T3's side..."