Skip to content

Releases: hishamk/spatialboard

SpatialBoard 0.3.5

Choose a tag to compare

@hishamk hishamk released this 14 Oct 16:32

Fixed

  • A node whose port resolver throws no longer aborts the rest of a recompute pass. Resolving a node's ports runs host code and can throw, but it sat outside the guard that already isolates a throwing compute, so the exception escaped the flush and every node later in the topological order silently lost its recompute. The resolver now parks the error on that node and the pass carries on, exactly like a throwing compute.
  • A node that stops emitting an output port while staying on the board no longer leaves the dropped port's last value behind. The value map only refreshed the ports a node still has, so a resolver that narrows its outputs after a data or type change left the gone port readable at its stale value. On recompute a node now drops any stored value for a port it no longer backs, and the drop counts as a change so a downstream edge still naming that port re-reads null.

SpatialBoard 0.3.4

Choose a tag to compare

@hishamk hishamk released this 28 Aug 15:04

Fixed

  • Selection chrome drawn by the image, youtube, and blocknote-placeholder
    blocks now stays screen-constant at any zoom. These blocks render their own
    ring and handles in HTML inside the zoomed viewport layer and used fixed
    pixel values, so the chrome scaled with the viewport — a thick ring and fat
    rounded handle dots at high zoom. Ring outlines, handle borders, and handle
    corner radii now divide by zoom like the SVG selection chrome; the youtube
    block also drops the 6-unit handle floor that inflated its handles as zoom
    rose. Ring corner rounding stays in canvas units, tracking the node's ink.
  • Selecting a blocknote placeholder no longer shifts its layout: the ring was
    a border-width swap (1px → 2px) and is now an outline, which paints outside
    the box.

SpatialBoard 0.3.3

Choose a tag to compare

@hishamk hishamk released this 25 Aug 07:35

Fixed

  • The group fan-out FAB now mounts in floating chrome too. It was rendered
    only in the console-chrome branch, so boards on the default
    chrome="floating" had no fan affordance on grouped selections. The FAB is
    selection chrome — it anchors to the selection envelope both modes share —
    and keeps its own gates (grouped multi-selection, not read-only).
  • Undoing a fan-out pick no longer scatters the group to the transient ring
    layout. The pick's z-raise pushed its undo snapshot while the members were
    still fanned out, so undo restored the ring positions instead of the pile;
    the snapshot now captures the members at their home positions.

SpatialBoard 0.3.2

Choose a tag to compare

@hishamk hishamk released this 25 Aug 07:33

Added

  • DataFlowEngine.getComputeError(nodeId) — the error the node's last
    compute threw (sync) or rejected with (async), or undefined after a
    successful run. Errors park there instead of aborting the flush, clear on
    the node's next successful run, and both transitions notify change
    listeners so hosts can render the failure.
  • A graph:replaced engine event, emitted when the whole nodes map is
    swapped at once (undo / redo / deserialize) rather than mutated through the
    granular node lifecycle events. Consumers that mirror per-node state — the
    data-flow engine does — rebuild against the new graph on it.

Fixed

  • Deleting a node now recomputes its dependents immediately. The delete
    handler marked them dirty but nothing scheduled the flush (node deletion
    cascades the connected edges away without events), so stale outputs
    survived until an unrelated event touched the graph.
  • A node recreated with a deleted node's id no longer accepts the dead
    instance's late async result: generation counters bump on delete instead
    of resetting, so an in-flight promise from the old instance can never
    match the new one. Matters for serialized boards, where ids are
    deterministic.
  • A fresh run supersedes an async run still in flight for the same node:
    the superseded landing can no longer overwrite a newer result — or erase
    the error a newer failing run parked.
  • One throwing compute no longer costs the rest of the flush its
    recompute: sync throws are caught and parked per node instead of escaping
    the flush microtask after the dirty set was already cleared, and async
    rejections are handled the same way instead of surfacing as unhandled
    rejections. Failed nodes keep their last good outputs.
  • dispose() now severs the SpatialEngine subscription itself — a host
    that never called the connect() cleanup no longer keeps a recomputing
    zombie — marks the engine inert (a flush already in progress stops, and
    a late markDirty schedules nothing), and bumps the generation counters
    so async computes still in flight resolve into nothing, even across a
    dispose-and-reconnect revival.
  • setContainerSize ignores any measurement that is not at least one whole
    pixel on both axes. A NaN/Infinity size from an unvalidated host measurement
    used to flow into the maintain-center shift and poison the viewport (and the
    stored size that later fit math reads); a hidden container's 0×0 measurement
    used to shift the viewport by half the old size on re-show; and a sub-pixel
    measurement while a panel animates open (e.g. 0.5×0.4) used to consume the
    one-shot parked fit against a degenerate size. Such measurements now change
    nothing, parked fits keep waiting, and the next real (≥1px) measurement
    recovers everything. Measurements are coerced to numbers first, so untyped
    hosts passing numeric strings keep working.
  • Undo, redo, and deserialize now replay the data flow. They replace the
    whole nodes map at once, which arrives with no granular node events, so the
    data-flow engine used to leave restored nodes stale and removed nodes'
    values lingering until an unrelated edit; it now reconciles against the new
    graph — dropping gone nodes' state and recomputing the rest — with no
    manual touch. Ordinary edits are unaffected (no whole-graph recompute).
  • Deleting a node drops its whole stored state by id, not just the ports its
    type resolves at delete time. A node whose port set is data-driven and was
    narrowed before deletion used to leak the dropped ports' values onto a
    later node reusing the same id.
  • A throwing onChange listener is isolated: it is reported and the
    remaining listeners still run, on the synchronous and asynchronous notify
    paths alike, instead of starving later listeners or surfacing as an
    unhandled rejection.

