request:
  artifact: "one SSOT cheat sheet: SVG-ANI-BIBLE.md"
  subject: "Moincraft SVG Animator v3"
  outcome: "make architecture, operation, editing, audio, export, verification, release, and recovery obvious"
  format: "ultra-condensed; complete; practical; precise; emojified; tables/checklists/maps; direct URLs; no filler"
  safety: "publish no secrets; label optional capabilities; never invent deployment state"
  proof: "facts grounded in the current source, release records, and completed verification"

🎬 SVG Animator Bible

One page, one truth: a static-first storyboard studio with deterministic project/render/export behavior and an optional same-origin audio sidecar.

🧭 IdentityValue
Project directorymoincraft-images/svg-animator (relative to the workspace root)
Canonical schemaversion: 3
Stage1600 Γ— 900
Current release token20260803-perfection-v3
SSOTssot.moincraft.de
Catalog entrymoincraft-images/index.html β†’ relative svg-animator/ (both relative to the workspace root)
Public URL/originmoincraft.de/images/svg-animator/; sidecar origin gate uses AUDIO_SERVICE_PUBLIC_ORIGIN=https://moincraft.de.
Static-runtime contractexactly 6 files
Optional servicePython 3.11 aiohttp audio sidecar on loopback 127.0.0.1:8787; deliberately not installed in production

🧭 Active responsibility boundary

This UI/UX workstream ownsOwned elsewhere β€” coordinate, do not duplicate
information architecture, recovery and project-lifecycle flows, controls, responsive shell, accessibility, focus/keyboard semantics, visual polish, operational UI feedbackrendering/processing internals, montage or audiovisual export, media-processing pipelines, direct timeline/media manipulation

Keep the boundary explicit in every plan and handoff. UI may expose stable capabilities supplied by the media workstream, but must not independently redesign or reimplement them.

⚑ 60-second operator path

cd moincraft-images/svg-animator
npm ci
npx playwright install chromium
npm run build
npm run check
npm test
npm run test:browser

Sidecar setup/test (optional):

cd moincraft-images/svg-animator
python3.11 -m venv .venv-audio
.venv-audio/bin/pip install --require-hashes -r audio-service/requirements.txt
.venv-audio/bin/python -m unittest discover -s audio-service/tests
.venv-audio/bin/python audio-service/__main__.py

πŸ›‘ Starting the last command changes runtime state. Production uses the supplied systemd/reverse-proxy templates; do not expose port 8787 directly.

πŸ—ΊοΈ System map

