withCompaction's checkpoint store is now a signed token the browser holds per thread and sends back as forwardedProps.contextCheckpoint. The server keeps no conversation. withCompaction reuses the checkpoint only while the history still starts with the summarized messages; later summaries fold forward from it. Measured locally on a 60-pair history: NanoGPT turn 2 took 18.5 s instead of about 70 s; OpenRouter turns 2-3 took 3.2 s with no summary call.
269 lines
37 KiB
Markdown
269 lines
37 KiB
Markdown
# Unified landing and story product
|
||
|
||
`src/routes/`, `src/router.tsx`, `src/start.ts` and `src/server.ts` form one TanStack Start app. `/` renders `src/apps/landing/`; `/start`, `/chat`, `/history` and `/about` render `src/apps/product/`. Shared UI lives in `src/components/ui/`, shared pure helpers in `src/lib/`, and the database/runtime/admin in `src/autokit/`. Route-owned styles keep the landing, story app and admin surfaces separate.
|
||
|
||
The landing loader reads the existing Autokit content tables. Both surfaces use the Autokit 27 session, the Operator CLI and HTTP handlers, and the landing’s S3 database, with local and production timelines. The pathless `_public` route renders one native `<Outlet />` inside a persistent app stage. Its index child shows the start screen in the desktop hero; `/start`, `/chat`, `/history` and `/about` center it below the shared header. `public-layout.tsx` keeps the animated background and header mounted. The phone chrome and marketing sections disappear on app routes. App menus remain portalled into the app frame. `defaultViewTransition` and the shared `storytellurx-app` snapshot name animate the frame; layout mode follows presented route matches so the old hero position is captured before it moves. Mobile hides the preview with CSS.
|
||
|
||
The persona contracts and historical parity evidence below describe the story product. Unqualified product paths are relative to `src/apps/product/`; tests remain in `src/tests/`.
|
||
|
||
# V1 parity: native AI and independently authored personas
|
||
|
||
Baseline: `40124c2`. Reference: `../storytellurx` and live V1. Local implementation and commits; no push or deployment. The latest instruction introduces one consistent folder per persona. Shared infrastructure keeps flat type-first folders.
|
||
|
||
## Persona folder and export contract
|
||
|
||
```text
|
||
src/apps/product/personas/{id}/
|
||
definition.ts # definition: public data, no React/server imports
|
||
persona.ts # personaUI: typed component/style registry
|
||
components.tsx # Metrics + optional persona-owned slot overrides
|
||
styles.stylex.ts # personaStyles + persona metric styles
|
||
theme.stylex.ts # theme and getTheme(state)
|
||
archive.md # supplied instructions; server imports only
|
||
instructions.server.ts # personaInstructions: own archive + trusted instructions
|
||
```
|
||
|
||
The same rail applies to Samantha69, Nelly, Xixi, Hisoka, Cybelle, Galatée, Polaire, gateway and pause. No additional persona subfolders. Shared code stays at the roots of `components/`, `hooks/`, `contexts/`, `standard-schemas/`, `registries/`, `functions/`, `server/`, `storage/`, `styles/`, `content/`, `tests/` and `fixtures/`. Routes and Autokit retain framework conventions.
|
||
|
||
No empty scaffolds, generic `utils.ts`, wildcard barrels or catch-all providers. Every new file needs named exports and a real consumer before implementation.
|
||
|
||
`contexts/persona-presentation-context.tsx` exports `PersonaPresentationContext` and `usePersonaPresentation` for the native layout and part views. Its read-only story state is computed once from native messages. It also carries the native persistence failure flag and one ephemeral `imageMessageId` activated by native `onFinish`, so automatic image requests distinguish fresh completions from restored history; it has no message copy or replacement chat lifecycle. This avoids replaying the same story patches separately for every rendered bubble.
|
||
|
||
## Core consumes typed persona slots
|
||
|
||
Before, central maps and components selected persona behavior:
|
||
|
||
```diff
|
||
-stylex.props(chatStyles.transcript, transcriptStyles[state.personaId])
|
||
-<PersonaMetrics state={state} />
|
||
-<NarrativeComposer personaId={state.personaId} ... />
|
||
+const persona = personaUIRegistry[state.personaId];
|
||
+stylex.props(chatStyles.transcript, persona.styles.transcript)
|
||
+<persona.components.Metrics state={state} />
|
||
+<persona.components.Composer
|
||
+ state={state}
|
||
+ styles={persona.styles}
|
||
+ Suggestion={persona.components.Suggestion}
|
||
+ {...nativeComposerProps}
|
||
+/>
|
||
```
|
||
|
||
Each `persona.ts` exports the same contract:
|
||
|
||
```ts
|
||
export const personaUI = {
|
||
definition,
|
||
getTheme,
|
||
styles: personaStyles,
|
||
statusVariant: "classical",
|
||
titleMotion: { from: "'wght' 300", to: "'wght' 400" },
|
||
components: { ...defaultPersonaComponents, Metrics },
|
||
} satisfies PersonaUI;
|
||
```
|
||
|
||
`registries/persona-ui-contract.ts` declares typed slots for `Metrics`, `Bubble`, `Explanation`, `Composer`, `Suggestion`, `Image`, `Status`, `Background`, `StateChanges` and `SubmitIndicator`. Shared defaults receive presentation props and do not import the registry. A persona can override a slot in its own `components.tsx` without changing native chat mechanics.
|
||
|
||
```ts
|
||
export type PersonaUIRegistry = {
|
||
[Id in PersonaId]: PersonaUI & { definition: PersonaDefinition & { id: Id } };
|
||
};
|
||
export const personaUIRegistry = {
|
||
samantha69,
|
||
nelly,
|
||
xixi,
|
||
hisoka,
|
||
cybelle,
|
||
galatee,
|
||
polaire,
|
||
storytellurx_gateway,
|
||
storytellurx_pause,
|
||
} satisfies PersonaUIRegistry;
|
||
```
|
||
|
||
The public catalog imports `definition.ts` without importing UI. Only persona server modules import archives; the server registry consumes their validated `instructions.server.ts` exports. A persona cannot import another persona's implementation. Shared defaults prevent duplicated markup and mechanics; persona folders own identity-specific decisions.
|
||
|
||
Picker image fitting and localized composer placeholders also belong to each persona. The core supplies `placeholder={persona.definition.placeholder[locale]}` and composes `persona.styles.pickerImage`. It contains no named-persona styling condition and no `placeholder_{id}` locale lookup. Both languages retain the supplied strings exactly. Architecture checks reject literal named persona IDs in shared presentation modules.
|
||
|
||
## Native ownership replaces custom publication concepts
|
||
|
||
```diff
|
||
-new ApprovedTurnAdapter(() => resolveNarrativeTurn(...))
|
||
-PublishedTurnSchema
|
||
-selectCommittedTurns(messages)
|
||
-selectVisibleTurn(committed, visibleMessageIds)
|
||
+chat({ adapter: provider, ...createChatRequest(messages, context), stream: true })
|
||
+useChat({ connection, outputSchema: ModelTurnSchema, persistence, threadId })
|
||
+message.parts
|
||
+message.metadata?.requestPreview
|
||
+useMessageScrollerVisibility().visibleMessageIds
|
||
```
|
||
|
||
Native middleware validates the completed structured response, then forwards the original event with approved data. It forwards schema JSON deltas after the native `structured-output.start` event, and filters unrelated commentary and provider-private reasoning. It does not construct stream events or run a nested chat. Native generation result middleware also replaces the custom image adapter.
|
||
|
||
TanStack owns messages, parts, run status, tools, interrupts, stream parsing, cancellation and transcript persistence. `createChatUI` owns part dispatch. Unpatched shadcn MessageScroller owns bottom following, manual reading and visibility with autoScroll enabled and row anchors disabled (REUI ai-chat-9). Motion owns animation timing; Matter owns picker physics; Base UI owns horizontal suggestion scrolling, disclosure state, button behavior and dialog focus. See [the reference composition and current acceptance matrix](reports/chat-composition.md).
|
||
|
||
Application rules remain: `ModelTurnSchema` describes story data; `applyStatePatch` defines explicit bounded field/array semantics; `readState` derives persisted story state from valid completed native assistant parts. Its explicit presentation mode also reads the active native partial, accepting independently validated state fields while incomplete enums remain at their preceding values. No published/committed lifecycle, second message store, custom visibility observer or wrapper hook that merely renames native values.
|
||
|
||
Streaming `outputSchema` provides types but does not establish runtime validation. Retain completed-object validation in native middleware. Do not add Robot/XState/Jotai/Effect without a requirement native lifecycle cannot express.
|
||
|
||
## Shared export contracts
|
||
|
||
| File | Exports | Consumers |
|
||
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
|
||
| standard-schemas/persona.schema.ts | PersonaIdSchema, PersonaDefinitionSchema, inferred types | definitions, registries, routes |
|
||
| standard-schemas/persona-instructions.schema.ts | PersonaInstructionsSchema, PersonaInstructions | persona server modules, server registry |
|
||
| standard-schemas/narrative-state.schema.ts | NarrativeStateSchema, StatePatchSchema, inferred types | state rules, provider options, UI |
|
||
| standard-schemas/narrative-turn.schema.ts | ModelTurnSchema, SystemTurnSchema, PersonaTurnSchema, SuggestionSchema, inferred types | native chat, headless parts |
|
||
| standard-schemas/chat-request.schema.ts | ChatContextSchema, ModelIdSchema, LocaleSchema, inferred types | routes, request options |
|
||
| standard-schemas/request-preview.schema.ts | RequestPreviewSchema, RequestPreview | native metadata, inspector |
|
||
| standard-schemas/image-request.schema.ts | ImageRequestSchema, inferred type | image endpoint |
|
||
| standard-schemas/conversation-history.schema.ts | HistoryEntrySchema, HistoryExportSchema, inferred types | metadata index, export |
|
||
| registries/persona-ui-contract.ts | PersonaUI, PersonaStyles, typed slot props | persona modules, shared defaults |
|
||
| registries/persona-ui-registry.ts | personaUIRegistry | shell, native chat |
|
||
| registries/persona-catalog.ts | personaCatalog, selectablePersonas, getPublicPersona, personaImageSrc | picker, initialization, server registration |
|
||
| registries/persona-registry.server.ts | getPersonaDefinition, validatePersonaRegistry | provider instructions |
|
||
| registries/model-catalog.ts | modelCatalog, getPublicModel, normalizeModelId | routes, model/status pickers |
|
||
| registries/model-registry.server.ts | getModelDefinition | server request options |
|
||
| functions/apply-narrative-state-patch.ts | initialNarrativeState, applyStatePatch | initialization, state derivation, validation |
|
||
| functions/select-narrative-state.ts | readState | views, history metadata |
|
||
| functions/create-initial-messages.ts | createInitialMessages | native chat initialization |
|
||
| functions/markdown-typography.ts | markdownTypography | native NarrativeText renderer |
|
||
| server/create-chat-request.server.ts | createChatRequest | native chat endpoint |
|
||
| server/create-response-middleware.server.ts | createResponseMiddleware | createChatRequest + neutral native fixtures |
|
||
| server/create-model-adapter.server.ts | createModelAdapter, createImageAdapter | native endpoints |
|
||
| server/image-middleware.server.ts | imageMiddleware | native image endpoint |
|
||
| components/narrative-chat.tsx | NarrativeChat, useNarrativeChatContext (native accessor) | route, image composition |
|
||
| components/persona-ui-defaults.tsx | defaultPersonaComponents | persona registration |
|
||
| components/narrative-text.tsx | NarrativeText | native text parts, fragments, explanations, suggestions |
|
||
| components/persona-submit-indicator.tsx | TextSubmitIndicator, PaperPlaneSubmitIndicator | persona SubmitIndicator slots |
|
||
| components/narrative-composer.tsx | NarrativeComposer | default composer slot |
|
||
| components/turn-receipt.tsx | TurnReceipt | native structured turn renderer; Base UI owns disclosure |
|
||
| components/public-shell.tsx | PublicShell | routes, native layout |
|
||
| components/public-header.tsx | PublicHeader | shell |
|
||
| components/public-about.tsx | PublicAbout | about route |
|
||
| components/persona-picker.tsx | PersonaPicker | start route |
|
||
| components/model-picker.tsx | ModelPicker | start route |
|
||
| components/conversation-status.tsx | ConversationStatus | native layout |
|
||
| components/persona-metric-primitives.tsx | MetricNumber, MetricBreathingLabel, MetricLens, MetricSemantics, resonanceFrames | persona components |
|
||
| components/persona-suggestion.tsx | PersonaSuggestion | persona Suggestion slots |
|
||
| components/narrative-state-changes.tsx | NarrativeStateChanges | default StateChanges slot |
|
||
| components/request-inspector.tsx | RequestInspector | native message metadata |
|
||
| components/generated-image.tsx | GeneratedImage | native message composition |
|
||
| components/conversation-background.tsx | ConversationBackground | visible-message presentation |
|
||
| components/conversation-history.tsx | ConversationHistory | history route |
|
||
| components/history-preference.tsx | HistoryPreference | history UI |
|
||
| hooks/use-persona-physics.ts | usePersonaPhysics | picker |
|
||
| hooks/use-history-preference.ts | useHistoryPreference | preference UI |
|
||
| contexts/locale-context.tsx | LocaleProvider, useLocale | public presentation |
|
||
| contexts/persona-presentation-context.tsx | PersonaPresentationContext, usePersonaPresentation | native layout and part views |
|
||
| storage/conversation-storage.ts | historyStorage, createConversationPersistence, deleteHistory, deleteAllHistory, exportHistory | native chat, history UI |
|
||
| storage/history-index-storage.ts | `listHistory`, `updateHistoryIndex`, `removeHistoryIndex`, `subscribeHistoryIndex` | metadata UI, native persistence delegation |
|
||
| storage/preference-storage.ts | readPreferences, writePreferences, subscribePreferences | opt-in preference |
|
||
| styles/design-tokens.stylex.ts | personaVars, spacing, motionTokens | shared styles, persona themes |
|
||
| styles/*.stylex.ts | named shared style maps | owning shared components |
|
||
| content/*-messages.ts | locale dictionary | locale context, initial messages |
|
||
| content/initial-prompts.json | supplied V1 starter data | initial messages |
|
||
| content/about-description.html | unchanged published rich-text fragment | PublicAbout |
|
||
| fixtures/native-conversation-stream.ts | neutralTurn, nativeConversationStream, nativeIncrementalConversationResponse, nativeImageStream, neutralImageDataUrl | offline fixture verification, browser tests |
|
||
| playwright.config.ts | default Playwright config | public browser test runner |
|
||
|
||
## Import boundaries
|
||
|
||
```text
|
||
standard-schemas → Zod + schemas
|
||
functions → schemas + data-only definitions/content + native types + pure formatting packages
|
||
persona UI → own definition/components/styles/theme + shared primitives
|
||
public catalog → persona definition.ts only
|
||
server registry → persona instructions.server.ts exports
|
||
components → public registries + schemas/functions/hooks/styles + packages
|
||
routes → validated search + component/server delegation
|
||
```
|
||
|
||
No public import graph may reach `.server.ts`, archives or Autokit server infrastructure. Pure state/schema modules cannot import React runtime, storage or routes. Shared application modules cannot import route files. Production cannot import tests/fixtures. Architecture tests enforce boundaries; the mapped `PersonaUIRegistry` type enforces complete registration and matching identity keys at compile time. Obsolete archive/theme/metric/starter lookup keys are removed: persona modules import their owned values directly.
|
||
|
||
## Parity and remaining proof
|
||
|
||
Reference flow: seven-persona picker → supported Google/Hermès model selection → scrolling chat. Preserve model-driven pause, 1–5 fragments, persona suggestions (3–6), gateway suggestions (0–2), original copy, metrics, image presentation and public inspector. Suggestions fill the draft; Enter sends, Shift+Enter inserts a newline, IME composition must not submit. Native image results become media parts in their owning assistant message.
|
||
|
||
Approved differences: opt-in device history (off initially), supported current model names, progressive native structured output with final validation before committing state (the user’s later streaming request supersedes the initial complete-turn-only reveal). No new Stop flow. Public CSS uses StyleX with explicit `styleResolution: "application-order"` so authored shorthands are emitted and slot composition follows application order; The story surface does not use Tailwind; the separate landing stylesheet compiles only its own sources, including its sound-aware Button. Autokit precompiled admin CSS remains isolated.
|
||
|
||
```text
|
||
Reference shell: 384px max-width; 32px header; 12px/16px header type.
|
||
Owning sources: public-shell.stylex.ts + persona-owned theme/style slots.
|
||
Proof required: public keyboard tests + computed styles + screenshots.
|
||
```
|
||
|
||
```ts
|
||
test("persona registration preserves the public conversation", async ({ page }) => {
|
||
await page.goto("/chat?persona-id=samantha69&model-id=gemini");
|
||
await expect(page.getByTestId("public-shell")).toHaveCSS("max-width", "384px");
|
||
await expect(page.getByRole("textbox", { name: "Votre message" })).toBeVisible();
|
||
await expect(page).toHaveScreenshot("samantha-introduction.png");
|
||
});
|
||
```
|
||
|
||
Before claiming parity: save reference/current screenshot, accessibility and computed-style evidence for all seven personas at 390×844, 768×1024 and 1440×900. Cover deterministic/settled/dragged picker, low/medium/high metrics, fragments, suggestions, inspector focus restoration, image success/failure/history restoration, pause, locale, storage failure, reduced motion, keyboard and zoom. Never update baselines to conceal differences. Neutral fixtures exercise mechanics without new narrative content.
|
||
|
||
The `/about` route preserves the supplied localized flow copy and the published `sites.description_html` fragment recovered with curl on 2026-09-08. The original 6,915 bytes stay in `content/about-description.html`; its SHA-256 is verified. About uses V1’s plain monospace shell/header. Scoped `public-about.css` applies descendant typography to preserved rich-text markup, an intentional exception to atomic StyleX authoring for that static fragment.
|
||
|
||
Historical pre-composition verification covered combined/fallback provider paths, validation failure, cancellation, withheld draft/reasoning and inspector metadata, with a passing production build and 195 public browser cases. Those counts do not certify the later progressive-rendering and native-composition changes. Screenshot baseline comparison remains separate from behavioral verification; full pixel parity is not claimed.
|
||
|
||
Accepted data constraint adjustment: the 88 original starters in `src/apps/product/content/initial-prompts.json` contain French labels/prompts up to 69/351 characters and English labels/prompts up to 60/305. The shared `SuggestionSchema` therefore permits bounded labels of 70 and drafts of 400 characters, preserving the supplied text without truncation or a separate initialization schema. Contract tests validate every original starter in both locales.
|
||
|
||
## Provider retry ownership
|
||
|
||
Text generation uses TanStack AI 0.70.0, ai-client 0.40.0, ai-react 0.31.0, ai-openai 0.30.1 and ai-compaction 0.3.0 (`patches/@tanstack__ai@0.70.0.patch` aligns one adapter declaration with `exactOptionalPropertyTypes`). Both gateways use the OpenAI-compatible Chat Completions adapter with SDK `maxRetries: 0`. A transient provider failure before any output (connection, HTTP 408/409/429/5xx) is retried inside the same request by the response middleware's `onModelError` hook, at most twice. The browser response-recovery flow then owns one budget: an initial request plus at most two automatic attempts, waiting 1/2 seconds, each carrying `X-Generation-Attempt`. Recoverable failures include connection errors, interrupted output, HTTP 408/409/429/5xx, idle or attempt timeouts, and a completed turn without fragments. Other formatting mistakes are coerced on the server (`fitTurn`) and never reach recovery. Authentication, credits, configuration, refusals, cancellation, and unknown errors stop the cycle.
|
||
|
||
Recovery uses isolated native ChatClient runs against the same pre-turn history; every candidate regenerates the original turn, and nothing of the failed reply is sent back. Readable partial text stays mounted until a candidate passes server schema and state validation. Metrics, suggestions, and image generation use validated completed turns only. Stop, panic, context changes and unmount cancel both backoff and candidate requests; stale candidates cannot commit. Exhausted cycles require manual retry, and restored incomplete browser history never starts requests automatically.
|
||
|
||
There is no server conversation store or durable-stream log. Request disconnection still aborts the provider. Browser history remains local. Validation logs contain issue codes and paths, never values or raw output; upstream gateway retention is independent. Resuming after a dropped connection generates a new candidate rather than replaying stored server events.
|
||
|
||
Long histories are compacted by `withCompaction` + `summarizeOldest` (`context-budget.server.ts`). Its checkpoint store is carried by the browser, not the server: after a summary the server emits a `context.checkpoint` event holding the summary and the summarized prefix's hash, HMAC-signed with `APP_KEY` and bound to the thread. The text connection keeps the latest token per thread in memory, out of the chat, and sends it back as `forwardedProps.contextCheckpoint`. `withCompaction` reuses it only while the history still starts with the summarized messages, and later summaries fold new messages into it instead of re-summarizing the whole history (measured 20 Oct: a long NanoGPT turn without the summary call took 18.5 s instead of about 70 s). A reload, an edited history or an unverifiable token simply summarizes again.
|
||
|
||
Image generation separately uses the OpenAI SDK's native `maxRetries: 4` through the TanStack image adapter. Offline tests exercise both gateways, exact text/image request ceilings, server-side retries, cancellation, and failure classification. Browser tests verify preserved text, inline progress, completion-only state updates, manual recovery, and single image activation.
|
||
|
||
## Continuation audit: image envelope and browser evidence
|
||
|
||
The image hook supplies `body: { messageId }`. TanStack merges that body with the generation input and creates the `data`/`forwardedProps` envelope itself. Nesting `forwardedProps` in the hook body made the endpoint reject automatic image requests. Native HTTP regressions now verify the exact flat wire shape and reject the former nested shape before provider execution.
|
||
|
||
The public suite includes 126 persona/metric/locale cases and 69 behavior cases across three viewports. It covers all seven personas in French/English at 0/0.5/1 metrics, one/five fragments, three/six suggestions, draft filling, inspector focus, picker/model navigation, failure/retry, imagery, pause, history and IME composition. The explicit `STORYTELLURX_CAPTURE_CURRENT=1` mode retains every behavioral assertion and attaches current screenshots without baseline comparison. Default execution still requires V1 baselines; capture success does not claim visual parity. Helium and curl are authorized options, not mandatory gates for other work.
|
||
|
||
The historical 2026-09-08 pre-composition production-browser run passed **195/195 cases**, including all 126 metric/state interactions. After the final status inset and Nelly octave corrections, all **171 affected cases** passed again. That checkpoint checked restored history with bottom positioning. The current REUI ai-chat-9 composition uses native bottom positioning without another model request; fresh whole-app verification is pending the external local-s3 dependency restoration. The paired capture runner saved 126 reference/current pairs with accessibility trees and computed styles. The completed final capture refresh reuses unchanged V1 references. All 126 shell/input/form/submit rectangles match within 0.01px. Input font sizes/line heights and all 378 semantic meters were also verified against the paired evidence. No baseline has been updated to hide a difference.
|
||
|
||
## Typography and browser corrections
|
||
|
||
The user requested `@bureaudouble/typographe` on 2026-09-08. Its existing alpha.9 dependency is now direct, along with the already resolved HTML parser. Typography is an approved change to punctuation and line breaking; persona fonts, layout, schemas, drafts, saved transcripts and inspector data retain their separate responsibilities.
|
||
|
||
```tsx
|
||
// components/narrative-text.tsx — consumed by native parts and suggestions
|
||
const { locale } = useLocale();
|
||
return <TextPart {...props} extensions={typography[locale]} />;
|
||
```
|
||
|
||
`functions/markdown-typography.ts` exports the single TanStack Markdown extension (`@tanstack/ai-react` 0.29 renders with TanStack Markdown, which escapes raw HTML and removes executable URLs itself). It returns only text to the existing Markdown tree's text nodes: Typographe’s generated markup is not admitted. Code, links, URLs and literal HTML/entity-bearing text stay exact. Alpha.9 exposes an HTML-string API. Generated indexed spans share quote context across inline emphasis within each block; only their text returns to the original nodes. The library handles widows inside the final multiword text node; it does not bind a final single word across separate inline nodes. Application code does not recreate that missing rule. Tests exercise French/English quotes and punctuation, widows, emphasis, code, URLs, escaped HTML, sanitization and unchanged transcript values. No punctuation rules are copied into application code.
|
||
|
||
Browser evidence found and corrected: SSR controls accepting input before hydration (`useHydrated` now gates composer and preference); picker grid blending; header colors, corners and button reset; model detail color; About layout and published content; classical presentation for every persona except gateway; persona-owned explanation labels and send indicators; Samantha metric typography. Automatic images now activate from native `onFinish`, because native `runId` becomes null at completion. Initial messages, `setMessages` and persistence restoration do not fire that callback.
|
||
|
||
Native completion and storage failures now have browser proof, including displayed French/English typography across bold text. Production validation middleware is shared unchanged with the neutral fixtures so invalid-output cases exercise the actual publication boundary.
|
||
|
||
The current chat uses the supplied shadcn composition: default `MessageScroller.Provider`, native `scrollAnchor` on user messages, a bounded transcript viewport and a sibling composer in normal footer flow. The header remains visible during scrolling. The application has no composer resize observer, compensating inset variable or header-direction scroll handler. Persona fonts, tokens, metrics and decoration remain independently owned. See [Chat composition](reports/chat-composition.md) for the source comparison and exact TSX changes.
|
||
|
||
The native scroller uses REUI ai-chat-9’s `autoScroll` provider and `scrollAnchor={false}` on every message. It follows arriving content at the live edge and releases following on manual wheel, touch or keyboard navigation. The package owns the jump control and all scroll calculations. No custom collapse handler, user-turn spacer, or shadcn package patch is used.
|
||
|
||
Suggestion rows use Base UI `ScrollArea.Root`, `Viewport`, `Content`, horizontal `Scrollbar` and `Thumb`; the real viewport is also the existing Motion card effect’s scroll container. Content grows naturally, with no empty suggestion placeholders. Public state/details use a closed-by-default `TurnReceipt`; expanded content has natural height and no inner constrained scroller. The textarea stays focusable and read-only while the native run is busy. Send and retry use Base UI buttons with existing native callbacks.
|
||
|
||
Variable-font medium weight is declared with the correct axes in the owning personas. Reduced-motion presentation keeps static suggestion emphasis and a representative Nelly waveform. Hisoka, Nelly, Xixi and Polaire use V1’s rendered sRGB primary values to preserve their palette across different CSS compiler gamut conversions. Typography wrapping, keyboard focus, complete-turn validation, opt-in history and reduced-motion behavior are explicit usability changes.
|
||
|
||
Historical pre-composition verification: 298 unit/integration tests passed; type-aware check has zero errors and three existing warnings; production build passes. The complete 195-case browser run passes, followed by 171 passing affected-case checks after the final spacing corrections. The final paired visual refresh contains 126 reference/current pairs with accessibility and computed-style artifacts in `/private/tmp/stx-parity/matrix-complete`. These local captures are not approved screenshot baselines; typography wrapping and reduced-motion differences remain explicit. No deployment or push is authorized.
|
||
|
||
## Source size
|
||
|
||
The historical pre-composition application contained 7,538 physical TypeScript/TSX/CSS lines in 139 source files, including the preserved Autokit setup. The same count at checkpoint `053afbc` is 14,361 lines in 82 files: a 47.5% reduction. Counts include blanks/comments and exclude tests, fixtures and the generated route tree. The former single-chat README’s 94.2% reduction described a different, much smaller scope and has been removed. Persona-owned UI and validation are retained instead of pursuing an arbitrary line-reduction target.
|
||
|
||
## Native progressive rendering
|
||
|
||
The user’s streaming request replaces the initial complete-turn-only presentation. `StructuredOutputPart.partial` is the sole partial-data source: TanStack owns JSON parsing, assembly, SSE transport and lifecycle. Components render available fragments, rewrite text and disabled suggestion labels as they arrive. Public explanations and state changes stream inside the closed-by-default receipt and become visible on explicit expansion; receipt counters reflect available fields. The active partial’s valid state fields update the shell, persona registry, metrics and state-change view. No second draft store or parser exists.
|
||
|
||
Only the active latest assistant part is previewed. Completion still passes `ModelTurnSchema` and state validation; failure or cancellation removes provisional presentation and restores completed state. Images, actionable suggestions and the public inspector wait for a validated final result. Persisted transcript parts retain TanStack’s native streaming/error statuses; only completed parts enter subsequent model context and history state. Malformed partial text values never reach Markdown or React as objects.
|
||
|
||
Server validation diagnostics report Zod issue paths/codes/constraints without logging response prose or request credentials. Native `RUN_ERROR.code` selects localized quota, availability and format errors in the composer. The Gemini adapter patch preserves provider codes through TanStack’s `toRunErrorPayload`; no custom provider-error parser is added.
|