88 lines
10 KiB
Markdown
88 lines
10 KiB
Markdown
# StoryTellurX V2
|
||
|
||
One TanStack Start application serves the marketing landing and the story product, backed by the landing’s existing Autokit database.
|
||
|
||
- `src/apps/landing/`: marketing content, components, fonts and styles.
|
||
- `src/apps/product/`: story UI, onboarding, personas, AI and device-local history.
|
||
- `src/components/ui/`: shared UI primitives; `src/lib/`: shared pure helpers.
|
||
- `src/routes/`: the single router; `src/autokit/`: shared schema, runtime, admin and seeds.
|
||
|
||
## Run locally
|
||
|
||
```sh
|
||
pnpm install
|
||
cp .env.example .env
|
||
# Configure the existing landing APP_KEY and S3 credentials; add the AI provider keys.
|
||
pnpm dev
|
||
```
|
||
|
||
`/` renders the marketing landing. `/start` enters the story app; `/chat`, `/history` and `/about` are its internal routes. The desktop hero frames the native app outlet as a phone; mobile and tablet hide that preview. A pathless `src/routes/_public.tsx` layout keeps the header, animated background, locale, sound and disclosure state shared. TanStack view transitions move the app from the hero to the centered screen on app navigation. `/admin` and `/api/rpc/*` use the shared Autokit runtime. Saving stories remains device-local and opt-in.
|
||
|
||
## Build revision
|
||
|
||
The product menu shows the first seven characters of the commit used to build the app, with the full hash in its tooltip. The build reads a valid full SHA from `APP_REVISION`, or falls back to `git rev-parse HEAD` in the source checkout. The same value is embedded in the server and browser bundles, independently of the hosting provider.
|
||
|
||
When Git or revision metadata is unavailable, invalid, or times out, the menu shows `untracked` and the build continues. Container builds that exclude `.git` can supply `APP_REVISION` as a build environment variable. Restart the development server or rebuild to pick up a new commit; uncommitted edits are not represented by the hash.
|
||
|
||
## Shared database
|
||
|
||
The runtime uses Autokit 27 / Migration 19 with the landing’s S3 database configuration. Keep the existing `APP_KEY` stable so encrypted provider settings remain readable. Local uses `autokit--local.sqlite`; production uses `autokit.sqlite` in the configured bucket. No separate local S3 server or V2 database path is used.
|
||
|
||
The database CLI runs `src/autokit/server.ts` through Deno:
|
||
|
||
```sh
|
||
pnpm db plan --timeline local
|
||
pnpm db apply --timeline local
|
||
pnpm db plan --timeline production
|
||
```
|
||
|
||
Review the plan and preserve a backup before any destructive migration. Deployment runs the compiled `db apply` command as the predeploy step in the deployment timeline. Environment files and local backups are ignored by Git; `.env.example` documents names without credentials.
|
||
|
||
## Bunny Magic Containers
|
||
|
||
`.github/workflows/release-on-bunny.yml` pins a reviewed commit of [bureaudouble/deno-bunny-deploy](https://github.com/bureaudouble/deno-bunny-deploy) that supplies `APP_REVISION` during the build. The shared action contains the Dockerfile, ignore rules, and Deno launcher. It reads the existing install, build, predeploy, and runtime settings from `deno.json`; this repository needs no local Dockerfile or container launcher. Images and build caches are stored in private GHCR packages.
|
||
|
||
GitHub variables `BUNNY_APP_ID` and `BUNNY_APP_URL` select the deployment. Secrets `GH_PACKAGES_TOKEN` and `BUNNYNET_API_KEY` authenticate private package installation and deployment. Build credentials use secret mounts. The workflow runs the existing predeploy command with the selected Bunny container's environment before updating its image.
|
||
|
||
The container keeps the existing Cloudflare R2 bucket and database. Set `APP_DATABASE=production`, preserve `APP_KEY`, and keep the existing `S3_*` and AI provider credentials in Bunny. `APP_URL` is the public origin used by the shared Deno launcher. `/healthz` reports the running commit; CI verifies that revision and the home page before purging the `autokit` page tag. No database or object-storage transfer is part of this hosting setup.
|
||
|
||
To enable Bunny HTML caching, set runtime variables `BUNNY_PULL_ZONE_ID` and `BUNNY_CDN_API_KEY`, then run `node scripts/bunny/configure-cache.ts` with those variables to configure the pull zone. The script installs credential-header cache bypass before enabling HTML caching and keeps query strings and hostnames separate. Set the GitHub variable `BUNNY_PULL_ZONE_ID` too so CI purges old HTML after deployments.
|
||
|
||
The shared `@bureaudouble/autokit-operator/page-cache` Bunny adapter caches eligible anonymous HTML on `/`, `/about`, `/start`, and `/history` for at most 60 seconds. Bunny separates cached HTML by the `age_legal` and `locale` cookies; Deno varies by `Cookie`, `Authorization`, and `X-Publishable-Api-Key`. Consent and language cookies pass through unchanged. The legal modal reads consent during server rendering, so `age_legal=true` omits it even before hydration; absent or false consent still requires the existing two-step confirmation. Requests with login cookies, authorization, API keys, or query strings remain uncached. Run `scripts/bunny/configure-cache.ts` during provisioning to install the selected-cookie cache key and credential bypass before deploying cacheable cookie variants. Autokit change notifications purge the shared `autokit` page tag; hashed assets retain their immutable cache. Each host registers its native adapter plus a targeted `remote` adapter for the other host. Writes therefore invalidate both CDNs; target selection prevents forwarding loops. `BUNNY_CACHE_ORIGIN` and `DENO_CACHE_ORIGIN` override the pilot and existing Deno origins. Both hosts authenticate purges with the unchanged `APP_KEY`. The short TTL bounds stale pages if the other host is unavailable or still running an older deployment.
|
||
|
||
## Package ownership
|
||
|
||
- TanStack AI owns messages, parts, runs, transport, cancellation, generation and transcript persistence. Native structured parts stream text and state through `partial`; shared middleware validates the final Zod result before committing it. No parallel transcript or custom streaming protocol exists.
|
||
- Native `createChatUI` dispatches parts through typed persona components. Shadcn's headless MessageScroller owns scrolling and visible-message tracking; Base UI owns dialogs; Motion and Matter own animation and picker physics.
|
||
- StyleX owns persona composition. The story product uses StyleX; the imported landing retains its scoped Tailwind build, with new shared and preview UI styled in StyleX. Autokit's precompiled admin stylesheet is isolated from the public application.
|
||
- `@bureaudouble/typographe` formats French and English presentation through a native TanStack Markdown extension. Code, URLs, drafts, saved transcripts and inspector data remain exact.
|
||
|
||
Adding a persona starts with its validated descriptor, UI registry, styles, theme and server-only instructions. The core transport and storage do not change. The complete file rail and extension constraints are documented in [ARCHITECTURE.md](ARCHITECTURE.md).
|
||
|
||
## Verify
|
||
|
||
`patches/@tanstack__ai-gemini@0.29.1.patch` fixes the native adapter’s JSON Schema mapping in both structured-output paths. It uses Gemini’s `responseJsonSchema` field, preserving Zod literals and validation; the older `responseSchema` field rejects `const`. It also preserves HTTP error codes through TanStack’s native error utility so quota and availability failures are distinguishable. Wire-level regressions use the real SDK with intercepted HTTP requests. Keep the pnpm patch until an upstream release passes that regression.
|
||
|
||
```sh
|
||
pnpm check
|
||
pnpm test
|
||
pnpm build
|
||
```
|
||
|
||
Public Playwright tests use neutral native provider fixtures and a local preview on port 3000. Default screenshot assertions require separately captured V1 baselines. Explicit `STORYTELLURX_CAPTURE_CURRENT=1` runs the same behavioral assertions and attaches current images without comparing or updating baselines; these captures do not establish visual parity.
|
||
|
||
No push or deployment is part of this refactor. Full parity is claimed only after the reference comparison and behavioral matrix are complete.
|
||
|
||
## Offline streaming review
|
||
|
||
[Streaming layout review and code-first plan](reports/streaming-layout-review.md) links the recorded neutral Flash-Lite response, per-chunk DOM measurements and proposed fixes. Test fetch is denied by default; provider tests must replay fixtures. The separate one-time recording step is never run by tests or CI. The opt-in layout audit blocks unexpected API/external requests and exercises local native TanStack streams. Its current measurements diagnose jumps; they are not approved stability baselines.
|
||
|
||
Turn details use a closed-by-default receipt adapted from REUI `ai-chat-12`, composed with the installed Base UI primitives and StyleX persona tokens. Expanding reveals natural-height state and public explanation content, without inner scroll panels. `public-rationale-receipt.spec.ts` verifies streamed geometry and keyboard disclosure offline. See `reports/streaming-layout-review.md` for the corrected UX and historical measurements.
|
||
|
||
Chat layout follows the shadcn default `MessageScroller.Provider` + user `scrollAnchor` composition, with a static header and flow composer. Suggestions use headless Base UI ScrollArea. See `reports/chat-composition.md` for the source comparison and native behavior; `@shadcn/react` is unpatched.
|
||
|
||
`structured-completion-order.test.ts` checks that native combined structured output completes before its successful `RUN_FINISHED`, plus validation and server-cancellation failures. `@tanstack/ai` 0.61 orders these events upstream, so the former pnpm patch is gone.
|
||
|
||
App-owned images and fonts live in `src/apps/landing/assets`. Import images from TypeScript or reference fonts with relative CSS URLs so Vite emits content-hashed `/assets/` URLs. These built files receive `Cache-Control: public, max-age=31536000, immutable`. Keep fixed-name manifest resources in `public/`; they do not receive this immutable policy. The page imports its own favicon, while the fixed public icon remains for `public/product/manifest.json`.
|
||
|
||
Do not put private or generated user documents in `public/`, the client build output, or imported app assets. Serve them from an authenticated server route. Generated responses retain the default `Cache-Control: private, no-store`; keep them outside the explicit anonymous HTML cache allowlist. A file extension such as `.pdf` or `.jpg` does not opt a server response into shared caching.
|