Skip to content

Kestrel Neon theme family, a shared theme-effects layer, and the Core Assembly CoS avatar - #6250

Merged
atomantic merged 5 commits into
mainfrom
cos/task-mtnbhavy/agent-d0198b37
Sep 4, 2026
Merged

Kestrel Neon theme family, a shared theme-effects layer, and the Core Assembly CoS avatar#6250
atomantic merged 5 commits into
mainfrom
cos/task-mtnbhavy/agent-d0198b37

Conversation

@atomantic

Copy link
Copy Markdown
Owner

Summary

  • New theme family kestrel: Kestrel Neon (night) and Kestrel Dawn (day), from a CRT boot-terminal concept — cyan/magenta on a scanlined void, a perspective grid floor, IBM Plex Mono throughout, uppercase tracked titles that glitch at night. Palettes graded by the existing contrast contract; docs in docs/themes/kestrel-neon.md.
  • Effects become theme features, not per-theme CSS. A manifest lists effects (scanlines, vignette, sweep, grid-floor, glitch — registry THEME_EFFECTS), useTheme publishes them as data-port-theme-effects, and one shared block per effect paints on a dedicated .port-fx-layer element rendered by main.jsx, tuned by --port-fx-* tokens. Sweep and floor animate transform only (compositor, no full-screen repaint). Black ICE's scanlines moved onto the layer with identical values.
  • Heading treatment is now tokens (--port-heading-tracking, --port-heading-glow, --port-title-tracking, --port-title-transform) read by the shared h1–h3 rule.
  • Fixes a latent applyTheme defect: inline --port-* properties from the previous theme leaked into the next when a theme declared an optional token. It now clears undeclared ones from the DOM.
  • New CoS avatar core (Core Assembly): the concept's rotating wireframe icosahedron on a plain 2D canvas (no WebGL). Edges follow the agent-state color, rings follow --port-accent-2, drag rotates, reduced motion holds one frame. Pure geometry/renderer in client/src/lib/wireframeCore.js.
  • Shared hooks generalized on the way: useCanvasRollPalette takes a resolver, useCanvasDprSize can follow the container height, new usePrefersReducedMotion (BrainGraph adopts it).
  • Deferred cross-theme rule tokenization and avatar-registry unification: Tokenize the per-theme card/focus/nav/code rules in index.css (finish what heading tokens started) #6248.

Test plan

  • npm run theme:check (now also rejects a --port-fx-* token for an unlisted effect)
  • Client: portosThemes, chipContrast, useTheme, wireframeCore, CoreCoSAvatar, ConfigTab, ChiefOfStaff, piano-roll, BrainGraph, barrel suites; full client and server suites (only known load flakes, all green in isolation)
  • Server: cosStatusRoutesAvatar schema test accepts core
  • cd client && npm run build
  • Screenshotted a static preview against the built stylesheet for both Kestrel variants, Black ICE (unchanged), and Classic Midnight (no effects) to confirm the effects attribute, overlay, grid floor, and avatar frame

…no WebGL dependency

The Kestrel Neon concept's telemetry widget becomes an avatar style (`core`).
Geometry and the frame renderer are pure (client/src/lib/wireframeCore.js) so
the projection math is pinned without a canvas; the component drives them with
requestAnimationFrame. Edges take the agent-state color so all seven states stay
distinguishable, rings and vertices follow the theme's --port-accent-2 and
re-resolve on theme switch, speaking bursts spin and glow, drag rotates, and
prefers-reduced-motion holds one static frame. It skips CoSCanvasGuard because
it needs no WebGL, so it also works where the three.js avatars fall back to the
failure panel.
Kestrel Neon (night) and Kestrel Dawn (day) bring the CRT boot-terminal
concept: cyan and magenta on a scanlined void, a perspective grid floor,
IBM Plex Mono throughout, uppercase glitching titles at night.

Full-screen effects are now theme features instead of per-theme CSS: a theme
lists them in its manifest (scanlines, vignette, sweep, grid-floor, glitch —
registry THEME_EFFECTS), useTheme publishes the list as
data-port-theme-effects on <html>, and index.css keys one block per effect on
that attribute, parameterized by --port-fx-* tokens. Black ICE Terminal's
scanlines (both modes) move onto the shared layer with identical values.
theme:check fails on an unknown effect or a registered effect with no block.

Fixes a latent applyTheme defect: it only ever set custom properties, so a
token one theme declares and the next does not stayed inline on <html> after
a switch. It now removes the stale ones (pinned in useTheme.test.jsx).
…ecomes tokens

Review follow-ups on the Kestrel/effects work:

- The effects layer no longer borrows body::after and #root::before. main.jsx
  renders <div class="port-fx-layer"> and the grid floor (::before), the static
  scanline+vignette overlay (::after), and the sweep band (.port-fx-sweep child)
  all live there, so a theme's own body pseudo-element rules (Lumen Glass Day's
  drift) can never collide with the shared layer.
- The sweep and the grid floor animate transform only, so they stay on the
  compositor instead of repainting a blended full-screen layer every frame.
- Heading treatment is four tokens (--port-heading-tracking, --port-heading-glow,
  --port-title-tracking, --port-title-transform) read by the shared h1-h3 rule;
  the glitch effect rests on --port-heading-glow so listing it never dictates a
  theme's title look. Kestrel's per-id CSS collapses to a family block.
- theme:check rejects a --port-fx-* token declared for an effect the theme does
  not list; themes declare only the tokens where they diverge from :root.
- applyTheme clears stale --port-* properties by walking the inline style rather
  than remembering what it wrote.
- The Core Assembly avatar sizes through useCanvasDprSize (now following the
  container height), resolves theme tokens through useCanvasRollPalette (now
  taking the resolver), parses triples with chipContrast's parseTriple, and
  reads reduced motion from a new usePrefersReducedMotion hook that BrainGraph
  adopts too. Edge colors are parsed once at import, not per frame.
…ICE headings keep their glow

useCanvasDprSize resizes on mount before the avatar's own effect installs
drawRef, so a real (non-zero) layout hit `drawRef.current is not a function`.
The hook now tolerates a not-yet-installed draw (the rAF loop paints a tick
later), and a test with a sized layout pins it. The shared heading rule sets
text-shadow explicitly, which dropped the glow Black ICE headings inherited
from body — both Black ICE manifests restate it in --port-heading-glow.
… reduced motion

With prefers-reduced-motion the rAF loop stops after one frame, so a new
agent state never reached the canvas. An effect now requests the single
frame explicitly; the reduced-motion test pins it.
@atomantic
atomantic merged commit 858531a into main Sep 4, 2026
7 checks passed
@atomantic
atomantic deleted the cos/task-mtnbhavy/agent-d0198b37 branch September 4, 2026 19:54
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