vestige/apps/dashboard/e2e/reasoning-flow.spec.ts
Sam Valladares 844b91a804 feat(dashboard): every organ is a living WebGPU field (LivingFieldPass)
Turn all 17 sparse organs from text-on-black voids into full-bleed, moving,
bioluminescent WebGPU fields. Each cell maps to a real backend fact; verified
live on the real brain with a decoded fill% + motion + zero-error probe.

Foundation: one reusable LivingFieldPass (src/lib/observatory/field/) does
splat -> separable-blur (render passes) -> membrane base coat -> HDR cells with
orbital drift and a CPU pick-mirror. Each organ writes a ~20-line data->cells
mapper (cell-layout.ts: layoutGalaxy / layoutRings / FIELD_HUE).

Organs brought alive (fill% before -> after, all zero WebGPU errors):
stats 0.8->80.9, observatory 10.8->81.4, graph 12.3->82.4, memories ->78.4,
blackbox 18.2->83.3, contradictions 1.6->69.6, dreams 0.3->64.5,
settings 2.7->76.4, reasoning 2.7->39.2, patterns 7.1->45.6,
activation 10->64.9, feed ->89, explore ->83.3, intentions ->66.6,
importance ->73.7, memory-prs ->74.4, schedule ->37.1.

Backend surfaces: add api.memoryChangelog() + ChangelogResponse; surface
suppress/unsuppress on the memories field (shift/alt-click -> scar).

Tests: palace-launch-gate now asserts fillPct>30 + isAnimating (not just
non-black), with a longer settle for reasoning's deep_reference and a raised
timeout for the curated tour. all-routes-smoke treats settings as the WebGPU
field it now is. Gates green: check 0/0, build ok, palace-launch-gate 22/22 +
tour, all-routes-smoke 20/20.

Protected work untouched: MemoryCinema, graph/cinema, observatory scene
shaders, node-renderer, palace-map.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 06:20:35 +07:00

154 lines
6.8 KiB
TypeScript

import { test, expect } from '@playwright/test';
import { BASE, captureErrors, expectNoErrors, sampleCanvas } from './helpers/dashboard';
// ─────────────────────────────────────────────────────────────────────────
// Reasoning Theater — the deep_reference flow.
//
// Proves the real 8-stage cognitive pipeline round-trip:
// 1. /dashboard/reasoning mounts its WebGPU organ canvas.
// 2. A real query is typed into the ask box and submitted (Enter).
// 3. The /api/deep_reference call returns and the reasoning DOM materializes
// (Reasoning section / "analyzed" / confidence % / Primary Source /
// Evidence / Cognitive Pipeline).
// 4. The WebGPU organ field lights up (8-stage organ renders non-black).
// 5. No page/WebGPU-validation errors fired.
//
// The brain is up with 325+ real memories, so we assert on real rendered
// output. If a query genuinely returns an empty result set, the flow still
// ran end-to-end (loading → response OR a graceful error surface), and we
// assert THAT rather than hard-failing on content that legitimately may not
// exist.
// ─────────────────────────────────────────────────────────────────────────
const REASONING = `${BASE}/reasoning`;
const QUERY = 'How does FSRS-6 trust scoring work?';
test.describe('Reasoning Theater — deep_reference flow', () => {
test('typing a query runs the pipeline and renders reasoning + lights the organ', async ({
page
}) => {
const capture = captureErrors(page);
// ── 1. Route mounts its WebGPU canvas ──────────────────────────────
await page.goto(REASONING);
const canvas = page.locator('canvas').first();
await canvas.waitFor({ state: 'attached', timeout: 15_000 });
await expect(canvas).toBeVisible();
// ── 2. Type a real query + submit ──────────────────────────────────
const input = page.getByPlaceholder('Ask your memory anything…');
await expect(input).toBeVisible();
await input.click();
await input.fill(QUERY);
await expect(input).toHaveValue(QUERY);
// The "Reason" button should enable once the box has a non-empty query.
const reasonBtn = page.getByRole('button', { name: 'Reason' });
await expect(reasonBtn).toBeEnabled();
// Submit via Enter (the input has an onkeydown Enter handler).
await input.press('Enter');
// ── 3. Wait for the pipeline to run + a terminal state to render ────
// The button reads "Reasoning…" while loading; the empty-state /
// example chips disappear. Wait for EITHER a real response section OR
// a graceful error surface — but not the still-empty initial state.
const reasoningHeading = page.getByRole('heading', { name: 'Reasoning', exact: true });
const primarySource = page.getByText('Primary Source', { exact: true });
const cognitivePipeline = page.getByRole('heading', { name: 'Cognitive Pipeline' });
const errorSurface = page.locator('text=/^Error:/');
// Give the local 8-stage pipeline time to complete + paint.
await Promise.race([
reasoningHeading.waitFor({ state: 'visible', timeout: 30_000 }),
cognitivePipeline.waitFor({ state: 'visible', timeout: 30_000 }),
primarySource.waitFor({ state: 'visible', timeout: 30_000 }),
errorSurface.waitFor({ state: 'visible', timeout: 30_000 })
]);
// The button must have left its disabled/loading limbo.
await expect(page.getByRole('button', { name: /Reason(ing…)?/ })).toBeEnabled({
timeout: 15_000
});
const errored = await errorSurface.isVisible().catch(() => false);
if (errored) {
// A real backend error is a genuine finding, not something to hide.
const msg = (await errorSurface.textContent())?.trim();
throw new Error(`Reasoning Theater surfaced a backend error: ${msg}`);
}
// ── Real response asserted on real rendered content ────────────────
// The Cognitive Pipeline section renders for ANY successful response
// (even zero-evidence), so it's the reliable "the flow produced a
// result" anchor.
await expect(cognitivePipeline).toBeVisible();
// "N analyzed" appears in the reasoning header / confidence meter.
await expect(page.getByText(/\banalyzed\b/).first()).toBeVisible();
// A confidence percentage renders (the big meter uses "%"). Assert a
// digit-followed-by-% is present somewhere in the response region.
const confidencePct = page.locator('text=/\\d+%/').first();
await expect(confidencePct).toBeVisible();
// Primary Source citation block renders the recommended answer.
await expect(primarySource).toBeVisible();
// Evidence section header renders with its count.
await expect(page.getByRole('heading', { name: /^Evidence/ })).toBeVisible();
// ── 4. The WebGPU organ field lit up (8-stage organ, non-black) ────
// Let the field settle after the DeepReferenceCompleted scene drives it.
await page.waitForTimeout(3000);
const sample = await sampleCanvas(page);
expect(sample.ok, 'canvas should be sampleable').toBe(true);
expect(
sample.rendered,
`organ field should render non-black (avgLum=${sample.avgLum}, variance=${sample.variance})`
).toBe(true);
await page.screenshot({
path: 'e2e/screenshots/reasoning-flow-lit.png',
fullPage: true
});
// ── 5. No real app / WebGPU-validation errors ──────────────────────
expectNoErrors(capture);
});
test('an example chip also drives the full flow', async ({ page }) => {
const capture = captureErrors(page);
await page.goto(REASONING);
await page.locator('canvas').first().waitFor({ state: 'attached', timeout: 15_000 });
// The example chips render only in the empty state. Click the first one;
// it sets the query AND immediately calls ask().
const chip = page.getByRole('button', {
name: 'How does FSRS-6 trust scoring work?'
});
await expect(chip).toBeVisible();
await chip.click();
const cognitivePipeline = page.getByRole('heading', { name: 'Cognitive Pipeline' });
const errorSurface = page.locator('text=/^Error:/');
await Promise.race([
cognitivePipeline.waitFor({ state: 'visible', timeout: 30_000 }),
errorSurface.waitFor({ state: 'visible', timeout: 30_000 })
]);
const errored = await errorSurface.isVisible().catch(() => false);
if (errored) {
const msg = (await errorSurface.textContent())?.trim();
throw new Error(`Reasoning Theater surfaced a backend error: ${msg}`);
}
await expect(cognitivePipeline).toBeVisible();
await expect(page.getByText(/\banalyzed\b/).first()).toBeVisible();
expectNoErrors(capture);
});
});