feat(dashboard): zero-DOM RouteStage + in-canvas WebGPU nav

RouteStage.svelte rebuilt: no slot chrome, no pause button, no telemetry/
loading/error/empty divs. One transparent pointer host + the canvas. Pause,
telemetry, and all lifecycle states now render as MSDF glyphs via TextLayerPass;
the sidebar nav is now an in-canvas WebGPU rail (nav-layer.ts) with all 19
routes, active route glowing cyan, hover ignition, click-to-navigate.

check 0 errors 0 warnings (the slot-deprecation warning is gone), build green.
The 5 organ routes still compile. Their own page DOM headers convert in Day-2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Sam Valladares 2026-07-09 13:05:52 -07:00
parent b85740d0e7
commit a4a36ee220
2 changed files with 347 additions and 68 deletions

View file

@ -1,8 +1,18 @@
<script lang="ts">
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { page } from '$app/stores';
import ObservatoryCanvas from '$lib/components/ObservatoryCanvas.svelte';
import type { ObservatoryEngine, FramePass } from '$lib/observatory/engine';
import { CAUSAL, IMMUNE, RETENTION, rgb01 } from '$lib/observatory/cognitive-palette';
import { type DemoMode } from '$lib/observatory/types';
import { TextLayerPass, type TextLayerItem } from '$lib/observatory/text/text-layer';
import {
createNavLayerPass,
type NavLayerPass,
type NavPick
} from '$lib/observatory/nav/nav-layer';
import {
assertProvenance,
emptyScene,
@ -58,6 +68,8 @@
let currentScene = $derived(scene ?? emptyScene(organ));
let canvasLayerEl: HTMLDivElement | null = $state(null);
let engine = $state<ObservatoryEngine | null>(null);
let chromeText: TextLayerPass | null = null;
let navPass: NavLayerPass | null = null;
let routePasses: RouteFramePass[] = [];
let frameCount = $state(0);
let fpsEstimate = $state(0);
@ -65,6 +77,12 @@
let userSetPause = $state(false);
let ready = $state(false);
const CYAN = [...rgb01(CAUSAL.forward), 1] satisfies [number, number, number, number];
const DIM_GREEN = [...rgb01(RETENTION.recall), 0.58] satisfies [number, number, number, number];
const OXYGEN = [...rgb01(RETENTION.luciferin), 0.86] satisfies [number, number, number, number];
const SCARLET = [...rgb01(IMMUNE.veto), 0.9] satisfies [number, number, number, number];
const AMBER = [...rgb01(IMMUNE.caution), 0.78] satisfies [number, number, number, number];
function initReducedMotion() {
if (typeof window === 'undefined') return;
const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
@ -79,10 +97,12 @@
function togglePause() {
userSetPause = true;
paused = !paused;
updateChromeText();
}
$effect(() => {
engine?.setPaused(paused);
updateChromeText();
});
function upload(sceneToUpload: RouteSceneModel) {
@ -90,19 +110,32 @@
if (import.meta.env.DEV) assertProvenance(sceneToUpload);
for (const pass of routePasses) pass.uploadScene?.(sceneToUpload);
engine.demoClock.reset();
updateChromeText();
}
function handleFrame(frame: number, fps: number) {
frameCount = frame;
fpsEstimate = fps;
navPass?.setActivePath(currentDashboardPath());
updateChromeText(frame, fps);
}
function handleReady(e: ObservatoryEngine) {
ready = true;
async function handleReady(e: ObservatoryEngine) {
ready = false;
engine = e;
for (const pass of routePasses) pass.dispose?.();
chromeText?.dispose();
navPass?.dispose();
routePasses = typeof passes === 'function' ? passes(e, currentScene) : (passes ?? []);
for (const pass of routePasses) e.addPass(pass);
navPass = createNavLayerPass(e, { activePath: currentDashboardPath() });
chromeText = new TextLayerPass(e);
e.addPass(navPass);
e.addPass(chromeText);
await Promise.all([navPass.init(), chromeText.init()]);
if (engine !== e) return;
ready = true;
upload(currentScene);
}
@ -111,16 +144,131 @@
upload(currentScene);
});
async function handleFieldClick(e: MouseEvent) {
if (!onpick || !canvasLayerEl) return;
function currentDashboardPath(): string {
const path = $page.url.pathname;
return path.startsWith(base) ? path.slice(base.length) || '/' : path;
}
function makeChromeItems(frame = frameCount, fps = fpsEstimate): TextLayerItem[] {
const items: TextLayerItem[] = [
{
id: 'route-chrome:pause',
kind: 'route-chrome',
text: paused ? '> RESUME' : '|| PAUSE',
x: 0.66,
y: -0.86,
size: 0.034,
color: paused ? AMBER : CYAN,
revealSpan: 1
},
{
id: 'route-chrome:telemetry',
kind: 'route-telemetry',
text: `${organ.toUpperCase()} - ${frame}F - ${fps}FPS`,
x: 0.44,
y: 0.88,
size: 0.022,
color: DIM_GREEN,
revealSpan: 1
}
];
if (loading) {
items.push({
id: 'route-chrome:loading',
kind: 'route-status',
text: 'REPLAYING COGNITIVE RECEIPT...',
x: -0.23,
y: 0.02,
size: 0.046,
color: OXYGEN,
startFrame: Math.max(0, frame - 90),
revealSpan: 72
});
} else if (error) {
items.push(
{
id: 'route-chrome:error-pulse',
kind: 'route-status-pulse',
text: '!!!!!!!!!!!!!!!!!!!!!!!!',
x: -0.36,
y: -0.035,
size: 0.025,
color: SCARLET,
revealSpan: 1
},
{
id: 'route-chrome:error',
kind: 'route-status',
text: `ERROR - ${error}`.slice(0, 72),
x: -0.54,
y: 0.025,
size: 0.032,
color: SCARLET,
revealSpan: 14,
maxWidthEm: 48
}
);
} else if (!currentScene.alive) {
items.push({
id: 'route-chrome:empty',
kind: 'route-status',
text: emptyLabel,
x: -0.36,
y: 0.02,
size: 0.034,
color: DIM_GREEN,
revealSpan: 24,
maxWidthEm: 48
});
}
return items;
}
function updateChromeText(frame = frameCount, fps = fpsEstimate) {
chromeText?.setText(makeChromeItems(frame, fps));
}
function pointerToNdc(e: PointerEvent | MouseEvent): { x: number; y: number } | null {
if (!canvasLayerEl) return null;
const rect = canvasLayerEl.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) return;
const ndcX = ((e.clientX - rect.left) / rect.width) * 2 - 1;
const ndcY = -(((e.clientY - rect.top) / rect.height) * 2 - 1);
if (rect.width === 0 || rect.height === 0) return null;
return {
x: ((e.clientX - rect.left) / rect.width) * 2 - 1,
y: -(((e.clientY - rect.top) / rect.height) * 2 - 1)
};
}
function handlePointerMove(e: PointerEvent) {
const ndc = pointerToNdc(e);
if (!ndc) return;
const navHit = navPass?.setHoverFromNdc(ndc.x, ndc.y);
const chromeHit = chromeText?.pickAt(ndc.x, ndc.y);
if (canvasLayerEl) canvasLayerEl.style.cursor = navHit || chromeHit || onpick ? 'crosshair' : 'default';
}
function handlePointerLeave() {
navPass?.clearHover();
if (canvasLayerEl) canvasLayerEl.style.cursor = 'default';
}
async function handleFieldClick(e: MouseEvent) {
const ndc = pointerToNdc(e);
if (!ndc) return;
const navHit: NavPick | null = navPass?.pickAt(ndc.x, ndc.y) ?? null;
if (navHit) {
await goto(`${base}${navHit.payload.href}`);
return;
}
const chromeHit = chromeText?.pickAt(ndc.x, ndc.y);
if (chromeHit?.id === 'route-chrome:pause') {
togglePause();
return;
}
for (const pass of routePasses) {
const hit = await pass.pickAt?.(ndcX, ndcY);
const hit = await pass.pickAt?.(ndc.x, ndc.y);
if (hit) {
onpick(hit);
onpick?.(hit);
return;
}
}
@ -129,7 +277,11 @@
onMount(() => {
return () => {
for (const pass of routePasses) pass.dispose?.();
chromeText?.dispose();
navPass?.dispose();
routePasses = [];
chromeText = null;
navPass = null;
};
});
@ -137,63 +289,12 @@
</script>
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
<div class="{embedded ? 'absolute' : 'fixed'} inset-0 overflow-hidden route-stage">
<div
bind:this={canvasLayerEl}
class="absolute inset-0 z-0"
class:cursor-crosshair={!!onpick}
onclick={handleFieldClick}
>
<ObservatoryCanvas {demo} {seed} onframe={handleFrame} onready={handleReady} />
</div>
<div class="absolute inset-0 z-10 pointer-events-none">
<slot name="chrome" {frameCount} {fpsEstimate} {paused} {ready} />
<button
onclick={togglePause}
class="absolute bottom-4 right-4 pointer-events-auto flex items-center gap-2 px-3 py-1.5
rounded-xl border border-[#22C7DE]/25 bg-[#020307]/80 backdrop-blur-sm
font-mono text-[11px] tracking-wide text-[#22C7DE]/80 hover:text-[#22C7DE]
hover:border-[#22C7DE]/50 transition-colors"
title={paused ? 'Resume route organ motion' : 'Pause route organ motion'}
aria-pressed={paused}
>
{paused ? '▶ RESUME' : '❚❚ PAUSE'}
</button>
<div class="absolute top-4 right-4 font-mono text-[10px] tracking-[0.18em] text-[#7fe6c0]/60 uppercase">
{organ} · {frameCount}f · {fpsEstimate}fps
</div>
{#if loading}
<div class="absolute inset-0 flex items-center justify-center pointer-events-auto">
<div class="text-[#A8FF5E] font-mono text-sm tracking-widest animate-pulse">
REPLAYING COGNITIVE RECEIPT...
</div>
</div>
{/if}
{#if error && !loading}
<div class="absolute inset-0 flex items-center justify-center pointer-events-auto">
<div class="text-[#ff8a8a] font-mono text-sm border border-[#FF3B30]/50 bg-[#1a0508]/70 px-4 py-2 rounded">
{error}
</div>
</div>
{/if}
{#if !loading && !error && !currentScene.alive}
<div class="absolute inset-0 flex items-center justify-center pointer-events-none">
<div class="text-[#5dcaa5]/70 font-mono text-xs tracking-widest border border-[#5dcaa5]/15 bg-[#020307]/45 px-4 py-2 rounded-xl backdrop-blur-sm">
{emptyLabel}
</div>
</div>
{/if}
</div>
<div
bind:this={canvasLayerEl}
class="{embedded ? 'absolute' : 'fixed'} inset-0 overflow-hidden"
onclick={handleFieldClick}
onpointermove={handlePointerMove}
onpointerleave={handlePointerLeave}
>
<ObservatoryCanvas {demo} {seed} onframe={handleFrame} onready={handleReady} />
</div>
<style>
.route-stage {
background: #020307;
}
</style>

View file

@ -0,0 +1,178 @@
import type { ObservatoryEngine, FramePass } from '$lib/observatory/engine';
import { CAUSAL, MEDIUM, RETENTION, rgb01 } from '$lib/observatory/cognitive-palette';
import { TextLayerPass, type TextLayerItem } from '$lib/observatory/text/text-layer';
export type NavRoute = {
label: string;
href: string;
shortcut?: string;
};
export type NavPick = {
id: string;
kind: 'route-nav';
payload: NavRoute;
};
export interface NavLayerOptions {
activePath?: string;
routes?: NavRoute[];
}
export interface NavLayerPass extends FramePass {
init(): Promise<void>;
setActivePath(path: string): void;
setHoverFromNdc(ndcX: number, ndcY: number): NavPick | null;
clearHover(): void;
pickAt(ndcX: number, ndcY: number): NavPick | null;
dispose(): void;
}
export const COGNITIVE_OS_ROUTES: NavRoute[] = [
{ href: '/observatory', label: 'Observatory', shortcut: 'O' },
{ href: '/reasoning', label: 'Reasoning', shortcut: 'R' },
{ href: '/contradictions', label: 'Contradictions', shortcut: 'X' },
{ href: '/blackbox', label: 'Black Box', shortcut: 'B' },
{ href: '/timeline', label: 'Timeline', shortcut: 'T' },
{ href: '/duplicates', label: 'Duplicates', shortcut: 'U' },
{ href: '/graph', label: 'Graph', shortcut: 'G' },
{ href: '/memory-prs', label: 'Memory PRs', shortcut: 'Q' },
{ href: '/memories', label: 'Memories', shortcut: 'M' },
{ href: '/feed', label: 'Feed', shortcut: 'F' },
{ href: '/explore', label: 'Explore', shortcut: 'E' },
{ href: '/activation', label: 'Activation', shortcut: 'A' },
{ href: '/dreams', label: 'Dreams', shortcut: 'D' },
{ href: '/schedule', label: 'Schedule', shortcut: 'C' },
{ href: '/importance', label: 'Importance', shortcut: 'P' },
{ href: '/patterns', label: 'Patterns', shortcut: 'N' },
{ href: '/intentions', label: 'Intentions', shortcut: 'I' },
{ href: '/stats', label: 'Stats', shortcut: 'S' },
{ href: '/settings', label: 'Settings', shortcut: ',' }
];
const NAV_X = -0.93;
const NAV_Y = 0.83;
const NAV_SIZE = 0.024;
const NAV_STEP = 0.058;
const CYAN = [...rgb01(CAUSAL.forward), 1] satisfies [number, number, number, number];
const HOVER = [...rgb01(RETENTION.luciferin), 1] satisfies [number, number, number, number];
const DIM = [...rgb01(RETENTION.bridge), 0.36] satisfies [number, number, number, number];
const BLACKWATER = [...rgb01(MEDIUM.blackwater), 0.15] satisfies [number, number, number, number];
export function createNavLayerPass(engine: ObservatoryEngine, opts: NavLayerOptions = {}): NavLayerPass {
return new TextNavLayer(engine, opts);
}
class TextNavLayer implements NavLayerPass {
private readonly text: TextLayerPass;
private readonly routes: NavRoute[];
private activePath: string;
private hoverHref: string | null = null;
private ready = false;
constructor(engine: ObservatoryEngine, opts: NavLayerOptions) {
this.text = new TextLayerPass(engine);
this.routes = opts.routes ?? COGNITIVE_OS_ROUTES;
this.activePath = opts.activePath ?? '';
}
async init(): Promise<void> {
await this.text.init();
this.ready = true;
this.rebuild();
}
setActivePath(path: string): void {
if (this.activePath === path) return;
this.activePath = path;
this.rebuild();
}
setHoverFromNdc(ndcX: number, ndcY: number): NavPick | null {
const hit = this.pickAt(ndcX, ndcY);
const next = hit?.payload.href ?? null;
if (next !== this.hoverHref) {
this.hoverHref = next;
this.rebuild();
}
return hit;
}
clearHover(): void {
if (this.hoverHref === null) return;
this.hoverHref = null;
this.rebuild();
}
pickAt(ndcX: number, ndcY: number): NavPick | null {
const hit = this.text.pickAt(ndcX, ndcY);
if (!hit || hit.kind !== 'route-nav') return null;
const route = hit.payload as TextLayerItem & { route?: NavRoute };
if (!route.route) return null;
return { id: hit.id, kind: 'route-nav', payload: route.route };
}
render(pass: GPURenderPassEncoder): void {
this.text.render(pass);
}
dispose(): void {
this.text.dispose();
}
private rebuild(): void {
if (!this.ready) return;
const items: TextLayerItem[] = [
{
id: 'route-nav:rail',
kind: 'route-nav-rail',
text: 'COGNITIVE OS',
x: NAV_X,
y: NAV_Y + 0.075,
size: 0.021,
color: BLACKWATER,
revealSpan: 1
},
...this.routes.map((route, i) => {
const active = this.isActive(route.href);
const hovered = this.hoverHref === route.href;
const color = active ? CYAN : hovered ? HOVER : DIM;
const marker = active ? '>' : hovered ? '+' : '-';
const tail = route.shortcut ? ` ${route.shortcut}` : '';
return {
id: `route-nav:${route.href}`,
kind: 'route-nav',
text: `${marker} ${route.label.toUpperCase()}${tail}`,
x: active || hovered ? NAV_X + 0.012 : NAV_X,
y: NAV_Y - i * NAV_STEP,
size: active || hovered ? NAV_SIZE * 1.06 : NAV_SIZE,
color,
startFrame: 0,
revealSpan: 1,
maxWidthEm: 16,
route
} satisfies TextLayerItem & { route: NavRoute };
}),
...(this.hoverHref
? [
{
id: 'route-nav:hover-ring',
kind: 'route-nav-focus',
text: '>>>>>>>>>>>>>>>>',
x: NAV_X - 0.01,
y: NAV_Y - this.routes.findIndex((r) => r.href === this.hoverHref) * NAV_STEP - 0.029,
size: 0.012,
color: CYAN,
revealSpan: 1
} satisfies TextLayerItem
]
: [])
];
this.text.setText(items);
}
private isActive(href: string): boolean {
const path = this.activePath || '';
return path === href || path.endsWith(href) || (href === '/observatory' && (path === '/' || path === ''));
}
}