Cynario Cynario alpha
Browse docs

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

Per Scene Mode → authoring projection Pipeline → runtime rendering

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
Read from the center out: two surfaces share one Scenario source; each Scene carries its own Mode, the Player uses a separate render pipeline, the PlayLog reconstructs runtime state, and optional services stay outside the static core.

Tech stack

LayerTechnologyPurpose
FrameworkAstroStatic deployment and routed shells
UISvelte 5Single interactive UI framework
Graph UISvelte Flow + Narrative MapVisual authoring and routed graph layout
LanguageTypeScriptShared schema and runtime types
StylingTailwind CSSApp-owned tokens and components
MarkupCommonMark pipeline; Legacy default, Markdoc/remark engines behind one seamConstrained source, sanitized at the render sink
LogicAllow-listed expression/runtime modulesBounded evaluation without eval
CollaborationTrystero/WebRTC, optional Yjs and encrypted relayOptional live desktop collaboration
CryptographyWeb Crypto + Ed25519Authenticated encryption, derivation, hashes, and signatures
PersistencelocalStorage + IndexedDB + OPFS + filesLocal storage selected by data size and durability need
HostingStatic hosts or containerCloudflare 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.