Architecture
Architecture
The technical architecture for Cynario — a static, local-first web app whose Builder and Player surfaces share one Scenario contract and an append-only PlayLog.
On this page
Cynario’s core is a static Astro application with Svelte islands. Authoring, play, validation, encryption, persistence, import/export, and evidence generation run in the browser. The Builder and Player are product surfaces over the same Scenario contract; they are not Modes. A Mode is selected per Scene and chooses its authoring projection, while a separate render pipeline determines how that Scene appears during play.
Optional infrastructure can distribute static ciphertext, relay encrypted collaboration, publish federated catalogs, or add future account-backed services. Those systems surround the local-first core; ordinary creation and play do not require a central plaintext Scenario service.
High-level architecture
System constellation
A static browser core, with infrastructure at the edge
Builder and Player share the Scenario source. Each Scene selects a Mode, the Player renders through a registered pipeline, and the append-only PlayLog is the source of truth for runtime state. Local persistence and portable files keep the core independent of optional network services.
Client-side / static
Cynario browser core
Surface
Builder
Graph canvas and Scene editor author one shared source.
Surface
Player
Registered render pipelines present and advance the run.
Shared contract
Scenario source
Scenes · Paths · variables · logic · settings · content-addressed asset references
Runtime truth
Append-only PlayLog
Player state is reconstructed by replay; record-once values are captured, not recomputed.
Local persistence
Right-sized by data and durability
- localStorageScenarios, settings, small records
- IndexedDBMetadata, Blob fallback, wrapped capability
- OPFSLarge content-addressed media bytes
- Files.cynario packages, context packs, results
Tech stack
| Layer | Technology | Purpose |
|---|---|---|
| Framework | Astro | Static deployment and routed shells |
| UI | Svelte 5 | Single interactive UI framework |
| Graph UI | Svelte Flow + Narrative Map | Visual authoring and routed graph layout |
| Language | TypeScript | Shared schema and runtime types |
| Styling | Tailwind CSS | App-owned tokens and components |
| Markup | CommonMark pipeline; Legacy default, Markdoc/remark engines behind one seam | Constrained source, sanitized at the render sink |
| Logic | Allow-listed expression/runtime modules | Bounded evaluation without eval |
| Collaboration | Trystero/WebRTC, optional Yjs and encrypted relay | Optional live desktop collaboration |
| Cryptography | Web Crypto + Ed25519 | Authenticated encryption, derivation, hashes, and signatures |
| Persistence | localStorage + IndexedDB + OPFS + files | Local storage selected by data size and durability need |
| Hosting | Static hosts or container | Cloudflare Pages or self-hosted nginx/container |
Data flow
The diagrams below are simplified interaction sketches. The runtime does not treat a mutable “current player state” object as authority: each accepted step appends records to the PlayLog, and replay reconstructs the derived state. Imported or restored data passes the same validation and sink-level security rules regardless of transport.
Player surface
┌──────────────┐ ┌───────────────┐ ┌─────────────────┐
│ Load JSON │────▶│ Parse & Store │────▶│ Render Start │
│ Scenario │ │ in state │ │ Node │
└──────────────┘ └───────────────┘ └────────┬────────┘
│
┌──────────────────────────────┘
▼
┌─────────────┐
│ User clicks │
│ choice │
└──────┬──────┘
│
┌────────────▼────────────┐
│ Append PlayLog step │
│ Capture record-once │
│ Replay derived state │
└────────────┬────────────┘
│
┌────────▼────────┐
│ Is ending? │
└────────┬────────┘
│
┌───────────┴───────────┐
▼ ▼
┌───────────┐ ┌───────────┐
│ Render │ │ Show │
│ next node │ │ ending │
└───────────┘ └───────────┘
Builder surface
┌─────────────────────────────────────────────────────────────┐
│ Graph Canvas │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ A │───▶│ B │───▶│ C │ │
│ └──┬──┘ └─────┘ └─────┘ │
│ │ │
│ On node click ─────────────────────────────────────────────┼───┐
└─────────────────────────────────────────────────────────────┘ │
│
┌──────────────────────────────┘
▼
┌───────────────────┐
│ Scene Editor │
│ Sidebar │
│ │
│ • Source content │
│ • Scene Mode │
│ • Logic/effects │
│ • Outputs │
│ │
│ [Save] [Delete] │
└─────────┬─────────┘
│
▼
┌───────────────────┐
│ Scenario Store │
│ (Scenes, Paths) │
└─────────┬─────────┘
│
┌─────────────────┴─────────────────┐
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Re-render │ │ Persist by │
│ graph │ │ storage tier │
└───────────────┘ └───────────────┘
URL structure
/ - Home and scenario library
/builder - Builder surface (visual graph editor)
/player - Player surface (played experience)
Security considerations
- Untrusted inputs: Imported, decrypted, fetched, generated, and peer-supplied content all pass the same bounded import gate.
- Rendered markup: Markup is sanitized at the render sink; logic uses the bounded interpreter
without
eval. - Local persistence: Browser storage is not a secure enclave and remains inspectable or clearable by the local browser profile and device user.
- Local-first core: Builder and Player do not require a central application database. Static hosts, catalogs, signaling, relays, and containers remain ordinary network trust surfaces.
- Encryption: Authenticated ciphertext protects transport and storage boundaries; decrypted Scenario content is still untrusted.
See the full trust model on the Security & trust page.
Performance considerations
- Large graphs: Svelte Flow handles many nodes; virtualize where needed.
- Markup rendering: Memoize parsed and sanitized content where safe.
- Media: Keep large content-addressed bytes in OPFS with IndexedDB fallback rather than inflating Scenario JSON.
- Bundle size: Keep optional surfaces and pipelines split from the static shell.
- Offline: The local core and portable files remain the floor; network features degrade without becoming a prerequisite for authoring or play.
Related
- Data model — the Scenario JSON schema everything compiles to.
- The Scene (the Scene source) — the author-facing model for what a node actually contains.