Skip to content

29-s3-followup: module node-data writes become undoable and batched - #227

Merged
AlexZ005 merged 5 commits into
release/nextfrom
feat/29-s3-followup
Sep 19, 2026
Merged

AlexZ005 merged 5 commits into
release/nextfrom
feat/29-s3-followup

Conversation

@AlexZ005

Copy link
Copy Markdown
Collaborator

Roadmap 29, lane 29-s3-followup — plan plans/core/pending/module-node-data-undo-and-batch.md (scope 1, 2, 4; scope 3 is modules PR below).

Stacked on #224 (feat/29-sdk-polish, S1/S2): this branch fast-forwarded it in, so merge #224 first and this diff shrinks to the last three commits.

What landed

  • 5181f78 S3a api.flow.setNodeData records ONE flownodes op:data undo entry (the patched keys' previous values, structuredClone'd) with moduleId. Before: a module's write left the stack alone and the next Ctrl+Z undid whatever came before (sdk-polish measured 60 writes, 0 entries). flownodes data items may carry their own graphId (additive, history-local).
  • 28d2c99 S3b api.flow.setNodesData([{id, patch}, …]) → ONE entry across any number of graphs, returns how many nodes it wrote. Wire unchanged: one ordinary nodedata per node (no batched type exists; a new one would leave older peers unconverged — the plan's "undo correctness first"). The data-entry undo now replays items in reverse so a batch writing one node twice undoes correctly.
  • 9c9a345 S3d sdk-game-seams 7b: sixty nodes over three graphs, one real Ctrl+Z restores all sixty and nothing else (the creation survives), late joiner holds the edited values, a later undo reaches it.

Suites

suite base now
sdk-game-seams 59/59 78/78
ai-flow-physics (held) 38 38/38
hud-actions (held) 65 65/65
vitest 158 158
svelte-check 341/47 341/47, identical list
build green green

Counterfactuals

  • S3a record removed → 3 red (undo hit the previous game entry)
  • S3b per-item records → 3 red (11 → 24 entries); forward-order undo → 1 red (twice-written node stays edited)
  • S3d no entry → 7 red (Ctrl+Z undid the creation: 40 of 60 nodes left); no batch → 5 red (one of sixty reverted)

For the integrator

  • CHANGELOG: "Module SDK: a module's api.flow.setNodeData is now undoable, and api.flow.setNodesData([{id, patch}]) makes a group edit one undo step (the collectible manager's group settings use it)."
  • docs-site module-sdk.md: flow.setNodeData(id, patch) → one undo step; new row flow.setNodesData(list) → number written, one undo step, one nodedata per node on the wire.
  • CLAUDE.md (core): flownodes data items may carry graphId + the entry a moduleId; data undo replays items in REVERSE (a batch may write one node twice).

Modules side: theprototype-app/modules feat/29-s3-collectible (collectible v1.1.1).

🤖 Generated with Claude Code

AlexZ005 and others added 5 commits September 19, 2026 13:09
…rule

- api.flow.nodes() snapshots carry x/y (read-only) — addNodes took positions in,
  nothing gave them back, so a module building a graph could not ask where it was
  already occupied (DEVX #16)
- api.flow.freeRegion({w, h, graphId}) answers where a block of new nodes lands:
  left-aligned under the lowest card, a margin from the origin in an empty graph
- the rule lives in ONE leaf, src/lib/flowLayout.js (imports nothing); gameRecipes'
  copy left core in R3a, and the surviving right-of-everything copy in hudActions'
  addBinding now calls the same function with side 'right' (byte-identical x,
  pinned by a unit test)
- suites: sdk-game-seams +7 checks (positions on both peers, two recipes in a row
  beside a user node overlap nothing and stack downward, the overlap detector
  catches a constant-placed block, unknown graph = margin); new unit test
  tests/unit/flowLayout.test.js (7)
- counterfactual: freeRegion returning a constant -> "two recipes in a row land
  on nothing" and "each block lands BELOW" go red (e2e), 5 unit tests red
- hud-actions 65/65 green (addBinding placement); svelte-check 341/47 = base,
  identical error list; vitest 143 -> 150 at this commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… in the seam

- api.flow.onChange(fn) (graph documents AND the trigger log — a node firing is
  what a collected-state list changes on), api.game.onChange(fn) (the game
  singleton), api.peerVars.onChange(fn) (my row + remote rows): thin subscribe
  wrappers over stores core already has, each journalled for teardown and also
  returning an off() for a toolbox that mounts and unmounts (DEVX #17)
- coalesced INSIDE the seam (src/lib/coalesce.js, a leaf): the svelte
  subscribe's synchronous first call is swallowed, any number of ticks before
  the next frame run the handler once, a flush queued at teardown never runs, a
  throwing handler cannot break the store write. One FRAME, not a microtask: a
  microtask was measured to fold a local burst but not thirty edits arriving
  from a peer (+30 handler calls for 30 messages); a 100ms timer races the frame
  so a background tab still gets its call
- suites: sdk-game-seams +15 checks (60 idle frames with a time node ticking fire
  nothing; a 30-tick bulk edit = 1 call; the peer's handler fires as edits arrive
  and folds them, +1 for 30; a node firing = 1; game-state change fires on both
  peers; 20 setVar in a burst = 1; a peer-var write fires mine and the peer's; the
  returned off() stops one subscriber; deactivate unsubscribes all three);
  tests/unit/coalesce.test.js (8)
- counterfactual: debounce removed (flush called per tick) -> bulk edit 30,
  arriving +30, firing +30, setVar 20: five checks red; microtask instead of
  frame -> "arriving edits are coalesced" red (+30 for 30)
- sdk-game-seams 61/61 (base 34), hud-actions 65/65, vitest 158 (base 143),
  svelte-check 341/47 = base with an identical error list, build green

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…module

- A module's node-data write went out on the wire and never touched the undo
  stack, so the next Ctrl+Z undid whatever came BEFORE it (the sdk-polish
  measurement: 60 writes, 0 entries, the undo removed the nodes' creation).
- setNodeData now records a `flownodes` op:'data' entry with the patched keys'
  previous values (structuredClone'd, so a module mutating its patch later
  cannot rewrite history) and `moduleId`. Same `nodedata` message on the wire.
- flownodes data items may carry their own graphId (overrides the entry's) so
  one entry can span graphs - the batch call in S3b needs it. Additive; every
  existing entry has none and behaves as before.
- sdk-game-seams section 4b: one entry, attributed, one undo restores the
  previous data on BOTH peers, one redo re-applies, unknown id still false.
- Counterfactual: record call removed -> 3 red (depth 5 -> 5, top entry was
  `game`; undo left the edit in place on both peers).
- sdk-game-seams 65/65 (base 59/59); vitest 158/158; svelte-check 341/47,
  identical list; build green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- setNodesData([{id, patch}, ...]) writes each node through the same path and
  validation as setNodeData (unknown ids and junk items skipped), records ONE
  `flownodes` data entry for the whole batch (items carry their own graphId, so
  a group spanning one object graph per collectible is still one step), and
  returns how many nodes it wrote. An empty batch records nothing.
- Wire: the ordinary per-node `nodedata`. There is no batched type, and adding
  one would leave older peers unconverged until nodesync - the plan's rule
  (undo correctness first, wire economy second); sdk-polish measured 60
  messages at 7.1 ms, all delivered.
- The flownodes data handler now undoes its items in REVERSE: a batch that
  writes one node twice recorded the second item's `before` after the first
  write, so forward order restored the intermediate value.
- sdk-game-seams section 4c: 12 nodes over the scene graph + an object graph,
  one entry of 13 items spanning 2 graphs, replicated, one undo restores all
  on both peers (incl. the twice-written node), one redo, empty batch inert.
- Counterfactual A: record per item -> 3 red (11 -> 24 entries, undo left 12
  of 12 edited). Counterfactual B: forward-order undo -> 1 red (the
  twice-written node read "grp" after undo).
- sdk-game-seams 72/72 (base 59/59); vitest 158/158; svelte-check 341/47,
  identical list; build green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e Ctrl+Z, a late joiner

- Section 7b reproduces sdk-polish's measurement shape: sixty nodes over the
  scene graph and two object graphs, created in three calls (so the entry
  under the edit is a CREATION), one setNodesData call (60 in ~5 ms), all
  sixty on the peer; a REAL Ctrl+Z restores all sixty on both peers and the
  sixty nodes are still there; after redo a late joiner (C dials A) holds the
  edited values from the ordinary full-state reply, and a later Ctrl+Z
  reverts them on the joiner too.
- Counterfactual 1: skip the history entry -> 7 red; Ctrl+Z undid the
  creation (40 of 60 nodes left, the edit still in place), exactly the
  sdk-polish finding.
- Counterfactual 2: drop the batch (one entry per node) -> 5 red; one Ctrl+Z
  reverted one node of sixty (tags ["bulk60","orig"]).
- sdk-game-seams 78/78 (base 59/59); ai-flow-physics 38/38 and hud-actions
  65/65 (the other flownodes users) held; vitest 158/158; svelte-check 341/47,
  identical list; build green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant