Skip to content

feat: design & quality uplift (Suspect Web, Case Archive, world switcher, renderer decomposition) - #123

Open
komod0 wants to merge 25 commits into
mainfrom
feat/design-uplift-2026-06
Open

feat: design & quality uplift (Suspect Web, Case Archive, world switcher, renderer decomposition)#123
komod0 wants to merge 25 commits into
mainfrom
feat/design-uplift-2026-06

Conversation

@komod0

@komod0 komod0 commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Summary

A large design-and-quality uplift building on the repo audit. Lands the previously-uncommitted media-generation work, a full SurfaceRenderer decomposition, a new Suspect Web A2UI component, the Case Archive + on-desk world switcher, a per-world copy system, and a batch of polish/quality fixes.

Authored as komod0. Branched fresh off main.

What's included (10 focused commits)

  • chore(repo): enforce style in eslint (eqeqeq/no-var/no-explicit-any/no-console + named exports, Next.js entrypoints excepted), add MIT LICENSE, refresh docs after the legacy-renderer/training removal, bump deps.
  • feat(media): AI image/video/music/sfx/voice generation routes & per-domain stores; live score + reactive audio-event bus; shared lib/api/responses + elevenlabs/client to collapse route boilerplate.
  • refactor(a2ui): decompose the SurfaceRenderer god-component (2708 → 370 lines) into renderers/ + internal/; harden the generated Modal (focus trap, Escape, autofocus). Behavior & public exports unchanged.
  • feat(a2ui): new Suspect Web relationship-graph component end-to-end; split normalizeA2UI/walk() into per-type dispatch; dev-only catalog-drift validator makes the v0.9 catalog load-bearing.
  • feat(workspace): persisted Case Archive (capped history + search + restore); promote world-switching from a dropdown to a first-class desk gesture (gallery tiles + on-desk switcher + cinematic transitions).
  • feat(chat): per-world AestheticCopy system (kills noir-only English in the other 5 worlds); detective avatar + diegetic message metadata; real-audio VU meter (was Math.random); prompt-history recall.
  • feat(settings): focus trap, shared FileUploadField (fixes drifted upload size limits), viewport meta + desktop notice.
  • fix(ui): replace emoji slop-tells with theme-tinted lucide icons; theme the Sandpack preview to the active world.
  • test: fix a fraudulent always-passing e2e test, replace race-prone waitForTimeout sleeps with deterministic waits, drop CI retries to 0, add useA2UIStream coverage.
  • fix(a2ui): emit catalog-complete Slider to clear the last catalog-drift warning.

Verification

Local full gate green: prettier ✓ · eslint (0 errors) ✓ · stylelint ✓ · typecheck ✓ · vitest 1223 passing / 107 files ✓ · next build ✓. Zero catalog-drift warnings across the suite.

🤖 Generated with Claude Code

komod0 added 25 commits June 19, 2026 01:54
- eslint.config.mjs: enforce eqeqeq/no-var/no-explicit-any/no-console and
  prefer named exports (Next.js entrypoints excepted); pre-existing
  violations set to warn for incremental cleanup
- add MIT LICENSE (README claimed MIT with no file)
- docs: fix stale dir references after the legacy-renderer/training removal,
  correct test count, archive proposals referencing deleted code
- bump deps and add skills-lock.json
- new routes: images/generate, images/[id]/{direct,redevelop}, interrogation,
  voice-design, live-music/key, sfx/[world]/[kind]
- per-domain stores (image/cast/sfx/recording) + resolveImageBytes
- live score (Lyria) + reactive audio-event bus and useLiveWire hook
- shared lib/api/responses (apiError/parseJsonBody) and elevenlabs/client to
  collapse duplicated route boilerplate
- new aesthetic fonts
…lines)

Extract ~25 component renderers into src/components/a2ui/renderers/ and the
shared context/binding/motion/validation/registry helpers into
src/components/a2ui/internal/. SurfaceRenderer is now the context providers +
COMPONENT_MAP assembly. Public exports and behavior unchanged; the existing
COMPONENT_MAP registry made the extraction mechanical. Also hardens the
generated ModalRenderer (focus trap, autofocus, Escape-to-close).
- new relationshipGraph component end-to-end: schema arm + normalize coercion
  (suspectWeb/links/source-target synonyms), SUPPORTED_LEGACY_TYPES, adapter
  handler, catalog schema, RelationshipGraph renderer (deterministic cork-board
  layout, red-string edges), and playbook guidance
- refactor normalizeA2UI (366-line monolith) and walk() (265-line switch) into
  per-type dispatch; extract liftTitle helper
- validateCatalogDev: dev-only non-throwing validation of adapter output against
  catalog schemas, making the v0.9 catalog load-bearing
- archiveSlice: persisted, capped (24) history of completed generations with
  oldest-eviction; CaseArchive slide-up film-strip with search and
  non-destructive restore (reuses the variant snapshot/restore path)
- promote world-switching from a dropdown to a first-class desk gesture:
  WorldGalleryTile living previews, ProfileSelector gallery, on-desk
  WorldSwitcher triggering the existing cinematic transition
- WorldTransition + useWorldSwitch cinematic world-change pipeline; CaseYarn
  red-string overlay; per-world fallback narration in DetectiveWorkspace
- extend AestheticCopy with logTitle/thinkingLines/chatEmptyLine/
  ttsUnavailableLine + dictaphone/interrogation strings; in-character values
  for all 6 worlds (kills noir-only English leaking into other worlds)