SpatialBoard 0.3.0

Choose a tag to compare

@hishamk hishamk released this 23 Aug 10:14

Six fixes and additions driven by real host-app usage of the data-flow board.

Added

  • fitToContent / fitToNodes (and their animated variants) accept options: padding (canvas-space margin, defaults unchanged), insets (screen-pixel bands reserved for host chrome floating over the canvas — content centers in the remaining region), and maxZoom (framing-zoom cap, so fitting a single small node stops zooming all the way in). FitOptions / FitInsets are exported from both the board and spatialboard/engine entries. Option values are sanitized: a NaN/Infinity padding or maxZoom and negative/non-finite insets can never poison the viewport transform.
  • Fits requested before the board's first size measurement are parked and applied when the container measures, instead of framing the 2000×1500 stand-in defaults. A fitToContent() inside a seeding effect now just works — no more double-rAF / setTimeout dances in hosts. fitToFrame parks the same way, and attaching a different container re-arms parking (a reused engine remounted into another panel waits for the new measurement instead of framing the previous container's size). Headless engines (no container attached) keep fitting immediately.
  • PortValue admits arrays (unknown[]): matrices, vectors, and token lists flow through ports directly instead of being wrapped in an object to satisfy the type. (Note for consumers narrowing PortValue: after typeof v === "object" the type is now Record<string, unknown> | unknown[] — code assigning that to a Record or indexing by string needs an Array.isArray branch.)

Changed

  • Output change detection is deep (SameValueZero primitives, arrays, plain objects, depth-capped), and a flush now skips downstream nodes whose upstream outputs did not actually change — a freshly built but structurally equal payload no longer recomputes the downstream graph. A compute that reads external state and relied on those accidental recomputes should call markDirty explicitly. Structural comparison applies to plain data only: Date, Map, Set, class instances, and subclassed arrays compare by reference, so a fresh exotic instance always counts as a change.

Fixed

  • Duplicate port wires between the same pair of nodes no longer make the target read as a cycle (its compute silently never ran; cycleNodeIds flagged it). The topological sort now counts in-degree per unique adjacency, so twin wires — easy to create from a StrictMode double-run seeding effect — cannot wedge a node. createEdge additionally returns the existing edge id for an exact duplicate port wire instead of stacking a twin.
  • animatePanTo(x, y, 0) — and any camera animation given a zero, negative, or NaN duration — now snaps to the target instead of tweening elapsed/0 into NaN viewport transforms that blanked the canvas.
  • Port wires no longer intercept pointer events over the solid cards they cross: buttons, inputs, and draggable cells inside a card stay clickable under a crossing wire. The wire remains selectable in the gaps, and the right-click "Objects here" list reaches it even where a card covers it. Freeform edges keep their full hit priority, so annotation arrows over images behave as before.
  • An inline nodeTypes prop (nodeTypes={[...coreBoardNodes, myType]}, the README pattern) no longer rebuilds the registry — and recomputes the whole data-flow graph — on every host render; when a compute published into host state, that rebuild loop could hang the page.

Full details in CHANGELOG.md.

SpatialBoard 0.2.0

Choose a tag to compare

@hishamk hishamk released this 11 Aug 20:22

The first public release of SpatialBoard — an infinite-canvas library for React: whiteboards, node graphs, and slide decks in one MIT-licensed package.

Try it live: https://spatialboard.hishamkhalifa.com

Highlights since 0.1.0:

  • Three package entries — spatialboard (the slim board), spatialboard/blocknote (opt-in rich text), and spatialboard/engine (headless: no React, no CSS, runs in tests and agent tooling).
  • Typed data flow — nodes declare ports and a pure compute; the reactive engine propagates values through the wired graph, detects cycles, and badges live values on edges.
  • Presentations — frames double as slides with animated transitions (pan, fade, zoom, cube), one call: engine.enterPresentation().
  • Compact mobile chrome — bottom toolbar, on-demand inspector sheet, and touch-sized hit targets under 640px.
  • Interop — Excalidraw .excalidrawlib import and a Mermaid importer that turns flowcharts and sequence diagrams into editable nodes.
  • Round-trip exports — PNG/SVG exports embed the board's SBD source, so an exported image drops back onto a canvas as editable nodes.
  • Agent-native surface — budgeted state snapshots for LLM context, a one-call authoring API, and SBD: a markdown board format models read and write.

Full details in CHANGELOG.md.