flowchart LR
  U[πŸ‘€ Author] --> H[index.html]
  H --> S[studio.js]
  H --> C[studio.css]
  S --> E[engine.js]
  S --> R[project-history.js]
  S --> A[asset-cache.js]
  E --> J[Project JSON v3]
  E --> X[Silent standalone SVG]
  A --> I[(IndexedDB images + audio)]
  S -. optional same-origin .-> P[/api/audio/*]
  P --> T[Edge TTS]
  P --> F[Freesound API]
  S -. fallback .-> B[Browser speechSynthesis]

πŸ“¦ Ownership: edit the owner, not a copy

ConcernOwnerContract
Data model, migration, evaluator, SVG exportengine.jspure/deterministic project behavior
UI, commands, transport, provider orchestrationstudio.js, index.htmlzero-word icon shell + accessible names
Undo/redo and snapshotsproject-history.jsimmutable revisions; UI state is separate
Image/audio cachingasset-cache.jsversioned keys, quotas, deterministic eviction
Authored stylingstudio.source.cssedit this, then generate studio.css
Generated stylingstudio.cssruntime artifact; never hand-edit
Icon spritescripts/build-icons.mjs + locked manifestgenerated into index.html; no CDN
Release tokenmarked sites + stamp/check scriptsone coherent token across URLs/imports/cache
Audio boundaryaudio-service/optional, loopback, same-origin proxy only
Release recordsdeploy/runtime manifest, checksums, notices, licenses
Behavioral prooftests/, tests/browser/Node, Python, and Playwright contracts

πŸ“ The six-file law

index.html
studio.css
studio.js
engine.js
project-history.js
asset-cache.js

Everything else is build source, tests, deployment support, optional sidecar, or static assets. npm run check:release rejects token drift, unmarked token sites, external scripts/links, and changes to the six-name manifest; it does not scan the working directory. The atomic manifest-driven deploy must exclude stray files such as *.bak.

🧬 Canonical project v3 β€” pocket model

project:
  version: 3
  title: string
  settings:
    fps: 30
    snap: { enabled: boolean, mode: "frame | custom", intervalMs: "1..1000" }
    defaultSceneDurationMs: "2000..12000"
    audio:
      preferredTtsProvider: "edge | browser"
      fallbackTtsProvider: "edge | browser"
      masterGain: "0..1"
      defaultVoice: { provider: "edge | browser", locale, edgeVoiceId, browserVoiceURI, rate, pitch, volume }
  variables: [{ id, name, type: "text | number | boolean | color", value }]
  cast: [{ id, name, avatar, body: { type, accent }, voice: { provider: "inherit | edge | browser", locale, edgeVoiceId, browserVoiceURI, rate, pitch, volume } }]
  media: [{ id, kind: "sfx | music", title, creator, license, canonicalUrl, attribution, source: { provider: freesound, externalId } }]
  scenes:
    - id: string
      title: string
      durationMs: integer
      background: string
      dialogue: string
      speakerInstanceId: string|null
      narration: { enabled, provider: "inherit | edge | browser", locale, edgeVoiceId, browserVoiceURI, rate, pitch, volume, gain }
      instances:
        - { id, castId, visible, baseTransform: { x, y, scale, flip, z }, baseMovement,
            keyframes: [{ id, atMs, x, y, scale, movement }] }
      audioClips: [{ id, mediaId, startMs, trimStartMs, durationMs, gain, fadeInMs, fadeOutMs, loop }]
πŸ”’ Hard limitValueπŸ”’ Hard limitValue
Imported JSON1 MiBScenes1–12
Cast32Instances / scene16
Variables64Media records64
Keyframes / instance32Audio clips / scene32
Scene duration2,000–12,000 msDialogue/TTS180 chars
Positionx 80–1520, y 360–820Scale0.5–1.75
Voice rate / pitch0.5–2 / 0–2Gains0–1

πŸ”’ Normalization invariants

  • IDs are unique inside their owning collection; dangling castId, speakerInstanceId, and mediaId become safe absence with diagnostics, never fabricated records.
  • Instance order is stable-sorted by finite integer z, then canonicalized to 0…nβˆ’1.
  • Timeline values are finite integer milliseconds. At 30 fps, snap with round(frameIndex Γ— 1000 / 30) β†’ deterministic 33/34 ms boundaries.
  • Same-time keyframe collision: last authored wins. x/y/scale interpolate linearly; movement is stepped; omitted fields inherit.
  • Scene shortening clamps keyframes/clips, shortens crossing clips, removes zero-length clips, resolves collisions, and records one history transaction.
  • Imported Freesound URLs are discarded. Numeric externalId regenerates https://freesound.org/s/<id>/.
  • Media stores identity + attribution onlyβ€”never credentials or binary audio.
  • v1/v2 import migrates deterministically to v3; the pre-migration payload remains recoverable.

🧠 State, undo, and variables

StateLives whereUndoable?
Project contentProjectHistory current revisionβœ…
Selected scene/instance/keyframe/clipUI state❌
Open drawer/sheet, zoom, playbackUI/runtime state❌
Cached images/audioIndexedDB❌

Mutation path: command β†’ one history transaction β†’ normalize β†’ new immutable revision β†’ render. Do not mutate a history snapshot.

Typed variables: text, finite number, strict boolean, validated #RRGGBB color; names match ^[A-Za-z_][A-Za-z0-9_]{0,31}$. A bound scalar stores { variableId }; unlinking bakes its current literal. Resolve {{name}} in dialogue and supported authored fields at evaluation/export time. Rename by ID. Deletion is blocked while referenced; explicit delete + bake current values is one undoable transaction.

🧯 Browser recovery map

localStorage keyContainsClear rule
moincraft.svg-animator.project.v3current v3 autosavereplaced only by explicit New storyboard confirmation
moincraft.svg-animator.legacy-recoveryuntouched pre-v3 sourceexplicit clear legacy recovery action only
moincraft.svg-animator.import-recoverylast imported/unparseable sourceretained as an exceptional recovery source
moincraft.svg-animator.ui.v3non-undoable UI statesafe UI reset

Ordinary v3 autosave resumes without an interruption modal. When exceptional legacy/import sources exist, the recovery dialog exposes one source-specific download per payload; dismissal preserves both sources. New storyboard resets only the active v3 project and leaves exceptional recovery intact.

🎭 Cast, scenes, and timeline

  • Cast owns: identity, explicit DiceBear Adventurer appearance, voice defaults.
  • Instance owns: scene placement, visibility, flip, canonical layer, movement, keyframes.
  • One cast entry may appear multiple times in one scene. Cast edits update every instance immediately.
  • Avatar/body seam is a geometry invariant: head bottom y=-150, neck top y=-166, deliberate 16-unit overlap. Fix generator geometryβ€”never hide a seam with CSS.
  • Referenced cast/media deletion is blocked with exact usage count; destructive β€œremove all references” is distinct and atomic.
  • Voice precedence: scene override β†’ speaker cast β†’ project default. inherit contributes no provider-specific fields.
  • Hybrid timeline: proportional all-scene lane + selected-scene instance/movement/narration/SFX/music tracks.
  • Supported editing: scrub, playhead, snap, zoom, scene duration, auto-key, x/y/scale keys, stepped movement cues, clip timing/fades/loop.

πŸ”Š Audio: capability, not dependency

ProviderCredentialSeekIf unavailable
Edge TTS sidecarnoneβœ… decoded audioauto-fallback to browser speech
Browser speechSynthesisnone❌ reliable mid-utterance seekvisual playback continues; one status announcement
Freesoundserver-only FREESOUND_API_TOKENpreview/clip audiolibrary disabled; narration remains usable
ElevenLabsnot shippedβ€”no fake adapter/no-op

Exact sidecar endpoints:

MethodRelative routePurpose
GET./api/audio/healthprocess readiness only
GET./api/audio/capabilitiesversioned Edge/Freesound availability
GET./api/audio/voices?locale=…Edge voices
POST./api/audio/speechsynthesize narration
GET./api/audio/freesound/searchcapability-gated search
GET./api/audio/freesound/preview/<sound-id>validated numeric-ID preview proxy

πŸ›‘οΈ Sidecar security envelope

  • Bind 127.0.0.1; reverse-proxy only /api/audio/*; no CORS allowance.
  • Reject a present Origin unless it equals AUDIO_SERVICE_PUBLIC_ORIGIN.
  • AUDIO_SERVICE_PUBLIC_ORIGIN defaults to https://moincraft.de; set the exact deployed origin before enabling the sidecar. A mismatch makes speech POSTs return 403 while origin-less health/capability GETs can remain green.
  • Speech POST requires exact JSON content type and X-SVG-Animator-Request: 1.
  • Body cap 8 KiB; response cap 16 MiB; Edge timeout 3 s connect / 30 s total.
  • Defaults: synthesis concurrency 2; synthesis 10/min; search 30/min; capability TTL 30 s.
  • Trust forwarded client addresses only from the loopback proxy. Never publish FREESOUND_API_TOKEN, environment values, preview URLs, or provider credentials.
  • health β‰  upstream health. Use capabilities to answer β€œcan Edge/Freesound work now?”

πŸ’Ύ Cache keys and budgets

AssetKey
Backgroundbg:<backgroundId>:<release-token>
Avataravatar:v3:<release-token>:<sha256(canonicalAppearanceSpec)>
Edge audioaudio:edge:v1:<sha256(canonicalSpeechRequest)>
Freesound previewaudio:freesound:v1:<externalId>:<previewIdentity>

Images retain 48 MiB / 96 entries. Audio uses 256 entries and min(128 MiB, floor(originQuota Γ— 0.20)), or 64 MiB if quota is unavailable; retain β‰₯20% origin headroom and evict deterministic LRU entries.

πŸ“€ Deterministic standalone SVG export

Export is intentionally silent. It evaluates from an immutable project snapshot, resolves variables, preloads required visuals once, emits one root <svg>, and reports omitted narration/audio counts.

Export must be

  • βœ… self-contained: embedded visual assets; no external references
  • βœ… deterministic: identical project + assets β†’ byte-identical output
  • βœ… safe: no <script>, audio, credential, or provider URL leakage
  • βœ… standalone-loadable: valid SVG MIME/document in a fresh frame
  • βœ… honest: reports omitted narration and media clips

Export must never be

  • ❌ a live DOM clone
  • ❌ dependent on current UI selection/playhead/open panels
  • ❌ an audio/video/package exporter
  • ❌ allowed to silently skip a required visual asset

πŸ–₯️ Responsive + accessibility contract

WidthShell
β‰₯1280 pxtop bar + 56 px activity rail + stage + 352 px inspector + bottom timeline
768–1279 pxactivity rail; inspector right drawer; timeline bottom sheet
<768 pxstage-first; bottom icon dock; mutually exclusive full-width sheets; no document x-overflow
  • Persistent chrome is icon-only; authored content, values, status, errors, attribution, and help may use words.
  • Every control needs an accessible name and immediate hover/focus tooltip; never rely on title alone.
  • Escape cancels transport first, then closes the active dialog or sheet in the same keystroke. Focus returns to the opener.
  • Keyboard flows, native/select semantics, focus visibility, reduced-motion geometry, and no horizontal overflow are release blockers.
  • Golden viewports: 1600Γ—1000, 1024Γ—768, 768Γ—1024, 390Γ—844.

⌨️ Keyboard map

KeyAction
Space / Escapeplay-stop toggle / stop + close active overlay
Enter / Deleteadd-or-update keyframe / delete selected clip β†’ keyframe β†’ instance
Ctrl/Cmd+Z / Ctrl/Cmd+Shift+Zundo / redo
Ctrl/Cmd+Sdownload project
Home / Endselected-scene start / end
Shift+Arrowseek one frame
Arrownudge instance 8 px, or selected keyframe one snap step
Alt+Arrownudge keyframe exactly 1 ms, bypassing snap
Activity rail Arrow/Home/Endroving focus

Shortcuts are suppressed while typing in editable controls.

πŸš€ Release runbook β€” stamp once

flowchart LR
  A[Edit owners] --> B[Unstamped preflight]
  B -->|all green| C[Stamp exactly once]
  C --> D[Final build + full matrix]
  D --> E[Two-build byte comparison]
  E --> F[Atomic six-file deploy]
  F --> G[Checksum + public smoke]
  G --> H[Keep previous release]

A. Unstamped preflight

npm ci
npx playwright install chromium
npm run build
npm run check
npm test
npm run test:browser
.venv-audio/bin/python -m unittest discover -s audio-service/tests

B. One-way release action

npm run release:stamp -- <new-release-token>

πŸ›‘ Run once only after preflight. Token format: starts alphanumeric; then alphanumeric, ., _, -; max 96 characters. Do not manually edit marked release sites.

C. Final proof on stamped artifacts

  1. Run the complete matrix again.
  2. Build twice from the same tree; compare all six runtime bytes/checksums.
  3. Regenerate the hand-maintained ledger from the final stamped build (no npm script does this): sha256sum index.html studio.css studio.js engine.js project-history.js asset-cache.js > deploy/runtime-checksums.sha256.
  4. Confirm the four background checksums below are unchanged.
  5. Deploy only the six manifest files atomically to the existing relative route.
  6. Verify deployed bytes against deploy/runtime-checksums.sha256.
  7. Smoke the public studio route and ./api/audio/capabilities when sidecar is deployed.
  8. Retain the previous six-file release for rollback.

πŸ§ͺ Current golden verification ledger

ProofObserved result
Clean dependency installnpm ci passed
Deterministic buildtwo builds; all 6 runtime files byte-identical
Release/syntax checkspassed
Node tests99/99 passed
Python sidecar tests14/14 passed in the retained Python 3.11 environment; a fresh hash-locked install is blocked by an aiohttp==3.14.3 wheel-hash mismatch
Playwright12/12 passed
Responsive matrix4 golden viewports; no horizontal overflow
Accessibility interactionsfocus restoration, keyboard paths, reduced motion passed
Sidecar origin gateloopback probe: foreign origin 403; configured Studio origin 200 audio/mpeg
Production sidecardeliberately absent; optional capability request returns 404 and visual authoring remains usable
Populated export7 assets; first run 7 misses/7 writes; second run 7 hits; bytes identical
Standalone export proof8,653,449 bytes; root SVG; no parser errors, scripts, audio, or external refs

Runtime SHA-256:

617e1f83bf7ca5ffd3b6a97bddfd8880d9f7504b9f41fe9c8ce10e4fccf74de2  index.html
6ab329fa377004ee0e81467c44e2f3b93f0f8e966292446b06d3206d0c085f4a  studio.css
f11120720781697573230810304cf9a12ccb0235af250da521cf8766a66ff199  studio.js
f419008a61ab08bd98d52512709062c616330acc1ef3f26df3883d7e595f5b19  engine.js
ee0a63e2ed6776e5ad75c75938d8a7e2eaeee8973b97f581bb527d1dfeeb9285  project-history.js
21d24ae11c79cde4de12b5edcb2f91319b14bbf6047d1664745d6920adebbf55  asset-cache.js

Protected background SHA-256:

ade8ada726efa687d077c35da256b6bacacab6d5e297eb16a89e82ed66bfe26c  bg-star-observatory.png
5c8df338ac500bfaf3868e49ba950d48082a5a2a0adba8fb35f810f74b70c543  bg-ember-inn.png
9d9e1a162dd1e056277993dfab2a7743fd7e516ed39795ce17942ed1653ed846  bg-moss-gate.png
859bb49d307d47c636047b48b2c8d03ddf70c3139e7fb88026132c16e961da92  bg-mist-harbor.png

🌐 Production cutover β€” 2026-08-04

Release factObserved state
Live routehttps://moincraft.de/images/svg-animator/
Active release/var/www/images/svg-animator-releases/20260803-perfection-v3-20260804032305
Route pointer/var/www/images/svg-animator is a symlink to the active release
Atomic operationLinux renameat2(RENAME_EXCHANGE) swapped the staged symlink with the prior directory
Rollback/var/www/images/svg-animator-releases/rollback-20260803-production-core-2 retains the prior six files and protected assets
Served-byte proofall six public responses match the Runtime SHA-256 ledger above
Browser prooffresh uncached desktop and mobile sessions passed New storyboard, responsive-sheet Escape/focus return, and horizontal-bound checks
Audio edgeno production sidecar or proxy is installed; the Studio degrades to sidecar-unavailable without blocking visual authoring

πŸ›Ÿ Failure map

SymptomCheck firstCorrect action
Build reports release mismatchnpm run check:releaseuse stamp script; never patch query strings/imports by hand
CSS change absentedited studio.css?edit studio.source.css, then npm run build:css
Studio works but Edge does not./api/audio/capabilitiesinspect sidecar/proxy/provider reason; browser fallback is expected
Speech POST returns 403 while GETs look greenbrowser Origin vs AUDIO_SERVICE_PUBLIC_ORIGINset the sidecar variable to the exact deployed origin; restart and retest POST
Freesound disabledcapability reasonset token server-side only, restart sidecar, recheck capability
Sidecar health is green but provider failshealth vs capabilitieshealth proves process only; use capabilities/provider response
Import warns about referencesmigration diagnosticsrepair source IDs; do not fabricate referenced records
Cast/media will not deleteusage countremove references or use the explicit atomic destructive command
Export differsproject snapshot/assets/tokeneliminate UI/runtime inputs; compare required cached assets and release token
Export omits soundexport summaryexpected: SVG is silent; use project timing for a future package/video exporter
Mobile page scrolls sidewaysgolden viewport testkeep overflow inside timeline sheet; fix document-level overflow
Cache pressurequota/entry telemetryallow deterministic LRU eviction; do not bypass headroom limits

πŸ”„ Rollback

  1. Atomically restore the previous six runtime files.
  2. Verify their stored checksums.
  3. Smoke the relative studio route.
  4. Leave user storage intact: v3 preserves the legacy recovery payload, so rollback must not erase the v2 source.
  5. Sidecar can be disabled independently; the static studio remains functional and browser TTS may fallback.

πŸ“š Dependencies, law, and direct references

ComponentPinned/currentReference
Playwright1.62.1playwright.dev
Tailwind CLI/CSS4.3.3tailwindcss.com
daisyUI5.7.14daisyui.com
Lucide static1.28.0lucide.dev
aiohttp3.14.3aiohttp docs
edge-tts7.2.8upstream
Freesound APIexternal capabilityAPI docs
DiceBear Adventurerpinned explicit optionsstyle docs
Web Speech APIbrowser fallbackMDN
SVGexport platformMDN
  • Exact notices: deploy/THIRD_PARTY_NOTICES.txt.
  • Edge license text: deploy/licenses/edge-tts-LICENSE.txt.
  • Every Freesound selection retains creator, license, canonical source URL, and formatted attribution; dependency notices do not replace per-sound attribution.
  • Redistributors must preserve applicable notices and comply with each pinned dependency’s license.

βœ… Definition of done

  • Edited the owning source, not generated/runtime copies.
  • Schema v3 normalizes safely and migrations preserve recovery.
  • One user action equals one history transaction.
  • No credential, binary audio, arbitrary imported URL, or secret entered project/browser data.
  • Static studio remains useful with the sidecar absent.
  • SVG export is deterministic, self-contained, valid, silent, and honest about omissions.
  • Four golden viewports, keyboard, focus, tooltips, reduced motion, and overflow pass.
  • Node + Python + browser suites pass.
  • Release token stamped once; six-file release contract passes.
  • Two final builds are byte-identical; runtime/background checksums match.
  • Static deploy is atomic; public route/capability smoke passes; prior release remains rollback-ready.

North star: deterministic core, optional capability edges, no secret leakage, no hidden mutation, no dishonest export.