- chat: detective avatar instead of generic Bot icon, diegetic per-message
  timestamp/entry metadata, rotating thinking lines, sample-prompt chips,
  up/down prompt-history recall
- dictaphone: VU needle driven by a real WebAudio AnalyserNode (was Math.random)
…viewport

- useFocusTrap + CustomizationPanel dialog semantics (focus trap, Escape,
  focus restoration)
- shared FileUploadField replacing duplicated upload logic in Color/Audio
  customizers (fixes the drifted 10MB/20MB size limits)
- viewport meta + desktop-only notice in layout
- customization lab + theme-generator refinements
…e world

- KanbanBoard/DataDashboard: literal emoji glyphs (assignee, trend arrows)
  replaced with theme-tinted lucide icons
- SandpackPreview: build a Sandpack theme from the active aesthetic palette
  instead of hardcoded dark, so ejected previews match the chosen world
- noir effects, photo developer, evidence skeleton, globals polish
- events.spec.ts: replace the always-passing expect(true).toBeTruthy() with a
  real assertion that the stream request fires
- replace race-prone waitForTimeout sleeps with deterministic web-first waits;
  drop CI retries to 0 so flakes surface instead of being masked
- new useA2UIStream unit test (previously-untested client SSE consumer)
- stream/theme routes adopt shared apiError/parseJsonBody helpers
Both are unused after the legacy-renderer removal; no remaining importers.
The dev catalog-drift validator flagged the adapter emitting Slider without the
schema-required min/max/value. Emit the same defaults the renderer falls back
to (0..100, value seeded to min) so the flat output satisfies sliderSchema.
Verified: zero catalog drift warnings across the full suite.
The on-desk world switcher + archive buttons widened the main column's
min-content; a bare 1fr is minmax(auto,1fr) and won't shrink below that, so the
board pushed past the reserved sidebar margin and overlapped it. Use
minmax(0,1fr) so the column can shrink. Caught by the e2e 'panels do not
overlap' spec.
… gallery

- CustomizationPanel: only auto-close on committing to a BUILT-IN world (the
  cinematic-on-clean-desk case); selecting/creating a CUSTOM profile keeps the
  lab open so you can immediately tune it (was closing → tabs detached → e2e
  hangs on upload/tab tests)
- migrate ~30 e2e call sites from the removed 'Select active profile' dropdown
  to the new always-visible gallery ('Switch to <world>' buttons)
- rework Test 5.2 to reopen the lab between rapid built-in world switches
Update the two DeskLayout grid-template assertions to match the minmax(0,1fr)
main column from the sidebar-overlap fix.
- video: veo-3.1-*-preview rejects `generateAudio` (400 INVALID_ARGUMENT) —
  audio is native/prompt-steered. Gate the flag to the veo-3.0 family only
  (3.1 generates audio natively); fixes video generation failing outright.
- schema: coerce dataDashboard trend.value emitted as a string ("12", "+5%")
  to a number, dropping non-numeric values instead of salvaging the whole
  dashboard away.
- schema: drop out-of-enum button `action` values (e.g. "navigate") so the
  button still renders instead of being dropped from the surface.
- tests for all three, incl. veo-3.0 still opting into generateAudio.
- grand-hotel no longer reuses noir's jazz loop: music.src now points at a new
  lazy-generated /api/music/grand-hotel/atmosphere route that renders the
  world's musicStylePrompt (1920s lobby band) via ElevenLabs Music on first
  request, caches to disk, and falls back to the noir loop without a key —
  mirroring the existing lazy SFX pipeline.
- cyber-fixer voice: replace the stock 'Adam' (generic-AI sounding) with Matt
  (natural, hyper-conversational) and retune voiceDirection for a natural voice
  (was tuned for the robotic one).
…eractive fields

Display + interactive fields (text/stat/input/select/checkbox value, button
action) now accept {path}/functionCall bindings instead of being String()-ed to
[object Object] or salvaged out. Adds matchSet (conditional write), array
actions, a reveal wrapper, and a stateImage that edits-on-state via a new
non-destructive /fork route (caches both states).
TypewriterText kept typing progress in internal state seeded to empty, so the
conditional sidebar remount reset it and re-typed completed messages. Only the
streaming message animates now (animate={isStreaming}); completed messages
render instantly and snap to full text if streaming ends mid-type.
…tness

Consolidation audit follow-up: widen every resolved display/value field to
accept {path}/functionCall bindings (heading/paragraph/card title+desc, stat
helper, slider/dateTimeInput value+min/max, kanban/dashboard/graph title, icon
name, audio description/speaker) so a bound node is no longer dropped at
validation. Also: matchSet with an omitted then/else no longer DELETES the
target (undefined = v0.9 delete), and the eject exporter handles reveal
(children inline) + stateImage (base image) instead of dropping them. Plus
dev-timestamps flush-on-close. +regression tests.
…_MAP

- Extract the normalization layer into normalize.ts and the shared pure
  primitives (SUPPORTED_LEGACY_TYPES, isBindingObject) into legacy-constants.ts,
  a leaf module — one-way DAG (constants ← normalize ← schema), no cycle.
  schema.ts re-exports them so @/lib/protocol/schema imports are unchanged.
- Drop the redundant double-normalize: a2uiInputSchema already runs
  normalizeA2UI via z.preprocess, so callers pass raw nodes (route + adapter).
- Type COMPONENT_MAP as Record<CatalogComponentName|ExtensionComponentName, FC>
  so a missing/typo'd renderer is a compile error, not a runtime [Unknown: X]
  (mirrors the adapter's compiler-enforced handlers map).

Pure refactor — 1270 tests unchanged, behavior identical.
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