Not in “Skill” format, just a single doc as below. In case helpful.
Web App Construction Handbook
Distilled from the complete iteration history of this user’s four shipped projects (content/visualization, tool, and game × 2): general requirements and hard-won lessons that apply to this user’s projects.
Goal: let this user’s first prompt describing a prototype idea iterate along the shortest possible path into a polished, publicly launchable, production-grade application.
Part 0 · How to Use This Document
- This document should be treated as an implicit part of the first prompt for every new web app project by this user. At the start of a new project, the AI assistant should read it in full to absorb the user’s preferences and historical lessons.
- The document has three tiers of authority, each read differently:
- Part I, Core Invariants — the strongest constraints; they hold in any form factor, at any stage. In long conversations, prioritize remembering this section.
- Part II, Consolidated Preferences — a collection of best practices from past projects. When one doesn’t apply to a new subject or platform, or a better practice exists, simply state the reason for deviating; there is no need to ask the user’s permission.
- Advice for specific situations — use as needed:
- Part III, Stage Checkpoints — consult at kickoff and at deployment.
- Part IV, Pitfall Memo — a checklist of factual knowledge.
- Part V, Detailed Conditional Appendices. Triggers include: React tool-type apps, needing a backend, LLM-driven features, content pipelines at scale, PWA, and data visualization.
- If this document conflicts with the user’s own statements or with an existing in-project AI memory document / design document, the user / project side takes precedence. If you suspect the user has made a mistake, discuss it with them; if you suspect a project document is wrong or outdated and it carries no deliberate-decision annotation, follow this document and record the conflict in the handoff notes.
- Once you understand the new project’s requirements well, you may selectively copy the relevant content below into the project’s in-repo AI memory document to reduce dependence on this file.
Part I · Core Invariants
Architecture & Data
- Input is the single source of truth: derived data (computed results, sort ranks, formatted text) is never written to the store — it is always derived passively on the read side, so any change to an input automatically recomputes everything downstream, eliminating stale state. Performance-motivated caches must be verifiable and have explicit invalidation conditions.
- Core logic is headless and testable: engines, parsing, math, and store actions are pure logic that runs directly under node/vitest, with no browser.
- Errors surface in the UI and are isolated per item: a
console.warnand moving on is the same as swallowing the error; wrap each of N items in its own try/catch so one failure doesn’t take down the whole batch; all network access goes through a single API layer with status checks, parse validation, and error normalization. An app-level ErrorBoundary / global error handler is the backstop: uncaught errors show a recoverable UI, never a white screen. - Secrets live only on the server: API keys and hidden prompts go in server-only directories that are physically unable to enter the client bundle; the deploy pipeline automatically scans build artifacts for leaks — never rely on self-discipline.
- The build system maintains itself: automatic file discovery (copy whole directories; generate manifests by scanning build output) — never hand-written enumerations; the version number has a single source of truth (
package.json, injected everywhere at build time); cache invalidation is automated. “Remember to update it in two places” is an architecture bug. - All paths are relative: use
./from the very first line of code;dist/must be droppable, as-is, onto any static host. - Turn errors into feedback, not exceptions: when unreliable participants are involved (models, user input, third-party data), a validation failure is fed back to its producer as semantic feedback (the model re-decides; the user sees an inline explanation) — no interruption, no silent failure.
- Splitting discipline: start splitting with the very first feature after “it’s playable” (minimum four pieces: state/logic, rendering, input, data loading); any file over 400 lines with mixed responsibilities gets split; god objects are forbidden.
- Debug backdoors are “off by default in production,” not “remember to turn off before production”: double-gated by an explicit request declaration plus a server-side environment toggle — no toggle configured in production means deny by default; UI entry points are hidden via
import.meta.env.PROD. Dev/prod configuration uses runtime detection or build-time injection; manual “switch to production” commits are forbidden.
Interface
- UI state transitions must be instantaneous: a user action gets immediate visual confirmation; visual feedback arriving more than 200ms after a click is a bug. The root cause is usually coarse-grained full re-renders; the fix is derivation plus fine-grained subscriptions.
- Appearance parameters centralized in one place: all colors, fonts, font sizes, spacing, radii, shadows, z-index, and animation durations/easings are defined as CSS custom properties in a single entry point; hard-coded colors inside components are forbidden. The specific values and curves are matters of taste (→ Part II, aesthetic taste), but they must “live in tokens.”
- No emoji as UI icons: proper icons are always inline SVG.
- Accessibility baseline: aria-label / aria-expanded / role; icon-only buttons must have an aria-label; focus management; touch targets ≥ 44px;
documentElement.langkept in sync with the UI language; animations respectprefers-reduced-motion; every modal, starting with the very first one, ships the trio of focus trap + Escape to close +role="dialog"/aria-modal/aria-labelledby. - Unavailability must be discoverable: when a control is unavailable — whether you choose to disable it or hide it — the user must be able to learn why. Disabled controls need a nearby hint; progressive-disclosure hiding needs a guiding entry point. Button labels change with context.
- Destructive actions (delete, overwrite) must pass a confirmation, or be protected by reversibility (undo).
- Hints must be reachable on touchscreens: every tooltip / hint must have both a touch trigger path and a dismissal path (tap to toggle, long-press, tap elsewhere to close — any of these is fine).
- Usable on mobile from day one: mobile/touch gesture support is in place from the prototype stage; pixel-level polish belongs to the real-device testing round — a planned phase, not unexpected rework.
- Bilingual is a first-class citizen: Chinese by default, with one-tap instant switching to English; key symmetry between languages is guarded by tests; English copy is never inlined into code.
Part II · Consolidated Preferences
Collaborating with AI Assistants
- The first time you generate an AI agent memory document (either CLAUDE.md or AGENTS.md), symlink it to the other. Any memory goes into that document first.
- Code comments explain only the why (constraints, pitfalls, the reasons behind counterintuitive choices) and never restate what the code does; naming is semantic and the DOM structure readable — the code will be edited in relay by multiple AI tools, so readability is interoperability.
- Handoff notes go under
docs/notes/, always with the same three lists: “Where I improvised,” “What I’m not sure about,” and “Left for you.” Writing down deviations from the plan and points of uncertainty is more valuable than writing down what went right. - Overrides must leave a trail: whenever you override a historical preference from Part II, record “what was overridden + why” in the project’s agent memory document, so that AIs picking up the relay don’t mistake a deliberate choice for an oversight and revert it.
- Plan document lifecycle: plan → implement → move to
docs/archived/for the record, never delete; after each milestone, write a retrospective grounded in the real commit history; lessons flow back into this handbook and the memory document. - Claude Design frontend iterations are a planned phase: on the code side, first ensure appearance tokens are centralized in one place; when restyling, write down the deliberate design that must be preserved (responsive branches, the existing token system, bilingual constraints) as explicit constraints handed to the design round; projects expecting design iterations get a
build:singlescript (single-file HTML build). - Leave debug interfaces for the AI: a text serialization of core state, and standalone debug scripts for a single level/entry — so the AI can validate hypotheses quickly without going through the UI.
Tech Stack & Engineering
- Common base: Vite + TypeScript strict + Vitest; deployment on Cloudflare Pages (pushing to
mainauto-buildsdist/; satisfies accessibility from mainland China); Node version pinned with.nvmrc; data/content stored as human-editable plain text (CSV/JSON/Markdown), inlined or transformed at build time. - Support baseline: Chrome/Edge 111+, Firefox 128+, Safari/iOS Safari 16.4+ (supports
color-mix()). - Form-factor selection criterion: is there a set of “many interactive units that look alike, each with its own internal state” (cards × N, slots × N, multiple tabs, long-term evolution)? If yes, use the React stack (→ Appendix A); if it’s one main canvas plus a few floating panels, use vanilla DOM, pulling in small, focused libraries only where specialized capability is needed (D3 subpackages, marked, GSAP, Howler).
- Default to pure static: no backend, no external APIs, data bundled into the artifact at build time, works offline, zero external requests. If a backend is genuinely needed → Appendix B.
- Default to zero collection: pure static projects include no analytics or error reporting, and this stance is stated explicitly in the README and the privacy policy. LLM-driven projects are the exception: they require full logging.
- Any platform-dependent services (ads, payments, haptics, platform SDKs) are isolated behind a Provider pattern: a unified interface plus runtime platform detection, defaulting to no-op on the web.
- CI: GitHub Actions runs
npm ci → lint → build → test, with Node reading.nvmrc. The key lesson is to write system boundaries down explicitly — CI only checks and doesn’t deploy; the hosting platform only deploys and doesn’t test; if you want a gate, set up branch protection separately. The one-liner for “reproduce CI locally” goes in the docs. - Browser acceptance testing uses Playwright with screenshots as deliverables; game-type projects carry dual test suites (rule unit tests + full-game replays), all runnable with a single
npm test; Python helper scripts get pytest.
Architecture & Data Flow
- Split stores by lifecycle: session state and persistent state (user content, theme, language, settings) live apart; persistent state uses localStorage persist, with non-persisted fields declared explicitly (partialize); deliberate drops get a comment — no silent drops.
- Saves use a single versioned snapshot, atomically written to a single key, storing only progress that cannot be recomputed (static data is rebuilt from source definitions). Don’t let each variable write its own key — you will end up persisting internally inconsistent state.
- Proportionate component communication: props/callbacks between parent and child are the normal means — no need to detour around them; sibling components across the tree must never reference each other’s instances or DOM directly; shared state goes through the store or events.
- Pure-logic engine, event-stream-driven presentation: the rules engine never touches the DOM; it emits semantic events (MOVE / CAPTURE / EFFECT…); the frontend subscribes to those events to drive animation and sound — logic and presentation evolve independently.
- Data-driven rules: for games or complex simulation systems, put rules, numbers, and combination tables in
data/*.jsonwherever possible; the code is only an interpreter. Adding new content = editing data, not the engine; internal identifiers are decoupled from display names. - Core state should have a human-readable text serialization (e.g., an ASCII board): debugging, logging, test assertions, and troubleshooting alongside an AI all depend on it.
- Different truths of the same concept get different types: server truth / client cache / display state each has its own type, so the compiler prevents mixing them.
- Multiple sources converge on one intermediate representation: hand-authored content and algorithm/model output are both translated into the same command structure and run through the same execution pipeline — so upstream sources cannot possibly diverge in behavior.
Layout
- Immersive-canvas type (content / visualization / games): the main content fills the viewport; functional UI is a set of floating overlays (header, side panels, bottom toolbar, detail cards); the content canvas supports drag-to-pan, pinch/wheel zoom, and double-click reset; floating panels do not intercept gestures outside their own bounds; at extreme aspect ratios there is no overflow and no large dead zones.
- Tool-application type: header (logo + tabs + settings) + a main workspace grid. Information density is a virtue — on desktop, don’t be afraid of a high-density grid that fits everything on one screen, with tabular-aligned digits and tight line spacing.
- Desktop and mobile are two deliberate designs (high-density grid ↔ snap carousel; bottom toolbar ↔ bottom sheet). Preserve them as deliberate design during refactors — no “unifying them while you’re at it.”
- In canvas/visualization form factors, secondary panels are collapsible and collapsed by default to reduce first-view noise; collapsed/expanded state and selection highlights survive re-renders.
- Empty states are designed: slots/cards without content get guiding placeholders — otherwise the first impression reads “half-finished.”
- The stack-based ModalManager (history integration, back button closes the topmost layer, scrollbar follows the theme) is introduced only when a second modal that can coexist with the first appears — an app with a single confirm dialog does not warrant that machinery.
Visual Design
- Semantic token layering: surfaces
base → surface → elevated → hover → overlay; bordersborder / border-subtle / border-hover; text primary/secondary/tertiary plusmuted/dim/faint; brand: a singleaccentplusaccent-mutedandfocus-ring. - Multi-theming = shared variables in
:root(layout, z-index, durations) plus one wholesale override block per theme ([data-theme="x"]);color-schemefollows; Tailwind projects bridge via@theme; JS/Canvas reads lazily viacssVar(name, fallback). Adding a theme = adding one variable-override block. - Accent restrained but not timid: one brand color for the whole app, used only in a few signature spots (logo, active tab, focus ring); hierarchy is created with surface/border/text tokens instead.
- Three-font system:
--font-display(logo, big numbers, climactic moments — only 3–5 places app-wide) /--font-sans(body) /--font-mono(numerals, code); each family is a CJK + Latin pairing with matched weights and metrics, a complete CJK fallback plussystem-ui; numeric contexts always usefont-variant-numeric: tabular-nums. - Fonts strictly self-hosted or system stack, zero external requests; if a CDN is used, there must be a complete system fallback and graceful degradation when offline. For apps whose text set is stable, subset CJK webfonts at build time (can shrink size by two orders of magnitude); the subsetting script goes into the build pipeline.
- Temperament baseline: overall restrained, print-like — small radii, light shadows; light theme by default with a switchable dark theme, and dark is never pure black; the subject matter decides the temperament; design rounds set the direction from scratch.
- Neutral, non-emoji Unicode glyphs (▼ ✎ ⋮⋮) are allowed as a light design vocabulary, but each new one gets a real-device check to confirm it doesn’t render as emoji; prefer “icon + text” side by side.
Interaction & Animation
- Time-based animation uses rAF with continuous floating-point interpolation, 60fps with no jumps; time parameters adapt to playback speed; parameter tables and interpolation are written as pure functions and unit-tested; durations and easings live in tokens (
--duration-*/--ease-*), with the specific curves chosen per subject — scenes better served by bounce/elastic need not defer to the default curve. - Continuous animation, interruptibility, and logical consistency are all aligned to the same discrete boundary: execute unit by unit (① change the logical state first → ② then start the tween → ③ check for interruption only between units); entities never rest in an intermediate state; next-step decisions / network requests fire in parallel while the current animation plays — latency is hidden inside the animation’s duration.
- Complex choreography (game type) may use GSAP: timeline reuse plus object pools; do not create and destroy per invocation.
- Add gesture shortcuts for high-frequency operations (double-tap to cancel, long-press, etc.), handling races with single-tap; lists/slots support drag-to-reorder.
- Default states are chosen with care (default language, default range, sensible initial example data); obscure terms get a small “?” marker showing a one-sentence explanation on tap/hover.
- Sound and haptics (default for games, optional for tools): a unified SoundManager with a centralized asset manifest and preloading to cut latency; volume/mute persisted; each sound effect hangs on exactly one trigger point (the single entry point of that interaction); continuous actions use “loop + keep-alive, auto fade-out on stop”; haptics is a user-toggleable setting, isolated behind the Provider pattern.
Taste Convergence
- Summarized from history; not necessarily suited to the current project. Deviating from this section needs no paper trail.
- The light theme has repeatedly converged on “warm paper”: a parchment ground, a warm-ink text hierarchy, low-saturation earth tones, warm amber shadows — “paper resting on paper, not ice sitting on snow.”
- Dark themes have converged along two paths: a cool deep-navy family, and a warm ink-black + amber family; surface hierarchy steps up through lightness plus a touch of chroma.
- Three texture techniques: dual-layer shadows stored in CSS variables (a soft ambient shadow + a tight key shadow + a 1px inset top-edge highlight, deepened on hover); a low-opacity SVG noise underlay to kill the plastic feel; very faint radial/linear gradients as a directional light source without reading as “gradient UI.”
- Category/faction tints use
color-mix()gradients rather than flat translucency, never fading all the way to the background color; supplemented by a thin accent bar along the card’s top edge. - Motion taste: default easing is smootherstep / easeInOutCubic; panel expand/collapse transitions run about 160–320ms; override directly when the subject calls for elastic/bounce.
- Collapse-control vocabulary: directional chevrons (pointing where the panel will move), not directionless symbols like +/−.
- Custom scrollbars (including inside modals) with colors adapted to the theme.
- Content type may pair “serif headings, sans-serif body”; tool type is all sans-serif; game type may use calligraphic/decorative display fonts per subject (display slots only); leave a font-experiment switch in
theme.css(a class on<html>swaps font combinations). - Game type may ship multiple complete themes — provided tokenization keeps “adding a theme = adding one variable-override block.”
Mobile & Performance
- Thumb reachability: high-frequency control panels must not sit at the bottom of long scrolling content — on mobile, switch to a bottom sheet or a pinned bottom toolbar.
- In high-frequency gesture event callbacks, only record state, and apply it in rAF-coalesced frames; during gestures over large bitmaps, pan/zoom with GPU CSS transforms (
will-change: transform) and redraw the precise frame afterwards; add a temporary class during gestures to switch off expensive effects like shadows and blur. - Bitmap resolution is decided by DPR plus a pixel budget; heavy computation is chunked to yield the main thread.
- The initial load may do one-time heavy lifting, but off the critical path; render the skeleton first.
- Reducing request count is a build-time job: CSS merging, JSON bundling,
<script defer>— all automatic in the build pipeline; artifact size is consciously controlled (basemaps/data/fonts trimmed to what is actually needed).
Internationalization
- Language initialization priority: the user’s saved choice > browser language > default.
- Implementation follows the stack: vanilla projects use dictionary JSON plus
t(key); React projects use i18next; flat semantic keys; dictionaries split by domain (UI copy and content entry names in separate files). - Proper nouns get an explicit mapping table (official translations); apply loose normalization before lookup (as-is / lowercase / strip spaces / hyphen–space interchange); fall back to the original text when not found.
- Bilingual fields in content data use
_zh/_ensuffixes, read according to the current language. - Long documents (README / help / legal) are separate documents per language, at “fit for publication” quality.
- Number localization done thoroughly: magnitude words (万/亿 vs. k/M), unit conversion, trailing-zero trimming, decimal places chosen by magnitude.
Data & Content
- Upstream/legacy sources go through a build-time transformation pipeline: scripts hooked to
predev/prebuildrun automatically, cleaning and remapping into a clean JSON schema; each script ends with minimal structural validation (required fields, statistics on dropped items) and fails with a clear error rather than crashing partway through. - In-app explanatory copy renders the README directly (inlined at build time) — the docs are the copy; never maintain two versions of the same statements.
- Content/data projects provide a raw-data download button (Blob export, correct filename, license notice attached).
- Important user-created data must have an exit: localStorage is a cache, not storage — one clearing of site data and it evaporates. For any persistent content produced by user labor (settings combinations, notes, custom levels, drafts), provide JSON export/import; exports carry a schema version; imports must handle save-version migration — silently failing or half-parsing an old save is forbidden. For tool-type apps, this item goes on the pre-release checklist.
- Rendering is sanitization: any non-hard-coded text rendered through the HTML pipeline (UGC, URL parameters, model output) is treated as untrusted by default; sanitization funnels into a single point at the render entry, never scattered across components. Projects that render UGC ship a CSP header.
Note: the content of Parts III–V below sometimes lives in a separate document, so if this document appears truncated at this point, that is normal.
If it is truncated and you need the sections below, search the same directory as this document for the keyword
Part III · Stage Checkpoints.
Part III · Stage Checkpoints
Before the first commit
-
.gitignorein place (including.env,.env.*,.dev.vars, logs, and intermediate artifacts). - Token file (
variables.css/theme.css) created, with layering in place. - Relative paths from the first line onward.
- Mobile baseline in place.
- Automated checks for the architecture invariants wired into an npm script.
Before release
- Project name / studio name / brand name finalized, with document field replacements verified; projects headed for a store draft a privacy policy and terms of service.
- Bilingual/multilingual key system in place.
- Real-device testing round, plus a pass on a low-end device (or a GPU-throttled emulator): smooth on desktop ≠ smooth on a phone.
- Emoji audit: grep the diff for emoji code points.
- favicon, app icon, and apple-touch-icon all present (users will “Add to Home Screen”); custom scrollbars (including inside modals) colored via theme tokens.
- SEO / share metadata: every shareable entry point has a title / meta description plus
og:title/og:description/og:image(1200×630); bilingual sites get hreflang; content sites get a sitemap. - Production-form rehearsal: run the packaged artifact under the platform’s local simulator (e.g.,
wrangler pages dev dist); do not validate only on the dev server. - Deploy script = quality gate: lint + test + build + artifact secret scan must all pass before upload, completed in a single command.
- Documentation complete:
README.md(screenshots, live link, technical highlights, license; no leftover scaffold boilerplate);LICENSE(code under MIT; self-built data/content under CC-BY 4.0, with an in-app credits entry); deployment docs self-contained (first deploy + routine updates + smoke-test curl commands with expected output; one section per channel; platform gotchas recorded as they are encountered); a quick reference of common commands; the CLAUDE.md/AGENTS.md (symlinked) documents must be maintained. - Version discipline: bump the
package.jsonversion at each milestone, inject it at build time, make it queryable in the UI; maintain RELEASE_NOTES. - CI green + deployment confirmed live.
Part IV · Pitfall Memo
A list of mistakes not worth paying tuition for twice. In no order of priority.
- Mobile browsers render code points like ▶ ⏸ ↻ as colored emoji. Even neutral glyphs can be hit, hence a real-device check for every new one.
backdrop-filter: blur()is a frame-rate killer on low-end Android: use it only when visible content actually sits behind the blur; if there’s just a solid color behind it, delete it — the difference is invisible on desktop.- CSS transitions and JS animation fight each other: set
transition = 'none'before JS takes over, and restore it afterwards. - AudioContext must be resumed on the first user interaction, or it stays silent.
- Native
titletooltips only work for mouse users; touchscreen users never see them. - The store detects changes by reference: updates to collection/array fields must pass new objects; in-place mutation does not trigger subscriptions.
- Particle/looping animations must early-exit when idle, or they’ll spin every frame with zero content and drain the battery.
- A PWA cache-first strategy will serve users stale code after a deploy — HTML/JS/CSS must be network-first.
- LLM API message-structure constraints: the first message must be from
user, roles must strictly alternate, and adjacent same-role messages must be merged; the model all but ignores “its own previous turn buried inside user text” and will keep repeating itself. - The gotchas of routing, function directories, and secret injection only surface when running the packaged artifact locally (e.g.,
wrangler pages dev dist); everything working on the dev server does not mean production works.
Part V · Conditionally Triggered Appendices
Appendix A · React Tool-Type (trigger: form-factor selection lands on React)
- Default stack: React + Zustand + Tailwind (CSS-first) + i18next +
vite-plugin-pwa(→ Appendix E). - Derivation via
useMemo/ selectors; solve performance problems first with fine-grained subscriptions, withReact.memo/useCallback/ engine objects cached by key as auxiliary means. - Extract pure logic out of large components into
lib/pure functions, then test it there; use jsdom + Testing Library only for a small number of structural tests. - Any UI structure repeated 3 times (confirm dialogs, etc.) gets extracted into a shared component; when in-component logic balloons, extract a hook.
Appendix B · When a Backend Is Needed (trigger: community content, leaderboards, model gateways, or other needs that cannot stay pure static)
- Prefer stateless functions on the same platform (Cloudflare Pages Functions / Workers, with D1/R2 as needed): same origin as the frontend, same single deploy (no CORS, no separate domain); pick a portable framework (e.g., Hono: runs on both Workers and Node).
- The server is stateless; session state is held by the client; skip the database if you possibly can.
- Secrets are injected via the platform’s encrypted secrets and live only in server directories (Invariant I-4).
- Anonymous IDs without accounts; the server re-validates everything (never trust the client); per-user quotas plus cooldown rate limits; platform-side rate limiting (per-IP thresholds, no code changes needed) goes on the pre-launch checklist; hard caps on request body size and field length — a public, paid endpoint will be hammered.
- Edge/Worker runtimes prefer zero dependencies: native
fetchover vendor SDKs, thin hand-rolled wrappers over big libraries — in a small project, every dependency is future build/compatibility burden.
Appendix C · LLM-Driven Features (trigger: the app contains model-driven participants / assistants / generators)
Overarching principle: the deterministic shell (architecture invariants, loop guards, offline fakes, secret gating, observability) must be built early and built hard; model behavior quality (prompts, state representation) is an iteration of repeated playtesting with a real key — treat the two separately and validate them separately.
- Apply the secrecy criterion item by item, not by gut feel: server-side = anything whose exposure would break the product’s core / cost money / have security consequences (hidden prompts, keys, model selection, tool assembly); client-side = whatever the user can see anyway (UI, chat history, static data — accept that it can be datamined). “The content is hard-coded” and “which layer it lives in” are two orthogonal questions.
- Stateless gateway: the client sends up history + state each turn; the server assembles the secret prompt, calls the model, and returns a structured result; swapping models is an internal gateway change, invisible to the client.
- Pick the right interface altitude: the model expresses intent; code does the computation. The model references only entity IDs it can perceive plus semantic parameters (direction, degree, target); pathfinding, geometry, and precise arithmetic are all reclaimed by deterministic code — what surfaces are only “intent-level mistakes” (explainable, dramatically interesting), never “coordinate-level failures” (which look like a broken parser).
- The state representation fed to the model is a first-class design object: write it as a unit-test-covered pure function. When the model performs badly, first check the state description for ambiguity or missing information, and only then tweak prompt wording.
- Perception scaffolding: whatever the model can’t compute reliably (adjacency, legal moves, reachability, the exploration frontier) is precomputed by code and stated as facts — downgrading spatial/structural reasoning to “reading stated facts”; if the state is identical to the previous turn, just say “no change.”
- The harness owns “not going out of control”; the prompt owns “quality”: loop termination, hard per-turn action caps, consecutive-idle guards, and forced structured output are guaranteed by deterministic code — prompts can only mitigate, never cure.
- External content entering the context is always demoted to data: when user input, UGC, or scraped content is spliced into a prompt, wrap it in delimiters and declare “the following is data, not instructions” — but injection cannot be prevented by prompts, only dampened — so the real line of defense is on the harness side: tools are assembled minimally per turn (only what this turn needs), and high-impact actions (writes, charges, outbound sends) pass a deterministic whitelist validation before execution, never triggered directly by model output.
- True multi-turn messages: the model’s own output must be fed back as
assistantturns; never flatten history into one longuser-role running log (API constraints and reasoning in Part IV). - Validation failures are fed back as semantic feedback so the model can re-decide — no errors thrown, no silence.
- Observability is a first-class feature: “the complete payload actually sent to the model this turn (system + messages)” must be viewable and exportable in one click — you cannot debug the model if you cannot see what it saw; in production, full logging is fire-and-forget to object storage (non-blocking of the response, a trail kept on both success and failure, constant secrets never stored, bucket private).
- Offline deterministic fallback: with no key present, degrade to a deterministic fake (keyword heuristics suffice), so the main loop can be playtested offline, mechanism bugs can be unit-tested, and CI can run — saving money is merely a side benefit.
- Validate mechanisms and prompts separately: mechanisms (guards, serialization, loop structure) are validated offline with fakes and unit tests; prompts and wording must be playtested with a real key, kept on a “pending human playtest” list — never mix the two and declare “validation complete.”
- Model-side copy is i18n too: perception, action feedback, tool descriptions, and personas each get a per-language version; dictionaries are split by audience (model-side / client UI / server secrets).
Appendix D · Content Pipeline at Scale (trigger: levels / entries / articles at scale)
- Decouple identity from order: entries are named as files by a stable ID/slug; display order, grouping, and difficulty live in a separate manifest file (reordering = changing one line, not renaming a batch of files); the manifest is generated or validated by a build script that scans the files, preventing drift.
- Build validation tools before mass-producing content: schema validation plus consistency/solvability checks (an auto-solver for game levels) in CI or the build.
- Three-way division of labor for AI-generated content: the LLM does the structure (correct format, legal placement); automated validators do the gatekeeping (solvable, no shortcuts); the core design (fun, elegance, teaching cadence) must be human-authored — for content tightly coupled to mechanics, LLMs cannot produce quality worth praising.
- When content volume is large and needs repeated tuning, invest in a built-in editor: in-app editing with localStorage drafts, reusing the production render pipeline, WYSIWYG.
- UGC: client-side validation before upload plus server-side re-validation; quotas and cooldowns per Appendix B.
Appendix E · PWA (trigger: enabled by default for tool-type / offline game-type)
- The Service Worker caches all static assets; the app is fully usable offline; the cache list is generated from the build manifest, never hand-written; cache names carry a version number that is auto-bumped at build time.
- HTML/JS/CSS are always network-first; cache-first is safe only for immutable assets (versioned filenames, fonts, images) (reason in Part IV).
- Write the manifest in full: name / short_name / theme_color / display plus 192/512 icons;
start_urluses"./", not a specific HTML file. - Acceptance criteria: loads normally offline in Chrome; the “Add to Home Screen” experience is complete.
Appendix F · Data Visualization (trigger: visualization form factor)
- Hard rules of data encoding: when area encodes a value → radius uses a sqrt scale; heavy-tailed distributions always get a log scale, with the color-scale domain starting at the smallest positive value; interval-type values center on the mean/median, with min/max expressed explicitly via dashed circles or error bars; the same metric uses the same color scale and the same y-axis scale across all linked views; no data ≠ zero (fixed small size + neutral color + distinctive stroke + annotation); the legend range = the true min→max, not an assumed [0, max].
- Multi-view linkage: hover/selection of the same entity syncs bidirectionally across views; filters and time windows apply to all views simultaneously.
