| Left Drag (empty canvas) | Pan the canvas |
| Ctrl + Left Drag | Rubber-band multi-select |
| Space + Drag | Pan the canvas (alternate) |
| Middle Mouse + Drag | Pan the canvas |
| Scroll Wheel | Zoom in / out |
| Click node | Select — opens inspector |
| Double-click node | Edit node fields |
| Drag node | Reposition in world space |
| Drag selected group | Move all selected nodes — releases & deselects on drop |
| Right-click node | Context menu (Edit, Duplicate, Delete) |
| Right-click connection | Connection menu (type, animated, delete) |
| Click port dot | Start drawing a connection |
| Click target port | Complete connection |
| Click connection line | Select — inspect & edit type |
| Click minimap | Navigate viewport to that point |
| Home / H | Return to world origin (0, 0) |
| F | Fit all nodes to screen |
| + / − | Zoom in / out |
| G | Toggle grid |
| L | Toggle grid labels |
| Delete / Backspace | Delete selected node or connection |
| Escape | Cancel wire · deselect · close modal |
| Ctrl + A | Select all nodes |
| Ctrl + Z | Undo last action (20 levels) |
| Ctrl + F | Open search / find nodes |
| Ctrl + Enter | Save open modal form |
| Ctrl + V | Paste JSON from clipboard |
| ⌖ HOME | Animate viewport to world origin |
| GRID | Toggle background grid lines |
| LABELS | Toggle world coordinate labels |
| ORIGIN | Toggle origin marker at 0,0 |
| FLOW | Toggle animated dots on connections |
| SNAP | Snap node movement to 25px grid |
| MAP | Toggle minimap (bottom left) |
| + NODE | Add a new node at current viewport |
| FIT | Fit all nodes into view |
| ↩ UNDO | Undo last mutating action |
| ⌕ FIND | Search nodes by title, type, or field |
| ▶ SIMULATE | Start live field value simulation |
| ■ STOP | Stop simulation, freeze values |
| command | Solid cyan — direct instruction flow |
| event | Dashed cyan — async event emission |
| data | Solid green — state / data flow |
| script | Dashed amber — script / runtime link |
| bridge | Dashed dim — external integration |
| core | Central system — amber border |
| system | Runtime subsystem — cyan border |
| bus | Event/verb bus — purple border |
| plugin | Loadable plugin — green border |
| console | Dev console / bridge — dim border |
The ERD accepts three tiers of JSON. Drop a .json file onto the canvas, paste with Ctrl+V, or use FILE → Load JSON. The tier is detected automatically.
Fastest seed. Nodes are merged into the current diagram. No connections. No workspace envelope needed.
// Single node
{ "title": "Input Plugin", "type": "plugin" }
// Array of nodes
[
{ "title": "Input Plugin", "type": "plugin" },
{ "title": "Audio Manager", "type": "system" },
{ "title": "Asset Cache", "type": "console" }
]
Nodes and connections together. No workspace envelope. Merged into current diagram or offered as replace. The primary format for agent output when adding a subsystem.
{
"nodes": [
{
"id": "n_input",
"title": "Input Plugin",
"icon": "⌨",
"type": "plugin",
"x": 200, "y": 100,
"status": "running",
"fields": [
{ "key": "devices", "value": "3" },
{ "key": "mappings", "value": "keyboard" }
],
"ports": {
"out": ["events", "state"],
"in": ["commands"]
}
}
],
"connections": [
{
"from": { "node": "n_input", "port": "events" },
"to": { "node": "n_bus", "port": "verbs" },
"type": "event",
"label": "input events",
"animated": true
}
]
}
Complete workspace document. Shown with a merge/replace confirmation modal. Restores viewport position, workspace name, and project metadata.
{
"type": "ashlight.erd.workspace",
"version": "1.1",
"name": "My Architecture",
"project": {
"id": "my_project",
"label": "My Project",
"kind": "erd"
},
"viewport": { "x": 0, "y": 0, "zoom": 1 },
"nodes": [ ... ],
"connections": [ ... ],
"meta": {
"description": "Optional notes"
}
}
{
"id": "n_core", // omit → auto-generated
"title": "Ashlight Core", // required
"icon": "◈", // any unicode glyph
"type": "core", // core|system|bus|plugin|console
"x": 0, // world X position
"y": 0, // world Y position
"status": "running", // running|idle|error|warning|stopped
"fields": [
{ "key": "version", "value": "v1.0.0" }
],
"ports": {
"out": ["commands", "events"],
"in": ["config"]
}
}
Feed live data from a running engine into the ERD. Available in DevTools console as erdUpdate() and erdBatch().
// Single field update
erdUpdate('n_core', 'uptime', '01:23:45')
// Batch update — efficient for engine tick
erdBatch([
{ node: 'n_core', field: 'uptime', value: '01:23:45' },
{ node: 'n_bus', field: 'messages/s', value: '9,104' },
{ node: 'n_render', field: 'draw calls', value: '1,387' },
])
// Status change — changes node dot colour
window.postMessage({
type: 'ashlight.erd.status',
node: 'n_script',
status: 'error'
}, '*')
// Via postMessage from parent frame / BroadcastChannel
window.postMessage({
type: 'ashlight.erd.batch',
updates: [{ node, field, value }, ...]
}, '*')
The diagram loaded by default is the actual Ashlight Engine runtime architecture — not placeholder data. Every node represents a real system. Every connection represents a real data flow. Drop ashlight_runtime_erd.json to restore it at any time.
| Ashlight Core | The root system. Owns the tick loop, time scale, and health. All commands originate here. Positioned at world origin (0,0). |
| Ashlight Bus | Central verb and event bus. Every system publishes and subscribes here. The connective tissue of the runtime. Positioned below Core at (0, 320). |
| Plugin Manager | Loads, unloads, and hot-reloads plugins. Receives commands from Core. Reports loaded plugin count and version. |
| Scheduler | Manages the tick graph — jobs, priorities, and intervals. Feeds requests to Capabilities for permission gating. |
| Capabilities | Grants and revokes runtime permissions. Downstream of Scheduler. Controls what systems are allowed to do. |
| Registry | Entity ownership. Tracks all live instances by stable ID. Bidirectionally connected to the Bus — publishes entity state, receives events. |
| SessionFS | Virtual filesystem with mounts, cache, and watchers. All asset and config access routes through here. |
| ScriptRuntime | Sandboxed JS/DSL VM. Runs compiled scripts in isolates. Publishes events to the Bus. Hot reload supported via Runtime Scripts plugin. |
| Render Interpreter | WebGL2 backend. Receives state packets from the Bus. Reports draw calls, triangle count, and render passes. |
| Physics | Rigid body simulation. Bidirectional with Bus — receives commands, publishes collision state. Bodies and contacts shown live during simulation. |
| Animation | Clip and state machine manager. Driven by Bus command packets. Reports active clip and node count. |
| Environment | Day/night cycle, lighting, atmosphere. Driven by Bus. Sky mode shown as Dynamic during simulation. |
| Character Controller | Plugin. Movement, grounding, input map, retargeting. Receives command packets from Bus. Positioned below Bus at centre. |
| Runtime Scripts | Plugin. Hot-reload watcher. Feeds compiled scripts directly to ScriptRuntime. |
| Runtime Console | Log sink. Receives event and state packets. Read-only — no output ports. |
| DSL Console | Interactive DSL query terminal. Can publish verbs back to the Bus — bidirectional. |
| Bridges / Bus | External integration bridge. Bidirectional with Bus via delta and ack packets. |
Connections flow from output ports (left dot, cyan) to input ports (right dot, green). Animated dots show active data flow — their speed and colour reflect the connection type. The connection label appears at the midpoint. Click any connection to inspect and change its type in the inspector panel.
Hit ▶ SIMULATE to see all field values update in real time, with the event stream logging every change. This approximates what a live engine connection would look like.
Design, visualise and connect to your live engine.
Infinite canvas. Typed connections. Real-time field data.
Drop JSON from any AI agent — your architecture appears in seconds.