Architecture
Analysis Date: 2026-05-05
System Overview
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Data Layer (JSON Files) β
β `frames/*.crumbframe.json` β `partners/partners.json` β
β `schema/crumbframe.v1.json` β `.doktor_results.json` β
ββββββββββββββββ¬ββββββββββββββββ΄ββββββββββββββββββββββββββββββββ
β β
βΌ βΌ
ββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββ
β Validation Layer β β Web Renderer Layer β
β `scripts/doktor.sh` β β `web/index.html` β
β (Bash + jq + β β `web/renderer/exploded2d.js` β
β python3/jsonschema)β β `web/renderer/connections.js` β
ββββββββββββββββ¬ββββββββ ββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β External APIs β
β github.com/betaflight β github.com/ExpressLRS β
β shop.rc-hangar15.de β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Component Responsibilities
| Component | Responsibility | File |
|---|---|---|
| CrumbFrame Schema | Defines valid structure for all frame files, enforces Passkante | schema/crumbframe.v1.json |
| Frame Data File | Single drone build: components, connections, agent checks, partners | frames/whoop_1s_65mm.crumbframe.json |
| Partner Database | Global partner registry (shops, tools, knowledge, community) | partners/partners.json |
| Doktor Agent | Validates frames, runs agent checks, writes results | scripts/doktor.sh |
| Exploded2D Renderer | Draws SVG component map from frame position_2d data | web/renderer/exploded2d.js |
| Connections Renderer | Draws typed bezier curves between components on SVG | web/renderer/connections.js |
| Web Entry Point | Fetches frame JSON, orchestrates renderer classes | web/index.html |
| Agent Results Cache | Persists last check status and result per agent check | .doktor_results.json |
Pattern Overview
Overall: Data-centric, schema-first, no-framework
Key Characteristics:
- All domain knowledge lives in JSON files β code is thin orchestration over data
- Schema (crumbframe.v1.json) is the single source of truth for what a valid frame is
- Validation and rendering are independent consumers of the same frame JSON
- No build step, no package manager, no server β intentionally minimal
- Agent checks are declared in the frame file itself (self-describing data)
Layers
Data Layer:
- Purpose: Stores all drone knowledge as structured JSON
- Location: frames/, schema/, partners/, .doktor_results.json
- Contains: Frame builds, JSON Schema definitions, partner records, check results
- Depends on: Nothing (pure files)
- Used by: Doktor validation layer, web renderer layer
Validation Layer:
- Purpose: Check frame correctness, run agent checks against live external sources
- Location: scripts/doktor.sh
- Contains: Bash orchestration of jq, python3/jsonschema, curl calls
- Depends on: jq (CLI), python3 + jsonschema (pip), curl, frame JSON files
- Used by: Developers and contributors (manual pre-commit), future CI
Web Renderer Layer:
- Purpose: Visualize a frame's component layout and connections as interactive SVG
- Location: web/
- Contains: HTML shell, ES Module renderer classes, CSS
- Depends on: Browser fetch API, frame JSON (loaded via relative path)
- Used by: End users viewing drone exploded diagrams in a browser
Data Flow
Validation Path (doktor.sh)
- Receive frame file path as argument (
scripts/doktor.sh frames/whoop_1s_65mm.crumbframe.json) - Validate JSON syntax with
jq empty - Check
use_context[]against forbidden list (military,surveillance,weaponized,dual_use) β Passkante gate - Verify
passkante: truefield present - Log
meta.id,meta.class,meta.version,meta.contributors - Verify all
connections[].fromandconnections[].tokeys exist incomponents - For each entry in
agent_checks: executebashfield viaeval, store result - Run
python3 -c jsonschema.validate(...)againstschema/crumbframe.v1.json - Write results to
.doktor_results.json(JSON:{frame, checked_at, checks}) - Print summary: green / yellow / red
Web Renderer Path
- Browser loads
web/index.html - Inline
<script type="module">fetches../frames/whoop_1s_65mm.crumbframe.jsonviafetch() - Constructs
Exploded2D(container, frameData)β resolves$refcomponent references, calculates SVG bounds fromposition_2d renderer.initSVG()β creates<svg>element with computed viewBoxConnections(renderer.svg, frameData).draw()β draws bezier<path>elements per connection, styled bytypeCSS classrenderer.drawComponents()β draws<g>nodes (circles for motor/prop, rects for others), bindsmouseenterfor inspector panel
State Management:
- No application state beyond the DOM. Inspector panel updated via direct innerHTML writes on mouseenter.
Key Abstractions
CrumbFrame (JSON object):
- Purpose: Complete description of a drone build β hardware, spatial layout, connections, validation rules
- Examples: frames/whoop_1s_65mm.crumbframe.json
- Pattern: Self-describing JSON document with embedded agent checks and partner refs
Component Node (within components object):
- Purpose: Single hardware part with type, specs, 2D/3D position, and traceability (partner, contributor, donated_by)
- Pattern: Keyed object map; supports $ref to another component key for spec inheritance
Agent Check (within agent_checks object):
- Purpose: Declared automation task β firmware version watch, product availability, schema validation
- Pattern: Self-describing check with type, interval, action, optional bash command, last_checked/last_result fields
Connection (within connections array):
- Purpose: Typed directed edge between two component keys
- Pattern: {from, to, type} where type is one of power | signal | control | mechanical | video | data
Passkante:
- Purpose: Hard ethical constraint β zero dual-use/military/surveillance contexts permitted
- Enforcement: JSON Schema not.contains rule in use_context, explicit bash check in doktor.sh, passkante: true boolean field
Entry Points
Doktor CLI:
- Location: scripts/doktor.sh
- Triggers: Manual call bash scripts/doktor.sh frames/DATEI.crumbframe.json; intended as pre-commit hook
- Responsibilities: Full validation pipeline β syntax, Passkante, component integrity, agent checks, schema
Web App:
- Location: web/index.html
- Triggers: Browser open (requires local web server or file:// with CORS disabled)
- Responsibilities: Load hardcoded frame whoop_1s_65mm.crumbframe.json, render interactive SVG diagram
Architectural Constraints
- No server: All logic runs either in the browser (renderer) or on developer's local shell (doktor). No backend.
- Hardcoded frame path:
web/index.htmlfetches../frames/whoop_1s_65mm.crumbframe.jsonβ only one frame displayable without code change. - Global state: None. Each doktor.sh run is stateless except for the
.doktor_results.jsonoutput file. Web renderer holds state only in ES class instance variables. - $ref is custom, not JSON-Pointer: Component
$refvalues (e.g.,"$ref": "prop_fl") are resolved byExploded2Dconstructor using object key lookup β not standard JSON Schema$ref. - agent_checks bash field uses eval:
scripts/doktor.shline 88 runseval "$BASH"β arbitrary shell execution of whatever is in the frame'sbashfield. Frame files must be trusted. - No CI enforcement: Passkante and schema checks are only enforced if
doktor.shis run manually. No GitHub Actions workflow exists.
Anti-Patterns
Hardcoded frame in web renderer
What happens: web/index.html fetches '../frames/whoop_1s_65mm.crumbframe.json' directly as a string literal.
Why it's wrong: Adding a second frame requires editing index.html β renderer cannot display arbitrary frames.
Do this instead: Accept a ?frame= URL query parameter or build a frame picker UI that lists frames/.
eval in agent check runner
What happens: scripts/doktor.sh runs eval "$BASH" on the bash field from the frame JSON.
Why it's wrong: Any contributor-submitted frame with a malicious bash field executes arbitrary shell commands on the validator's machine.
Do this instead: Whitelist allowed command patterns or run checks in a sandboxed subprocess with restricted PATH.
$ref is non-standard
What happens: Component spec inheritance uses "$ref": "motor_fl" as a sibling component key (e.g., motor_fr.$ref = "motor_fl"), resolved only in Exploded2D.js.
Why it's wrong: doktor.sh and the JSON Schema validator do not resolve these refs β schema validation treats $ref-only components as valid by passing additionalProperties on the parent object. Consistency is fragile.
Do this instead: Use a dedicated spec_ref field name (not $ref) to avoid confusion with JSON Schema semantics.
Error Handling
Strategy: Bash set -euo pipefail β hard exit on unhandled errors in doktor.sh. Errors in agent checks are caught and logged to .doktor_results.json as status: "error" without aborting.
Patterns:
- JSON syntax error in frame β jq empty fails β exit 1 immediately
- Passkante violation β ERRORS counter incremented β non-zero exit at end
- Missing jq β exit 1 with install hint before any processing
- Missing python3 or jsonschema β warning only, validation step skipped
- Agent check eval failure β status: "error" in results, WARNINGS incremented, continues
- Web renderer fetch error β caught in try/catch, error message injected into #frame-meta element
Cross-Cutting Concerns
Logging: doktor.sh uses colored ANSI output (ok/fail/info/warn helpers). Results persisted to .doktor_results.json.
Validation: JSON Schema draft-07 (schema/crumbframe.v1.json) is the authoritative validator. Bash pre-checks in doktor.sh supplement it for Passkante.
Passkante / Ethics: Enforced at schema level (JSON Schema not.contains on use_context), at script level (bash loop in doktor.sh), and at data level (passkante: true required field, forbidden_contexts array in frame files).
Architecture analysis: 2026-05-05