Files
avcf/ARCHITECTURE.md
T

17 KiB

Architecture

Stack

Pure vanilla HTML/CSS/JS — no frameworks, no build tools. MathJax v3 for LaTeX rendering is the only runtime dependency (loaded before app scripts). Eight JS modules loaded in dependency order at the end of <body>.

Load Order

index.html
  └─ style.css
  └─ lib/mathjax/tex-mml-chtml.js  → MathJax v3 (LaTeX→HTML rendering)
  └─ dom.js          → $, $id, svgNamespace, STORAGE_KEY
  └─ helpers.js      → splitValueAndUnit, rebuildSelect, refresh* functions
  └─ error.js        → showError, closeError
  └─ draw.js         → routeBetween, drawpath, shadow link system
  └─ wcblock.js      → WCBlock base (drag/drop, link system, code traversal)
  └─ blocks-algo.js  → Algorithm tab blocks (WCStart, WCEnd, WCOutput, WCDecision,
  │                    WCAssign, WCExpression, WCVarRef)
  └─ blocks-data.js  → Data & Measurement tab blocks (WCQuantity, WCUnit, WCBoolean,
  │                    WCScalar, WCString, WCArray)
  └─ blocks-proc.js  → Procedure blocks (WCProcedure, WCProcCall)
  └─ wcprogram.js    → WCProgram + block registry + registration calls
  └─ persist.js      → saveWorkspace, loadWorkspace, newWorkspace
  └─ app.js          → entry point, setupUI, main()

File Roles

dom.js — Global constants & DOM shortcuts

Provides app-wide constants that every module depends on:

  • $ — document.querySelector.bind(document) — concise single-element selector
  • $id — document.getElementById.bind(document) — concise ID lookup
  • svgNamespace — "http://www.w3.org/2000/svg" — for SVG element creation
  • STORAGE_KEY — "workspace" — localStorage key for persistence

helpers.js — Utility functions & dropdown refresh

Pure helper functions (no module dependencies beyond DOM):

  • splitValueAndUnit(str, defaultNum) — parses "5 km" → {numVal: 5, unitSuffix: "km"}
  • rebuildSelect(sel, placeholder, values, current) — replaces <select> options preserving selection
  • refreshQuantityOptions() — syncs all quantity-name dropdowns from wc-quantity blocks
  • refreshUnitOptions() — syncs unit-suffix dropdowns per datum's selected quantity
  • refreshDefaultUnitOptions() — syncs default-unit dropdowns per quantity's matching units
  • getDefaultUnitSuffix(quantityName) — looks up a quantity's default unit
  • refreshAll() — runs all refresh passes in dependency order
  • refreshVarRefOptions() — syncs varref dropdowns with Data-tab variables

error.js — Error modal overlay

A self-contained error modal:

  • showError(msg) — shows the -ACK! overlay with a message
  • closeError(ev) — dismisses the overlay (click backdrop, X button, or programmatic)

Manages the two-click link system and polyline SVG paths:

  • EXT (25px) — distance a path extends from a hook in its exit direction
  • linkageFirst — tracks the first-clicked output hook during link creation
  • shadowPath — the dashed SVG path following the cursor during linking
  • routeBetween(x1, y1, x2, y2) — 45°-constrained polyline routing
  • resolveHook(path, which) — resolves from/to hook references on a path
  • getHookExit(hook, svgRect) — computes the extended control point for a hook
  • drawpath(path) — draws/redraws an SVG connection path between two hooks
  • startShadowLink(hook) — begins showing a dashed shadow path from a hook to cursor
  • onShadowMove(ev) — updates shadow path endpoint on mousemove
  • endShadowLink() — removes shadow path and stops tracking
  • resetLinkage() — cancels pending link and removes shadow
  • deleteLink(path) — removes a connection path and cleans up block flow arrays

Block files — Custom element classes

Five files define the custom element hierarchy (loaded in order):

wcblock.js — WCBlock base class:

  • Drag-and-drop with grid snapping
  • Two-click hook link system
  • Flow tracking (wcForeFlow, wcNextFlow)
  • Code generation traversal (generateCode, generateNextCode, generateOwnCode)
  • Selection, deletion, resize handle

blocks-algo.js — Algorithm tab blocks (WCStart, WCEnd, WCOutput, WCDecision, WCAssign, WCExpression, WCVarRef)

blocks-data.js — Data & Measurement tab blocks (WCQuantity, WCUnit, WCBoolean, WCScalar, WCString, WCArray)

blocks-proc.js — Procedure blocks (WCProcedure, WCProcCall)

Collectively they define two class hierarchies:

WCBlock (extends HTMLElement) — base for all draggable/linkable blocks:

  • Sets data-block attribute in constructor (used as CSS selector hook instead of listing all tag names)
  • Grid-snapped drag-and-drop (startDragging, dragAround, stopDragging; uses arrow functions for bound handlers)
  • Two-click hook link system (connectedCallback → _setupHook(hook) per hook button)
  • Flow tracking (wcForeFlow, wcNextFlow arrays of SVG path elements)
  • Code generation (generateCode(), generateNextCode())
  • Selection on click (.selected class, blue box-shadow ring)
  • Deletion via X button (top-right, shown on hover) or Delete key
  • X button omitted for WCStart / WCEnd; their delete() shows an error
Subclass Purpose Hooks Code Output
WCStart Entry point 1 output ""
WCEnd Program exit 1 input ""
WCOutput Print statement 1 input, 1 output printf("...");
WCDecision Conditional branch 1 input, 2 output (yes/no), 1 data in (bool) if(...) {...} else {...}
WCAssign Variable assignment 1 input, 1 output, 1 data in (scalar), optional data in (index) name = value;
WCQuantity SI quantity definition none (inert hook) typedef comment
WCUnit SI unit definition none (inert hook) unit comment
WCBoolean Boolean variable none (inert hook) int name = 0/1;
WCScalar Scalar variable with quantity + unit none (inert hook) type name = value;
WCString String variable (char* + length) none (inert hook) string name = {"text", len};
WCArray Typed C array none type name[] = {values};
WCVarRef Read-only variable reference in algorithm view 1 data out "" (visual only)
WCExpression MathJax-rendered expression dynamic data-in (per variable), 1 data out double name = formula;
WCProcedure Procedure definition with typed params none (inert hook) "" (comments only)
WCProcCall Procedure call 1 input, 1 output, dynamic data in/out proc(args);

WCProgram (extends HTMLElement directly) — root container:

  • build() — creates mandatory START/END blocks
  • makeNode(tag) — deep-clones a template by registry lookup, assigns unique ID
  • newNode(tag, parent) — creates a node and appends to the given parent (or registry default)
  • Convenience methods: newDecisionNode, newExpressionNode, newVarRefNode, newOutputNode, newProcCallNode, newUnitNode, newQuantityNode, newBooleanNode, newScalarNode, newStringNode, newArrayNode, newProcedureNode
  • generateCode() — traverses all blocks and produces complete C source

persist.js — Workspace persistence (localStorage)

Serialises and deserialises the full workspace:

  • saveBlockData(block) — reads position + type-specific data from a block
  • saveWorkspace() — serialises all blocks + connections to localStorage
  • restoreBlock(info) — deep-clones a template and restores data onto it
  • loadWorkspace() — deserialises and reconstructs the workspace
  • newWorkspace() — clears localStorage and reloads the page

app.js — Entry point & orchestration

Wires everything together:

  • createDefaultMeasurements() — pre-populates 22 SI quantities and 22 SI units
  • switchTab(name) — activates a tab by name
  • setupUI() — registers all event listeners (delegation pattern, no inline onclick)
  • main() — bootstrap: builds start/end, creates defaults, loads workspace, sets up UI

Data Flow

User clicks output hook
  → linkageFirst = { block, hook }
  → startShadowLink(hook) — dashed path follows cursor

User clicks input hook (different block)
  → checks link-type match (logic↔logic, data↔data)
  → checks data-type match for data links
  → creates SVG <path class="flow" or "flow-data">
  → pushes to wcForeFlow / wcNextFlow arrays
  → calls drawpath(path)
  → calls resetLinkage()

User clicks empty space or same block
  → resetLinkage() cancels pending link

Drag-and-Drop

User mousedown on block
  → startDragging records initial position
  → sets document.onmousemove = this._dragBound
  → sets document.onmouseup = this._stopBound

Mouse moves
  → dragAround snaps position to --grid-size increments
  → redraws all wcNextFlow + wcForeFlow paths

Mouse up
  → stopDragging clears document handlers

Code Generation

WCProgram.generateCode()
  → Gathers all wc-boolean + wc-scalar blocks → usedQuantities, usedUnits sets
  → Filters wc-quantity blocks to only used quantities → typedefs
  → Filters wc-unit blocks to only used units → unit comments
  → Generates variable declarations from datums
  → Walks flow graph from START via generateNextCode()
  → Writes to #c-code textarea

Decision blocks handle convergence: BFS per branch + intersection to detect shared blocks. Branch-exclusive code in if/else, shared code after.

Persistence

Save:  saveWorkspace() → JSON → localStorage["workspace"]
Load:  loadWorkspace() → localStorage["workspace"] → JSON → DOM reconstruction
New:   newWorkspace()  → confirm() → localStorage.removeItem → location.reload()

Key Patterns

Event Delegation (setupUI in app.js)

All toolbar and tab-bar events use delegated listeners on static parent elements, matching e.target.closest("button"). This avoids per-button listeners.

Custom Element Lifecycle

Each WCBlock subclass calls super.connectedCallback() first, which installs hook click handlers (guarded by _hooksSetup flag). Subclasses then cache their child DOM elements in this._el.* to avoid repeated querySelector calls.

SVG Path Routing

Paths are constrained to {0°, 45°, 90°} angles using routeBetween(). Each path segment:

  1. Extends EXT (25px) from source hook in its data-exit direction
  2. Routes with 45°-constrained polyline to target hook's exit point
  3. Approaches target hook from its data-exit direction

Hook Visual System

  • Logic output hooks (data-direction="out", data-link-type="logic"): solid blue circles, scale on hover
  • Logic input hooks (data-direction="in", data-link-type="logic"): dashed gray outlines, fill blue on hover
  • Data output hooks (data-direction="out", data-link-type="data"): solid green circles, scale on hover
  • Data input hooks (data-direction="in", data-link-type="data"): light green dashed outlines, fill green on hover
  • Side hooks (left/right): force horizontal path exits
  • Top/bottom hooks: force vertical path exits
  • Inert hooks (no data-direction): 20% opacity, non-interactive

Clicking an SVG connection path (class flow) deletes it:

  • Removes path from fromElement.wcNextFlow and toElement.wcForeFlow
  • Removes from DOM; nulls out custom properties
  • Delegated click handler on #connections in app.js

Tab System

Tab Pane ID Contains Toolbar Buttons
Algorithm pane-algorithm Flowchart blocks (start, end, output, decision, expression, varref) new decision, new expression, new variable, new output
Data pane-data Boolean/scalar/string variable blocks new variable
Measurements pane-measurements Quantity definitions + unit definitions new quantity, new unit

MathJax Integration (WCExpression)

WCExpression uses MathJax v3 (tex-mml-chtml.js) for client-side LaTeX rendering:

  • Config: inlineMath ($...$), displayMath ($$...$$), enableMenu: false, elements: [] (manual typeset only)
  • Rendering: MathJax.tex2chtmlPromise(latex) returns an HTML DOM node appended to .expr-rendered
  • Variable parsing: LaTeX source is stripped of \commands, braces, operators, and numbers; remaining tokens are deduplicated and filtered against known function/Greek names to produce a list of variable names
  • Dynamic hooks: For each parsed variable, a <button class="hook"> is created in .expr-varhooks with data-link-type="data", data-type="scalar", and wired through _setupHook()
  • Fallback: If MathJax is unavailable, .expr-rendered shows the raw LaTeX text
  • Persistence: Only the LaTeX string is saved/restored; variable hooks are regenerated from it on load

