[Architecture overview]

End-to-end AXIS architecture: Solid AXIS UI, unified registry, dual engines, chart apply pipeline, and optional edge plane.

Architecture overview

Abstract

AXIS is a composition host. The product thesis: own the axes (history, live, calc, library), swap the implementations. Shipping UI is Solid + Vite + lightweight-charts + CodeMirror 6; calculation is never inlined into UI components.

Conceptual model

Interface surface (module map)

ConcernPrimary paths
Entryfrontend/src/index.tsx, app.tsx
Statefrontend/src/store/
Pluginsfrontend/src/plugins/{registry,types,loader,bootstrap,active}.ts
Catalogssources/catalog.ts, streams/*, engines/catalog.ts, storage/*
Chartchart/ChartHost.tsx, pane-manager.ts, drawing-layer.ts
Editoreditor/*
Run applyindicators/runner.ts
Resultsresults/*, ui/ResultsPanel.tsx
Workerfrontend/worker/

Legacy parallel: state.js, registry.js, main.js — not product path. See repo frontend/LEGACY.md.

Control plane vs data plane

PlaneWhat movesWho owns it
ControlactivePlugins, endpoint, theme, layoutSolid store + localStorage
Historical dataBar arraysSource plugins → store.bars
Live dataBar updatesStream plugins → multiplex → bars/chart
Calculationscript + bars → RunResultEngine plugins
LibraryScriptDocumentStorage plugins
Telemetryplane connectivity / latency / ticksEphemeral store.telemetry + Connection HUD

Transport preference (ADR-014)

PathPreferred transportNotes
Load (history)REST / localVenue kline HTTP, CSV, mock walk
LiveWebSocketVenue WSS with reconnect; mock-poll local; DO = brokered
Engine serverHTTPPOST /run batch; latency in HUD
Engine pyodideLocalOffline-capable after asset cache

Live pairs with history via defaultStreamForSource. Optional live.preferAfterLoad auto-starts WS after Load (default off).

Dual engines

EngineTransportFidelity host
serverHTTP JSON { script, data }/runFlask or Worker→Flask
pyodideIn-worker-thread-ish browser PythonSelf-hosted wheels + pynescript_runtime.py

Timeout policy in runner scales with bar length (clamped). Live re-runs use shorter silent timeouts.

Chart apply pipeline

runAndApply:

  1. getActiveEngine().run(...)
  2. setLastRun
  3. Optionally open results
  4. Sync overlays in place (syncOverlayLines) — no blank destroy frame
  5. Markers + strategy report side effects
  6. Atomic Pine script drawings replace
  7. Equity pane when appropriate (skip hide thrash on silent live re-runs)

History vs live data path: loadBars bumps chartDataGen → ChartHost full setDataToChart + fit. Live ticks only manager.appendBar (never full setData/fitContent).

PaneManager is imperative DOM under a Solid-owned outer shell—Solid must not reconcile the pane root (ChartHost invariant).

Plugin registry

PluginRegistry holds ordered maps per kind; events registered / unregistered. Built-ins register once (ensureBuiltins / catalog ensure*Registered). Dynamic plugins via loadPluginFromUrl. Active selection is not inside the registry—it is store state.

Contract namespace conceptually: pynescript.axis.plugins.v1 (fields: id, name, kind, configSchema, capabilities, kind-specific methods).

Edge plane (optional)

Cloudflare project pynescript-superchart:

  • Pages: static dist/
  • Worker: /api/run proxy, keys/usage KV, scripts D1, stream DO fan-out
  • AXIS still speaks engine/storage plugins—not raw CF APIs in UI components

Invariants

  1. AXIS ≠ engine
  2. Bars/logs/lastRun not fully persisted
  3. Storage and engine endpoints may coincide (Worker) but remain separate plugin roles
  4. Legacy SuperChart keys migrate forward, never the reverse on write

Failure modes (architectural)

ModeManifestationMitigation
God-object UILogic in componentsKeep runner/registry pure modules
Engine leakageFetch /run from random widgetsOnly engine plugins perform calc I/O
Registry/store split brainFlat engineactivePlugins.enginesetActivePlugin keeps alignment
SPA asset fallbackPyodide loads HTMLAssert ZIP magic on assets

See also