AGENTS.md@packages/deck3d/src/serve · git:20260929.4aa0939 · 2026-09-29 · sha256 1f0ba71c48066b98

AGENTS.md@packages/deck3d/src/serve git:20260929.4aa0939A

Immutable. This exact content never changes and is served at /api/v1/blob/1f0ba71c48066b98.

# DOX — packages/deck3d/src/serve

Watch-and-rebuild authoring server (`deck3d serve`). See change: deck3d-cinematic-worlds.

| File | Purpose |
|------|---------|
| `AGENTS.md` | This file. |
| `index.ts` | `startServe(mdPath, opts)`: HTTP authoring server on `127.0.0.1` (`opts.port`, 0 = OS free port). Watches `<deck>.md` + `fx/` via `fs.watch`; `<deck>.json` is POLLED instead (`JSON_POLL_MS` 150 ms sha256 digest) because macOS delivers its watch events seconds late and every rebuild rewrites that file — the digest is both change detector and self-write filter (`jsonDigest`, set in the rebuild `finally`). Debounces rebuilds (`opts.debounceMs`, default 120 ms). Rebuild runs `parse`, re-pins local fx `sha256` in `<deck>.json`, renders to scratch file, writes `<deck>.html`, injects reload script into served copy only. Preserves slide via `location.hash` and staged tuning via `localStorage`. Rebuild failure keeps serving last good deck and emits `error-report` SSE overlay. Exposes SSE at `/__events` (`rebuild`, `ok`, `error-report`, `findings`). POST `/__overrides` writes `overrides.json` beside deck. POST `/__apply` deep-merges overrides into `<deck>.json` and rebuilds. Confines write targets to deck directory (`safeTarget`). Validates payloads via `validate()` before write; invalid payload returns 400 and leaves file byte-identical. Caps request body at 1 MB. `serveAsset(pathname, deckDir, res)` serves media beside the deck (mp4/webm/png/jpg/jpeg/webp) same-origin — required because a `file://` clip taints the canvas and cannot become a WebGL texture. Path confined to deck dir: `new URL()` collapses plain dot-segments but leaves `%2f` ENCODED, so `/a%2f..%2f..%2fsecret.png` decodes into a real traversal and returns 200 without the check (see `__tests__/f47-asset-route.test.ts`). Runs out-of-band `runCheck` on rebuild when `opts.check` set. `ServeOptions.run` is the CLI entry INJECTED by `cli.ts` — `serve` drives `parse`/`render` through it and must never import `cli.ts` back (that cycle left `cli.ts`'s top-level await pending and exited 13; pinned by `__tests__/serve.test.ts` #S10). Shared types live in `src/cli-io.ts`. `ServeHandle`: `url`, `host`, `port`, `htmlPath`, `settled()`, `lastError()`, `findings()`, `close()`. See change: deck3d-cinematic-worlds. |
| `__tests__/serve.test.ts` | Vitest suite for `deck3d serve`. Asserts watch rebuild on source edit, local effect edit rebuild + hash re-pin, last-good deck retention on invalid edit, loopback binding refusal for non-loopback, reload client injection into served HTML only, POST `/__overrides` and `/__apply` directory confinement, pre-write IR validation refusal, 1 MB payload cap, out-of-band check findings emission, out-of-band `deck.json` edit rebuild with no self-trigger storm (#S12), and that the SPAWNED binary stays up (#S10 — in-process `startServe()` never evaluates `cli.ts` as entry, so it cannot catch the ESM-cycle exit 13). `servedPalette()` parses `window.__DECK` rather than substring-matching the html: the runtime bundle contains "remember", so `toContain("ember")` passes even when nothing rebuilt. See change: deck3d-cinematic-worlds. |