Data Type System (WCBoolean / WCScalar / WCString)

Variable blocks come in three distinct types, not interchangeable:

  • WCBoolean (wc-boolean): A boolean variable. Has a name and a true/false value selector. No unit, no SI conversion, no quantity dropdown. Generates int name = 0/1;.
  • WCScalar (wc-scalar): A typed scalar variable. Has a name, numeric value, unit dropdown, and quantity selector with SI conversion display. Generates quantityName name = siValue;.
  • WCString (wc-string): A string variable. Has a name and a text value. Generates string name = {"text", length};. The string struct ({char *data; long int length;}) is emitted once before variable declarations when any string blocks exist.

Unlike the previous unified WCDatum block, there is no way to switch between types — they are separate custom elements with distinct templates and classes.

WCExpression (wc-expression) is a flowchart block (not a variable) with dynamic data-input hooks that match "scalar" data type for data-flow linking.

SI Measurement System

  • 7 base dimensions indexed dim-0..dim-6: L, M, T, I, Θ, N, J
  • 22 default quantities (length, mass, time, etc.) and 22 default units (m, kg, s, etc.)
  • Units support recursive factor resolution: 1 in = 0.0254 m → getSiFactor() follows references
  • Cycle detection via visited Set in getSiFactor()
  • Datum values auto-convert to SI: 5 km → 5000 in generated code
  • Quantity blocks live in the Data tab; Unit blocks live in the Measurements tab
  • Only quantities and units actually referenced by datum blocks appear in generated code

Performance Notes

  • _gridSize fetched once from CSS computed style, then cached per block (null sentinel)
  • Bound functions _dragBound / _stopBound created once in constructor, reused across drags
  • $id uses native getElementById instead of CSS selector parsing
  • Container-scoped querySelectorAll avoids #id descendant selectors
  • Child elements cached in this._el.* during connectedCallback
  • Deferred setTimeout(..., 0) for initial dropdown population avoids layout thrash

Known Limitations

  • No duplicate link detection (same source→target hook pair creates duplicate paths)
  • Data hooks only support "scalar" type for now; no mixed-type data flow (expression varhooks hard-coded to scalar)
  • No delete functionality for individual blocks
  • Datum value parsing only handles "number suffix" format (no expressions)
  • Code generation walks linear flow; only decision blocks handle branching
  • Tab-hidden SVG paths render at wrong positions (hidden tab getBoundingClientRect returns zeros)

Optimization & Simplification Notes

data-block CSS attribute

All WCBlock subclasses set data-block in the constructor. This replaces tedious tag-name selector lists (e.g. wc-start, wc-end, wc-output, ...) throughout style.css, app.js, and persist.js with a single [data-block] selector.

Dead code removed

  • WCBlock.collectDescendants() — was never called (BFS for downstream blocks; the decision-block codegen handles convergence inline)
  • Duplicate CSS rule wc-expression .expr-varhooks .hook[data-direction="in"] — covered by .expr-varhooks .hook

Persistence fixes

  • Data connections: save/load now handles both path.flow (logic) and path.flow-data (data) SVG paths. Previously, data links were silently lost.
  • Refresh order: refreshAll() runs after all blocks and connections are restored, ensuring varref dropdowns and hook data-type attributes reflect the full state.

Render deduplication

WCExpression._renderFormula() uses a shared done() helper to call _parseVariables + _rebuildVarHooks after both the MathJax and the fallback rendering path, instead of duplicating the calls.

Refresh consistency

newBooleanNode(), newScalarNode(), and newStringNode() now call refreshVarRefOptions() so existing varref dropdowns update immediately when new variables are created. The explicit refreshVarRefOptions() calls in app.js toolbar handlers were removed as redundant.