Skip to content

Float chat components on the two Glass tiers (standard prose/bubbles/composer, dense code/diffs/plots/tools) #61

Description

@kateebonner

Important

Problem

With the Brain mounted as the Chat's full-bleed background (slice #59) and the AA-floored Glass tokens available (slice #60), the chat's content cards are still styled for a solid chat surface: user bubbles carry an opaque --surface-base fill and assistant prose has no background at all — over the living Brain they would tile the motion away or become illegible. See #56 for the "everything floats, legibly" why.

Approach

Apply slice #60's two Glass tiers to the real content cards: the standard tier on user bubbles, assistant prose, and the composer; the dense tier on code blocks, diffs, run-plots, and tool cards. Bare assistant prose gains its own standard card; muted/secondary text is routed off the ultra-transparent standard tier onto the dense tier (the prose meta on a dense-backed inline zone — slice #60's dense token, no new literal); timeline row frames and gutters stay transparent so the Brain shows in the gaps. Then re-validate that each real archetype's rendered text clears its tier's contrast floor over the running-brain reference frame in both chat themes — extending slice #60's synthetic contrast test onto a component→tier mapping.

Scope

In: the seven content archetypes get a data-glass tier (standard: user bubble, assistant prose, composer; dense: code block, diff, run-plot, tool card); the opaque bubble fill is replaced by the glass tint; assistant prose gets a standard card; muted text rides the dense tier (a dense-backed inline zone for the prose meta); row frames/gutters stay transparent; a component→tier contrast mapping is validated over the reference frame in both themes.
Out: mounting the Brain / retiring the inline strip / one render loop (slice #59 owns the room; this slice only styles what floats on it); deriving tints/blur + the synthetic reference-frame validator (slice #60 owns the tokens + contrast helpers; this slice consumes them); motion / at-rest / data-true heartbeat (slice 4); the laptop-webview perf gate + motion-ease fallback (slice 5); Rail, titlebar, Panels, and the in-timeline sticky session-title header (nav/timeline chrome stays solid — not floating content); new geometry/accent tokens (the tier token already carries radius/border/shadow — no per-component literals).


Acceptance Criteria

  • Tier assignment: the user-message bubble, the assistant prose text-part, and the composer each carry data-glass="standard"; a markdown code block, a diff view, a run-plot/run-window, and a tool card each carry data-glass="dense". No third tier value and no data-glass on any non-content element are present.
  • Nothing bare, nothing opaque: every persistent readable region above (bubble, prose, code, diff, plot, tool, composer) resolves a Glass tint whose alpha is < 1 (translucent) — none is transparent (would be illegible over the Brain) and none is opaque (would tile the Brain away).
  • Gaps stay live: the timeline row frame (session-turn) and the centered column/gutter wrappers carry no glass background and resolve a transparent background — only leaf content cards float, so the Brain is visible between top-level cards.
  • Contrast over the reference frame (both chat themes): measured with slice Derive two AA-floored, chat-theme-keyed Glass tint tiers with a contrast test #60's validator against its worst-case running-brain reference frame — body text on a standard card clears ≥4.5:1 with the same safety margin; code/diff text on a dense card clears ≥4.5:1; graphical marks (syntax colors, diff fills, plot lines) on a dense card clear ≥3:1. The mapping is asserted per archetype, not eyeballed.
  • No muted text on standard: every muted/secondary-role text element (e.g. the copy/timestamp meta inside a prose card) sits in a dense-tier element or a dense-backed inline zone (slice Derive two AA-floored, chat-theme-keyed Glass tint tiers with a contrast test #60's dense token — never a new tint/literal); the mapping table rejects any (muted-role → standard) pairing.
  • No ad-hoc blur/tint literal: a source scan of the seven restyled cards' style-rules finds zero backdrop-filter/blur-radius/rgba-tint literals outside slice Derive two AA-floored, chat-theme-keyed Glass tint tiers with a contrast test #60's Glass token CSS — the scan is scoped to those card rules and explicitly excludes the in-timeline sticky session-title header's pre-existing chrome blur (out-of-scope chrome that stays), so styling the diff seam that lives alongside it does not require touching it; and the previous opaque --surface-base bubble fill is gone (replaced by the standard tint).
  • Chrome untouched: no data-glass appears on the Rail, titlebar, Panels, or the in-timeline sticky session-title header.

Testing Decisions

  • Extends slice Derive two AA-floored, chat-theme-keyed Glass tint tiers with a contrast test #60 test surface — component→tier contrast mapping (CREATE, alongside slice 2's glass-token contrast test): a table test over {archetype → (tier, text-role)} for the seven archetypes, asserting tierContrast(theme, tier, colorForRole(role)) ≥ floor(+margin) in both shipped chat themes using slice 2's exported contrast/tierContrast and the theme's real text tokens: body→standard ≥4.5:1+margin, code/diff→dense ≥4.5:1, marks→dense ≥3:1. The table also asserts no muted-role text maps to the standard tier — it rides dense / a dense-backed inline zone (guards the exact grey-on-glass failure the earlier build hit).
  • Tier-assignment DOM test (CREATE glass-float behavior test): render each archetype through the real message renderer / composer / tool / run-plot components and assert the correct data-glass value on the leaf card; assert the wrapping session-turn/column frame has no data-glass and a transparent resolved background (the "gaps stay live" + "nothing bare/opaque" criteria).
  • No-literal source guard (CREATE, static scan): scan the seven restyled cards' style-rules (not whole files) for backdrop-filter, blur radii, and rgba tint literals; assert zero outside the Glass token CSS; explicitly exclude the in-timeline sticky session-title header's pre-existing chrome blur (chrome that stays); and assert the user bubble no longer sets an opaque --surface-base fill.
  • Reuse existing component tests: extend the tool-card, run-card, and run-plot render tests to cover the new data-glass="dense" attribute rather than adding parallel harnesses; do not re-test the token derivation (that is slice 2's).
  • All tests are external-behavior (rendered attribute + computed style + slice-2 validator math) — none reach into engine internals or attempt live canvas/backdrop-filter pixel compositing (jsdom cannot composite backdrop-filter; the reference-frame floor stands in for "legible over the running brain").

Key Decisions

  • Tier map (the contract): standard = {user bubble, assistant prose, composer}; dense = {code block, diff, run-plot, tool card}. Composer is standard (a control surface holding body text), per the ADR grouping — the old branch's separate chrome tier collapses into standard.
  • data-glass attribute contract: values are standard | dense, defined and emitted by slice Derive two AA-floored, chat-theme-keyed Glass tint tiers with a contrast test #60; this slice only applies them and consumes the emitted CSS utility + validator. Applying an undefined tier value is a slice-2 change, never a local literal.
  • Assistant prose gains a card: assistant text-parts are bare (no background) today; they get a standard card so prose is legible over the Brain.
  • Nesting is conservative: a dense card nested in a standard card (code inside prose) keeps its dense tier; every tier's floor is derived over the bare worst-case Brain, and nesting only adds opacity, so contrast is guaranteed without per-nesting recomputation.
  • Muted/secondary text: rides the dense tier — the prose card's meta (copy/timestamp) is the concrete case, wrapped in a dense-backed inline zone (slice Derive two AA-floored, chat-theme-keyed Glass tint tiers with a contrast test #60's dense token, no new literal) so it never composites directly against the standard tint. This is the ADR's "locally-dimmed zone", realized without a third token.
  • Motion lives in the gaps, not thinner cards: cards stay at slice 2's derived tint; the visible Brain comes from the transparent row gaps/gutters — do not dilute a card's tint to "let more Brain through."

Constraints & Invariants

  • Do not mount the background, retire the inline brain strip, or touch the render loop — that is slice Mount the Brain chat-wide and absorb the inline brain strip #59; this slice only styles the floating cards.
  • Do not define new glass tints, blur values, or geometry — consume slice Derive two AA-floored, chat-theme-keyed Glass tint tiers with a contrast test #60's two tokens verbatim (they already carry radius/border/shadow); a value the tokens lack is a slice-2 change, not a per-component literal. The dense-backed inline zone for muted text uses the existing dense token — not a new one.
  • The standard tier's tint alpha is bounded below by its contrast floor; never override it lower locally.
  • Row frames (session-turn), the centered column, and inter-card gutters must never receive a glass fill — that would tile and hide the Brain.
  • Rail, titlebar, Panels, and the in-timeline sticky session-title header stay as-is; no data-glass on them, and their existing chrome blur is left untouched.
  • Amicode design-system law holds for the card chrome: geometry from the tier token, single-yellow accent — yellow is never a card fill; #fff676 stays the Brain's live-thought color.
Prior Art / Patterns Real seams to restyle (current branch): the message renderer `packages/ui/src/components/message-part.tsx` + `message-part.css` — user bubble `[data-slot="user-message-text"]` (currently `background: var(--surface-base)`), assistant `[data-component="text-part"]` / `[data-slot="text-part-body"]`, markdown code `[data-component="markdown-code"]` (`packages/ui/src/components/markdown.tsx` + `markdown.css`); the timeline `packages/app/src/pages/session/message-timeline.tsx` — `TimelineRowFrame` → `[data-component="session-turn"]`, diff group `session-turn-diffs-group` / `session-turn-diff-view` (note: this file also holds the sticky session-title header with a pre-existing `backdrop-blur` — that is out-of-scope chrome; do not touch it); the composer `packages/app/src/pages/session/composer/session-composer-region.tsx` — `[data-component="session-prompt-dock"]`; the tool card `packages/ui/src/components/basic-tool.tsx` — `[data-component="tool-trigger"]`; the run-plot `packages/ui/src/amicode/run-window.tsx` — `[data-component="amicode-run-window"]`; diffs `packages/ui/src/components/diff-changes.tsx`. Old-branch raw material to reuse (design-neutral, slice #60 lands it, this slice consumes it): `opencode-brain-atmosphere-wt/packages/ui/src/amicode/glass.css` — the `[data-glass=...]` tier utilities, the `@supports not (backdrop-filter)` opaque fallback so text never lands on bare canvas, and `glass-tokens.ts`'s `contrast`/`tierContrast` validator functions; note its three tiers (airy/dense/chrome) collapse to slice 2's two (standard/dense) with the composer moving from `chrome` to `standard`. The mounted background is slice #59's `BrainAtmosphere` (old `brain/brain-atmosphere.tsx`), which paints on a transparent, `aria-hidden`, pointer-events-none ground beneath the cards.

Source

Metadata

Metadata

Assignees

No one assigned

    Labels

    afkImplement + merge unattended

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions