Skip to content

Release 0.4.0 - #48

Merged
richardosborne14 merged 1039 commits into
mainfrom
release/0.4.0
Oct 12, 2026
Merged

richardosborne14 merged 1039 commits into
mainfrom
release/0.4.0

Conversation

@richardosborne14

Copy link
Copy Markdown

Release 0.4.0. Its main purpose: NodeGX now opens the apps people make with npm create nodegx. The released 0.3.0 refuses them, because the npm tools save projects in format 5.

What this PR is

  • release/0.4.0 is cline-dev at b7e84fa67, with main merged in. The merge keeps cline-dev's tree exactly: all 14 conflicting files are ones where cline-dev already carries main's 0.3.0 release fixes, or a newer regeneration of them (P112 OWN-013 §6.1).
  • Merge it with a merge commit, not a squash. Then tag v0.4.0 on main.

Checks

  • The six required checks passed on the pushed cline-dev at 01aeb0868 (run 38219618264). The editor suite ran 3,015 specs with 0 failures, at seed 19863.
  • Four commits have landed since then, one of them P107 code (a94119947). This PR's run covers them.
  • Known: the re-arms a held save spec depends on test order. It failed at one seed locally, and passed at CI's seed. If Test (editor) is red with only that spec, re-run the job.

Decisions a reviewer should see

  • R19: the TSFixme/any ratchet was raised visibly in 7efa3395d (+98 in src, +1,371 in tests). It's owed back down, as 0.3.0's raise was.
  • R17: a 0.3.0 project is copied to <project>.before-0.4 before its format moves to 5.
  • R21: ship now. What P107 and P116 land next goes into 0.4.1.

The changelog entry is in CHANGELOG.md, and the release notes are in dev-docs/tasks/phase-112-your-own-nodegx/GITHUB-RELEASE-0.4.0.md.

🤖 Generated with Claude Code

richardosborne14 and others added 30 commits October 9, 2026 18:10
…(their s70 item 5, c2fdb3b); the s36 handoff waits for their word

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h review — `docs/world/verbs.json` as the optional fourth file: a verb is the person's sentence with its steps (make, update, end, remove, keep here; ask another system) and one failure arm per step (record it and go on past the steps it skips, or stop), bound by name to the world, to the engine's fifteen step kinds and to NSP-014's 24 specced nodes with where each runs (a cloud-only node counted as `outside`), every refusal naming what was expected; `projectVerb()` → a workflow definition the admin route takes (an arm is the error edge's step, a keep a note the run keeps) and the functions the agent owes; `verbClaims()` → a claim per row (ClaimStep grew `there` and `holds`), "stays" measured not kept by the store; graded in the REAL engine on the enrolment note's fixture (three requests and done; two, the arm recorded in the person's words with the failure, the enrolment skipped) and in NSP-014's world (an ask is one request, a 404 its Failure; a make one create), the archive note's rows on SQLite and PostgreSQL. The reach review (Fable): a rule put on records that already break it was refused by the route after the backup — two dry-run checks read it first; on PostgreSQL the executor reported a refused rule as ran — `runMigration` awaits the schema barrier per step; a Record node's `prop-` ports are OUTPUTS — the component reach reads both ends; a path rule's hops after a rename carry `mustRewrite`; a changed answer that changes only a collection's words passes without a step — pinned `it.failing` until `describeTable` exists; workflow steps that sort or filter by a field named as outside the reach. Bug P116-ONT-003-two-systems (s9, Fable 5.1)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`validate_world` reads `docs/world/verbs.json` when it is there: graded with the three files
(through `nextQuestions` → `validateWorld`), and a `verbs` block in the answer with `outside`
(steps bound to a cloud-only node: counted, never refused), each verb's words (`verbWords`) and
the functions it calls (`projectVerb`'s, the ones the agent writes). `write_world` takes an
optional `verbs`; an unreadable verbs file makes the world `partial` and says which.

The write rule (`planWorldWrite`): a write that leaves the verbs out keeps them, and now grades
them against the world it writes. Before, a write that took out a relation a verb names passed
and left `verbs.json` pointing at nothing (mutant-checked: the spec goes red without the fix, in
the contract and through the MCP). A verbs file on disk that is not JSON stops such a write.

The brief gains "What the person does" (the fourth file, optional, one arm per step, the
functions are yours to write) and "Verbs you leave out are kept"; pinned in `brief.test.ts`;
the paragraph passes the content-authoring `written.py` lint.

Also: two type errors in P116's own specs that only `tsconfig.tests.json` sees (verbs.test.ts:55,
world.test.ts:249), present at d53e5f2.

Readings: contract 184/184, both tsconfigs clean; noodl-mcp `ont002WorldTools toolDisclosure`
30/30, `[surface] 8261 tokens / 20 resident tools — 19 under` (unchanged: both tools deferred).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he runner takes an arm's answer — ONT-007 AC1 ✅

`verbReach(plan, verbs, before, after)` beside `workflowReach`: a verb step `on` a noun or an
own-record relation whose collection the plan makes, changes, renames or removes, read off the
step's own binding (a workflow ties a step to a collection only through the function's graph).
A row names the verb and step in the person's words, the function it calls, the collection, the
changed fields the step writes (all for a make, the named ones for an update, the end field for an
end), and `rebind` when the step's `on` must be re-said (a renamed noun's new id; `null` when it is
on nothing after). The bombshell fixture grew `verbs.json` ("A company joins": the person step is
reached, the account step is the control). AC1's reach now names a collection, a component, a
workflow and a verb; its records half was green in s7.

The runner (`nextQuestions`): a question the relation has not answered, which a verb's failure arm
`answers`, is on the record with the arm's `sees` and `by: {verb, step, arm}`, not asked; the
relation's own answer stays the record when there is one. `validateVerbs`: an arm may answer only a
question answered in words (a decision flagged in ONT-006 §6.7 for revisiting).

Mutants, each red on its own spec only: make/update writing nothing, the rename's id forgotten, a
step on nothing reported as bound; the arm lookup off, the arm replacing the relation's answer, the
words-only refusal off.

Readings 2026-10-09: contract 192/192, both tsconfigs clean; noodl-mcp ont002WorldTools
toolDisclosure 30/30 (surface 8261, unchanged); backend ont-006/007/005/003-claims/003-write
122/122 in band, both engines; ontology census fresh.

Docs: ONT-007 §6.10 + status (AC1 ✅), ONT-006 §6.7 + owed list, ONT-002 note, ONT-008 §5 lists
what ONT-006/007 hand the view, README status, the s11 handoff.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e and history.back(), its popstate landing at the next yield, on both targets (s70, for C120 / C126)

world.ts LOCATION, THE SESSION HISTORY: the location keeps its entries and which is current (a push drops
the ones ahead). `replaceState` is a `history` event op `replace` (refused across origins as a push is,
fires nothing); `back()` is op `back`, moves nothing at the call, and lands as a task at the next yield
to the event loop: after the frame-end work and the promises that yield resolves, one at a time, popstate
then hashchange. On the first entry it lands as nothing. The person's `traverse` goes to the nearest entry
holding its url.

Spec format: Patch `replace` / `historyBack`, AnswerCall `{ op: 'history', how }`, WorldHandlers
`popstate` (every node with one hears a landed back). Trace format: `history` op push | replace | back
(JSON schema and validateTrace agree, 4 new rows). Runtime target: the page's history.replaceState / back
are the world's, and `yieldToEventLoop` lands waiting traversals. Probed on the runtime with a real Page
Stack (Use Routes): back() after a push lands at the next settle, the stack hears popstate and resets to
Home; the control without it stays on Detail. Stranger hashes rewritten; stranger 46/46, Page Stack and
Router conform on the runtime.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…icked — AC5 driven over stdio (P109-S36-PINUNREPORTED)

ISL-018 AC5, driven: Claude Code over the stdio door in a format-4 copy of landing-pages, five rounds; the editor's
one-time clean-up writes 0 on the agent's Run-driven Function (control: 2 written and logged), and the preview draws
the rows. Rounds 1-4 graded nothing of the door: agents build value-driven graphs, or answer the box themselves.

Round 5 found the pin silent. create_component, update_component and apply_plan now answer
ticked: { boxes: [{ component?, node, parameter }], why } (not on an unchanged write); no tool schema changes.
isl018DoorPins 6/6, two reverted arms red on exactly their rows; noodl-mcp suite 18 red = the same 18 by name
with HEAD's files. Also: P109-S36-STALEMCPDIST (the checkout's dist predates the pin), s35's bug header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…wser, eleven fixtures recorded, the model checked against them; rows C152, C153 (s70)

Step 3, scripts/node-spec/layout-measure.js: reuses render-report's withRenderedPage (one headless Chrome,
torn down per project). Each visual node's element through React's fibers (`props.noodlNode` on the
NoodlReactComponent wrapper, then `getDOMElement()`), so no viewer change. Its box in the parent's padding
box, its size, whether a press at its centre reaches it, opacity up the tree, transformed. Graded by hand on
a padded column with a gap, then on a planted control that moved.

Step 4, layout-fixtures/: eleven fixtures, one rule each, recorded with `--record` into measured/.
Step 5, tests/layout-check.test.ts, in CI with no browser: 62 boxes match, none mismatch, the better-scroll
path reported (Native platform scroll is web-only). Planted twice: gap 0 in the model reddened exactly the
gapped rows, left margin 0 in the mapping exactly the margined one. Both restored.

Found on the better-scroll path (Native platform scroll off): C152, the gap is dropped (the inner wrapper
copies the direction, not the gap); C153, nothing clips (a child below the scroller is drawn and takes a
press). Both are pinned as firing beside the native path's opposite reading. Both are P109's (R16).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…C4 steps 3–5 (071acfc); what is left of AC4: template combinations, content children, a percent in a scroll with content, the min-content floor

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y back, a Replace overwrites the entry, and a Reset goes to the start page and writes its address (P107-C120, C126)

As ruled on P107's rulings page: "A Pop steps the browser's history back" (C120) and "Reset goes to the
start page and writes its address" (C126), on P107's session history (b7950a3).

- A push records whether it made a history entry; its Pop's end calls history.back() over it and counts it
  as the stack's own, so that popstate resets nothing. A page whose push made no entry has the address
  replaced. A Replace writes its URL with history.replaceState.
- popstate: a stack whose address already names the page and parameters on its top does nothing, so a
  parent stack does not rebuild when a child pops, as the Page Router keeps a page it already shows.
- A Reset press or a Components edit builds the Start Page, and pushes its URL when the address names
  another page. The mount and the browser's Back still read the address.

Page Stack spec v18: 13 scenarios renamed to what they now do; 5 URL read-backs (C121-C124) moved from a
Reset to the person's Back and Forward; 4 added. Runtime conforms at seeds 13, 20732, 20735, 7: 162/162,
200/200, 0 divergences; mutants 846/847 (868/869 at 20735), 0 survivors, the 1 a known equivalent. Control
(HEAD's runtime file): red on exactly 16 scenarios. Navigation specs conform; viewer 1,837/1,837; page
model 42/42 with popstate sorted into WORLD_KINDS (red on cline-dev since b7950a3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…met (c5be134); the people's door rebuilt; the s37 handoff — C152, C153 from P107 next

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pes measured, the check's content hole closed, the Tag shown box-neutral; row C154 (s71)

- The shelf templates' 1,207 visual parents ranked by layout shape through layoutOf (385 shapes); nine new fixtures
  recorded on headless Chrome: the top shapes (button-row, text-column, text-row, centred-shell, page-shell,
  header-row), a percent in a scroll with content, the min-content floor, and the Tag.
- layout-check: a child's content now comes from its own children (hugSize, recursively), never from its own
  measured box; with the old rule a percent child in a scroll matched whatever the browser drew.
- layout-map: BOX_NEUTRAL — the Tag (`as`) moves no box (the runtime's sheet resets a Text's margins; a tagged
  Group lays out as a div), so a node that sets it is graded: template parents in reach 498 -> 623 of 1,207.
- 156 boxes match on twenty fixtures; 6 mismatch, all KNOWN: P107-C154 (low, P109's: with Native platform scroll
  on, a percent child of a scrolling column grows into the visible height; off, it is its content) and the
  model's named min-content limit.
- Planted: cross-axis center read as start reddened exactly the unequal-height rows; the old content rule
  reddened the check. Both restored.
- Gates: node-spec suite 31/31, 1,836 passed, 18 skipped, exit 0; tsc clean; browser-check PASS; spec-ledger
  holds at 125/147.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d migration on the records, the backup first, and moves the permission rules with the rename

Ruled 2026-10-09 (README §9 R9): the executor lives in @noodl/backend-contract's src/, so the deployed backend and the conformance specs run one copy. `src/migration.ts` holds dryRunMigration/runMigration (conformance/migration.ts re-exports them), `migrationContextOf(facade)`, `migrationPlanProblems()` (a plan from a body is not trusted: an unknown schema action used to count as a step done) and `rulesAfterMigration()` (a renamed collection's rules move to its new name; a { path } rule's hops are re-said — the field followed, the field matched by, a hop's filter keys — read before the run against the schema as it stands).

The route (admin-only, audited `world.migrate`): no `run` → the dry run, the plan's words, the rules it would move; `run: true` → refuses what POST /admin/schema refuses (system fields, the backend's own collections and account fields), checks the rules after would validate, takes the backup through the backup subsystem (on PostgreSQL the backend cannot: 409 until the person says where theirs is, `backupTakenElsewhere`, kept in the answer and the audit), runs the steps, then moves the rules in the running config for the steps that ran — a run that stops part way answers 500 `partly` and moves only those.

Found and fixed on the way:
- the executor stopped at the first page shorter than 500; under an operator's page cap (`queries.maxLimit`, settable down to defaultLimit) every row past it was neither counted nor moved (P116-ONT-007-pagecap) — it reads until an empty page;
- on PostgreSQL a renamed link field, or a collection a link points at, was read by its OLD name until restart — renameColumn/renameTable edited the cached schema object the declared-types memo is keyed by; a path rule through it answered 500 on the next request (P116-ONT-007-pgrenamememo) — fresh copies, as addColumn since BMG-003;
- on both engines a collection's link to ITSELF kept the old name after renameTable (P116-ONT-007-selflink).

Graded: nodegx-backend-contract 219/219 (tests/migration.test.ts 20, no engine); nodegx-backend ont-007-route 19/19 on SQLite and PostgreSQL over real sockets (a LOCKED backend: the same people seen before and after the rename, the moved `nobody` rule refusing on the next request), plus 14 more specs around it, 298/298. Mutants, each red on its own spec: the old page loop (contract 2, route 2), the rules not moved (route 3), each PostgreSQL in-place edit (ont-007-migration 1 each).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y the words is a step of its own, on both engines

ONT-007 §6.9 #5 (s9, pinned it.failing): an answer the store does not keep (`whenTargetGoes`, `setOnce`, …) projects into a collection's description, which createTable wrote once and nothing changed after — the plan had no step and the Schema card kept the old sentence.

- `IStorageSchema.describeTable?(table, description, columns?)` (18 names; SCHEMA_COVERAGE: uncovered, graded by ONT-007), on SQLite and PostgreSQL (a fresh schema object — the declared-types memo is keyed by it), null/empty takes the words away, a field it does not have is refused;
- `POST /admin/schema` `{ action: 'describeTable' }` (501 without the member, 400 in words, `_`-collections refused, audited `describedTable`); the world route refuses it on `_User`;
- the executor applies it; `planMigration` emits it for a kept collection whose words — its own or a kept field's — changed, after the rules and before the drops, with no reach row (nothing a component, a rule or a verb reads has moved).

Graded: nodegx-project-contract 195/195 (migrate.test 47: four pinned plans now say Person's words again; a sentence said again is [backup, describeTable] with no reach; the same world twice is [backup]; a field's words carried by name); nodegx-backend 18 specs 312/312 on SQLite and PostgreSQL (the s9 case an `it` with a restart read; ont-007-route 23 — words said and taken away, a missing field and _User refused); nodegx-backend-contract 219/219; noodl-mcp world tools 30/30, surface 8,261 unchanged. Mutant: the planner without the step — 5 red.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…escribeTable, three defects found under them; the s12 handoff (R10's on-delete rule, then ONT-008)

- README §9: R8 keep the person's word; R9 the migration executor in @noodl/backend-contract's src/; R10 a link's "when it goes" kept by the backend at delete time (Richard, 2026-10-09, each the recommendation). Status line and the ONT-007 row.
- ONT-007 §6.11: the executor moved, the plan validator, the rules after a rename, POST /admin/world/migrate (PostgreSQL's backup by the person's word), describeTable; graded numbers and mutants; what is not built (a read-only dry run, -renamecolumn, an MCP migrate tool — ask first).
- Bugs: P116-ONT-007-pagecap, -pgrenamememo, -selflink (fixed, 993a3ff); P116-ONT-003-links needs-ruling → open (R10).
- NEXT-SESSION-PROMPT for s12.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… ISL-049

docs:nodes:check was red with two stale pages: ISL-048 (c22c9ac, Keep Pages When
Unmounted) and ISL-049 (a5d6abb, Items Rendered's new wording) regenerated the
enriched catalog but not docs-site/docs/nodes. Regenerated; the diff is exactly
those two pages. Found under NSP-018 AC4 ("docs:nodes:check stays clean").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es in words over MCP, idioms

AC1: catalog:generate loads the specs from source (scripts/node-spec/load-specs.js, now
shared with spec-ledger) and takes a specced port's description from its spec; a runtime
description that says other words throws in both modes, naming the port and both sentences.
Planted in the runtime's counter.ts: catalog:check exit 1. CI's node-catalog job runs it.

AC2 (counting): Patch.says, the case a branch answers in words. scripts/node-spec/rules.js
reads it from the AST and adds the wire half from the patch: "at Max Value with Limits
Enabled → Unchanged; Count Changed stays silent". The catalog carries it as
rules: { [inputPort]: string[] }. Counter has all eight cases; spec-ledger counts 4 of 507
input reducers. Making says required today = 510 tsc errors, so that gate waits.

AC3: get_node_type returns rules on detail:"full" and, narrowed to the asked ports, on the
ports path; not on the default summary (CMP-006's traffic, AWP-005's size contract).
nsp018RulesOnTheWire.test.ts; planted red. Surface 8261 / 8280, no description changed.

AC5: Idiom { says, example } and NodeDecl.idioms, on the 13 NSP-011 T1 specs and the pilot
five; spec-ledger --check fails an NSP-011 T1 spec without one (planted on Or).

Stranger hashes rewritten (spec.ts and seven guarded specs). node-spec suite 31/31,
1,836 passed, exit 0; Counter conforms on the runtime.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…); the s73 handoff

NSP-018 §5 records each AC with its plant, §6 what is left (the words for 503 reducers,
the relationNode factory, pricing rules on the default summary, the docs pages). The
README row and the handoff carry the readings; s73 starts on AC2's words or NSP-019.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ide every delete on both engines

R10 (ruled s11): not a foreign key. `StorageColumn.whenTargetGoes` (goes-too · stays-unlinked · blocks,
the person's words) on a Pointer; `@noodl/backend-contract` `targetGoes.ts` plans the whole delete
read-only (any `blocks`, at any depth, refuses before a row is written), deletes the caller's own
record under their ACL, then empties and cascades as the master key. `AdapterFacade.rawDelete` calls
it, so every door keeps it: /classes (and a cloud function's Delete Record over loopback), BYOB
/api/:table, _batch. A refusal is a 409 `still-linked` naming what links to it (both single-record
doors used to answer every error with 404). Schema route: 400 for a rule that can't be kept, new
action `setWhenTargetGoes` (19th IStorageSchema member, both engines, a BRG-003 case covering it).

Projection: a link to a noun carries the rule and its claim is held (mutant `dropWhenTargetGoes`);
22 claims flipped across four worlds; a text id beside a kind stays not held. Migration: a changed
answer is a `setWhenTargetGoes` step. The claim specs delete through the real facade
(tests/helpers/facade-deletes.ts), never a rule in the conformance context. ONT-005 18 -> 16 of 38.

Graded on SQLite and PostgreSQL: ont-003-target-goes 20/20 (three doors, two levels, a block two
links deep, tree + loop, 501 past a page, the ACL, restart); ont-003-claims 26/26; 53 delete/gate
specs 827/829 then the two count gates fixed (BRG-003 ratchet: a case, not a bump; ops-rate-limit
admin 101 -> 102 was s11's unreviewed migrate route). Controls red then restored: rule skipped ->
exactly the 22 flipped claims fail; cascade before the caller's delete -> the ACL case fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…airs found, ONT-005 16 of 38; the s13 handoff (ONT-008, else -pairs)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…f 507; the reader stops dropping pulses

- `says` on every input-reducer branch of Switch, Condition, String Format and NSP-011's T1 nodes (16 specs fully;
  And and Or have none). Catalog generated → merged; docs unchanged (pages don't render rules yet).
- rules.js kept only literal `emit` elements, so a ternary pulse vanished from the line (Switch's Flip). A said
  branch's emit/outcome must now be literal, a spread is refused; Switch's Flip/State split into cases (no behaviour
  change — mutants come from the returned patches; Switch + Color Blend conformance green on the runtime).
- The reader follows a named helper (same file or a ./sibling import) and a reducer given by name: the Variables'
  words live once in variable-base.ts. `as const` reads as a literal; an idiom is never a branch.
- tests/rules-reader.test.ts (12), two plants each red on exactly their cases.
- P107-D38 filed: Boolean To String's Selector Changed fires on any different raw value, not only a flip.
- census.json / CENSUS.md regenerated from a clean HEAD export (stale at HEAD from other phases' tests; s73's inputs
  change nothing).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…terates its Maps through Array.from (P116-ONT-007-viewerbuild, found by P109 s37)

The viewer compiles the backend contract at ES5 without downlevelIteration; three Map iterations in migration.ts were refused
(TS2802), and a production build with errors writes no bundle. The PR workflow's ci:build:viewer step was red since 993a3ff.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lays out as it does with it on — its gap, its alignment, and what lies outside it hidden (P107-C152, C153, P109-S37-ISCROLLALIGN)

The inner box the better-scroll path puts the children in copied only the direction and the wrap: it now takes the Group's
row and column gap, Align Items and Justify Content. The Group hides what lies outside it on that path, as the native path
does. scroll-iscroll re-recorded; scroll-align added (column and row, with and without wrap, each beside its native twin).
The layout check's three rows flipped. P109-S37-SCROLLCENTRE (the model does not centre along a scroll axis) is KNOWN,
for the ruling C154 asks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… in every task file; the session says at its start (and if the work changes kind) whether Richard should switch

Also commits what two earlier sessions left uncommitted in P112's own files: the direction section (§0.1)
and OWN-011 (2026-10-06), and the scoping session's shared-with-P112 note in NSP-021 (2026-10-02).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… 507, 24 specs; C155 found; the s74 handoff

- `says` on Function, Set Variable, Expression, Variable, Router Navigate, States, Static Data and Cloud Function,
  ranked by how often the shelf templates place them. Work done at the frame's end or on a world answer is said in
  the words; the tail after the arrow stays what the reducer itself sends. A `pending` outcome is left off the wire
  (Router Navigate read "→ pending").
- P107-C155 filed (ours): the spec format has no wired port. Function :386 and Expression :611 run on a new text only
  while Run is not wired; the runtime target never wires, so both targets agree by construction. The words name the
  precondition.
- NSP-018 §5.1 records the computed-patch question for Group / For Each (NOTHING, a spread pulse, run(draft)).
- Catalog generated → merged; docs unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nless the content is longer — as ruled (P107-C154, P109-S37-FILLCAP, ISCROLLPERCENT, CENTRECUT)

Ruled 2026-10-09 by Richard from three worked screens (a checkout, onboarding slides, an inbox). A child now knows how its
parent scrolls (`parentScroll`, beside `parentLayout`; the children memo drops when Enable Scroll, Native platform scroll or
Wrap changes). Along the scroll: a share is never shorter than its content (min-content), so a long "fill the rest" no longer
runs under the next item; with Native platform scroll off, a Fixed % is of the measured window (--ndl-scroll-width/height),
a share starts at 0 and grows into an inner box that is never shorter than the window; centre and end are `safe` on the
Group and the inner box, so a long list starts where scrolling reaches it. Every child of the native and off twins in four
fixtures lands in the same place. The measuring records a Group's inner box (`inner`: how far the off path scrolls).
The model still reads a scroll as endless: 32 boxes KNOWN as ROOM, P109-S37-SCROLLROOM, P107's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the visible window, unless the content is longer"); the viewer build fixed; the s38 handoff

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e P109's df7b11100 lands

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ess the content is longer — as ruled (P109-S37-SCROLLROOM)

Along a scroll axis the window is the room and the reference (a percentage is of the window, a fill shares what it
leaves), a fill never shrinks below its content (a long one pushes the rest on and the parent scrolls), and the main
axis aligns `safe` (centred while it fits, at the start once longer). The 30 ROOM boxes leave KNOWN; the old model
planted back turns exactly those red, and the three new scroll rows in layout.test.ts. Layout suites 819 / 819.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
richardosborne14 and others added 29 commits October 11, 2026 23:57
…, and fix bare labels") and R22 ("The canvas's rule") recorded; P107-S88-OPTIONSCAST fixed (8136577, fecab78); the s89 handoff (the R20 + R21 drives in one editor launch, then allowAsChild measured first)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, and the agent's checker reports it (R23)

The editor has always marked four placements as errors on the node (a logic node
inside another, a child its parent does not hold, children under a node that holds
none, visual roots of two categories); the validator the Problems panel and the agent
server share asked none of them. Measured first, over Richard's 123 projects and the
85 committed ones: types and visual roots agree between the runtime export and the
catalog (0 different), but 60 nodes carry one of the four errors (nightbook 52, an
agent's For Each cards 6, a fixture 2), counted the same in the running editor.

R23 (Richard, 2026-10-12): "Yes, as errors". `@nodegx/project-contract/hierarchy`
holds the rule; the editor's NodeGraphNode warnings and ComponentModel call it, and
the new `invalid-hierarchy` rule reports it at error (existing errors never block an
agent write). The catalog stops mirroring the export's child rules by hand and takes
them from the export, gaining Component Children's `haveComponentChildren` (without
it the rule would have flagged 70 correct nodes).

Also: the enriched catalog the agent server reads had never received R21's
`array -> optionslist` (catalog:merge refused it without a castSemantics entry; now
written) — it also picks up P109's committed `currentPageTitle` text (edda642);
two published examples that put rows inside a For Each now use a template; the
spec-disagreements snapshot is regenerated for s88's bug; the corpus publish rate
moves 39 -> 38 (the new /Post Row); two kit tests name the two placement errors the
kit fixture's /Broken carries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lowAsChild measured, R23 ("Yes, as errors") recorded and built (9da5979); five bugs filed (HIERARCHY and ENRICHEDSTALE fixed; NIGHTBOOK, CFEXAMPLES, XMLEXAMPLE open); the s90 handoff (the kit overlay's mirror, then display name / deprecation)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… rules instead of deriving them again

The overlay's input is the runtime's own node-library export (generateNodeLibrary), which
already makes a Visual node a child and an export root. The overlay re-applied that
derivation by hand, the last of the three mirrors census §2 named. Measured first: over the
kits in 203 projects (65 distinct kit types, 358 entries) the overlay's allowAsChild,
allowChildrenWithCategory, allowAsExportRoot and singleton differed from the export's 0
times; a control Visual entry without allowAsChild differed on two fields. It now copies the
four fields as the built-in catalog has since s89, and a test pins that (planted: the old
derivation back -> red).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…data flow, as on the canvas (P107-S90-EMPTYLABEL)

describe.ts's endpoint read `label ?? displayType ?? type`, keeping "" and naming the
node " [id]"; the editor (NodeGraphNode.label) and the tree rows beside it treat an
empty label as none. 97 nodes in 357 projects. Test with a no-label control; the old
`??` planted back goes red.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…red type is named with its replacement (R24, R25)

Measured first: over 180 exported types, display names and deprecation agree between the
runtime export and the catalog (0 and 0 different). Over 357 projects (258 of Richard's,
122,451 nodes) the gap is per node: 17,130 cards titled by a port (usePortAsLabel: a
Variable's name, an Expression, a Text's text) that the agent named by type, and 52 nodes
of retired types the editor refuses to create while no agent check read the flag.

R24 (Richard, 2026-10-12, "Card title + type"): @nodegx/project-contract/node-title holds
the rule; the editor's BasicNodeType.labelForNode and the agent's describe.ts call it.
Data flow reads `Variable "screenWidth" [v].value`; tree and logic rows carry `title`.
The catalog, enriched catalog and kit overlay carry usePortAsLabel and its truncation.

R25 (Richard, 2026-10-12, "Warn on any"): `replacedBy` on the node definition (types,
register, React forwarding, export, catalog), set on the 16 of 30 deprecated types with a
clear successor (REST2 -> HTTP Request per NDA-011); catalog:generate fails on a missing
or deprecated successor. The validator's `deprecated-node` warns with a suggestion;
get_node_type and list_node_types show replacedBy. The rule found two published examples
teaching retired nodes: the WebSocket chat moves to the current controls, and REST2's own
example says it is retired (the examples gate lets an example keep only the node it
documents). nodeDocBudget moves 8,350 -> 8,450: Text Input's summary gains replacedBy,
+13 measured (8,338 -> 8,351).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s differed); display name and deprecation measured (0 of 180 types differ, the gap was 17,130 card titles and 52 retired nodes), R24 ("Card title + type") and R25 ("Warn on any") recorded and built (7ce9223); P107-S90-EMPTYLABEL fixed; the s91 handoff (drive R24 and R25, then the next §3.2 rows)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…backend and its secret, and a deploy from npm alone

Richard asked whether the npm kit is ready for a no-editor tutorial video (a Duolingo clone
calling Google text-to-speech). Static reads: it builds and previews from npm, but the key can
only sit in the browser, the agent cannot see its screen, and nothing deploys. R18 recorded.
P115's README points at OWN-016, which takes HST-005's VM route forward.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t.json when nothing changed (P112-S11-OPENWRITES), and one open makes one .before-0.4 copy, not two

OPENWRITES: the model's metadata holds dbCollections, systemCollections and dbVersionMajor set to
undefined (read in the running editor); buildProjectV2File counted them and wrote "metadata": {}, so
the save never matched the importer-built baseline and every open wrote the file. Keys holding
undefined are dropped, as JSON.stringify drops them on the way to disk. Unit: tests-unit/own-013
openWrites (3; removing the filter fails 2).

Double copy: Open project... loads the folder twice (openProjectFromFolder, then loadProject), both
before the first save, so R17's copy was made twice (Desk A.before-0.4 and -1, identical, same
second). One copy per folder per run now; 0.3.0's .before-0.3 copy shares the function.

Driven in the dev editor (2026-10-12): a fresh 0.3.0 copy -> one .before-0.4, equal to the original;
a converted project reopened -> 16 of 16 files unchanged, while a real metadata change in the same
session was written (control); the kit's reading-list project kept its id 95de5bf2 (IDREPLACE) and
its nodegx.project.json on first open, and changed nothing on the second.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…clarations, not a re-export (P112-S13-HARNESSTOKENS)

The editor's packaging copied StyleTokensModel/DefaultTokens.ts into render-harness/data. Since
HLS-001 it is a 12-line re-export, so the fallback found 0 of 193 shipped defaults and every
packaged render_report (0.3.0 included) rendered with no spacing, radius or font. It now packages
nodegx-project-contract/tokens.ts, and UNI-012's gate compares the shipped file's contents with
the checkout's; the old mapping fails it alone (1 failed, 7 passed). OWN-014 step 1 recorded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ixed), OPENWRITES' cause measured and fixed, IDREPLACE driven (§7); CREATEDLOST filed; the changelog names HARNESSTOKENS and only PAGEPORTS under Good to know

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ogram maps @nodegx/node-spec to source, and an MCP spec's cast goes through unknown

CI's Typecheck has been red on cline-dev (run 38174808907 at 88366e6): typecheck:backend-tests could
not find '@nodegx/node-spec'. Its tsconfig.tests.json has its own `paths`, which replace the root's, so
the module resolved through node-spec's `exports` to `dist/` (absent on CI; stale locally, missing
DRAWN_PORT_TYPES). Mapped to src/browser.ts, as the same file already does for @nodegx/export, plus
DOM.Iterable for world.ts's FormData.entries(). The next step, typecheck:mcp, then failed on
ont002WorldTools.test.ts:253 (TS2352, a narrowing cast). All eight Typecheck job steps exit 0 locally,
and a --traceResolution sweep finds no other program resolving a workspace package to dist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…harness ships inside @nodegx/mcp

build-render-harness.mjs (prepack; --remove at postpack) bundles the three harness entries into
dist/render-harness with its data and a vendored ws, and builds a production viewer into
dist/src/external/viewer, the packaged editor's layout one level down. render.ts probes it.
Graded from a packed tarball in an empty folder with HOME empty (scripts/own/stranger-render.mjs):
create_project then render_report renders with 193 shipped defaults; the registry's 0.1.0
refuses (control). A planted 3000px text is reported, and P112-S13-CLEANWARNINGS is fixed: the
verdict no longer says "the render is clean" over warnings. AC1 and AC2 met; publish owed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e the type scale's tokens (12 raw px sizes, the `type` ratchet red on cline-dev)

P109 ISL-005's Scripts section, script tab and Uses picker wrote 11, 11.5, 10.5, 12 and 13 px; the
ratchet allows none past its baseline (R1, phase 92). 11/11.5/10.5 → --font-size-xs (hints, tags),
12 → sm, 13 → md. `npm run type`: noodl-editor 622 → 610 = baseline. Undriven: the half-pixel sizes
moved by half a pixel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… ruled order — a value wire leads with value ports, a signal wire with signals

The editor suite's one red spec on cline-dev (CI run 38174808907: 3014 specs, 1 failure, "Expected
'value' to be 'onClick'"). Its wire is Widget.text (string) → valeu; GAM-019 (ruled 2026-09-14) orders
the alternatives by what the wire carries, so `value` leads. The line "signal inputs lead" predates the
ruling. Kept the signal half as its own case (Trigger.onClick → clik → onClick leads).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…stall: the backend ships in @nodegx/mcp, set_function_secret, and deploy works outside a package

build-backend-copy.mjs (prepack) bundles nodegx-backend's one-file service into dist/backend/cli.js
and provision_backend finds it. set_function_secret / list_function_secrets (deferred backend group,
resident surface unchanged) call CWF-009's /admin/secrets; the value is never echoed.
P112-S13-CLOUDDEPLOY fixed: deploy_cloud_functions failed on every npm install and on 0.3.0's
sidecar ("Cannot find package.json"); the bundle now has a package.json beside it (C68's fix),
shipped by npm and by the editor's extraResources; own015CloudBundleCwd with its control.
Graded from a packed tarball (scripts/own/stranger-backend.mjs): provision, secret, deploy, call
answer with audio from a key-checking stand-in for Google; the key is in 0 project files and 0
results, 1 secrets.json, and the control page holding it is found; the no-secret error names it.
P112-S13-SECRETPATH filed (the message carries the server path).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…red as a set, and the two specs that open a committed format-4 fixture in place opt out of R17's copy

test:ci on the cut tree (worktree at 7637269, seed 44986): 3015 specs, 2 failures.
- "Component instances can report accepted children for components": P107 R23 (9da5979) moved the
  derivation to @nodegx/project-contract/hierarchy, which keeps first-seen order where _.union kept
  newest-first. Every reader asks indexOf, so the spec compares contents.
- The run also left tests/testfs/import_proj1.before-0.4/ in the tree: projectimport.js opens the
  committed fixture in place (format 4). Those two opens pass { copyBeforeFormatUpdate: false }, as
  import_proj5's passes { upgradeOnLoad: false } for 0.3.0's copy.
Rerun at the same seed: 3015 specs, 1 failure (the known order-dependent "re-arms a held save",
unchanged), and no copy left in tests/testfs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n on @nodegx/preview), and a deploy no longer publishes the agent's files

The published preview already carried a complete deploy engine; bin/nodegx-deploy.js gives it a
name: npx --package @nodegx/preview nodegx-deploy <project> <out>. From a packed tarball in an
empty folder the made project deploys and renders in headless Chrome with 0 errors; a malformed
project is refused with nothing written. P112-S13-AGENTFILES fixed: .mcp.json, CLAUDE.md,
AGENTS.md and .claude/ are default deploy exclusions (the first deploy published the local
project path and the brief); jest spec with its control. P112-S13-DEPLOYBADJSON filed. AC1 met;
the VM steps need Richard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t 0.2.0, the bugs, and next (a Google key for the stranger round, publish, the VM)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d, NSP-019 "Needs changes" (a pop-out modal), the Frappe note a named list; handoff

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ibly — owed back down (Richard, R19)

The Lint check failed on cline-dev (CI run 38174808907) because the type-escape counts rose since
0.3.0's baseline (169a44a): src +98 (72 in nodegx-node-spec's node specs: drag, component-stack,
array-filter, states, page-router; 9 in modelrequest.ts; 7 in noodl-preview inPage), tests +1,371
(mostly the island and garden MCP specs: cg002Engine +234, ig007Garden3d +125, ig004Island +113, …).
Richard ruled 2026-10-12: raise it visibly, as 0.3.0 did, and record it as owed back down.
Measured in a clean worktree at a6ce18c (no peer's uncommitted file counted): src 1260, tests 2221;
`npm run tsfixme` exits 0 there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t and Test (editor) fixed or ruled, the cut tree's suite at 1 failure by name; G3's release body and feed post; R19 (ratchet raised) and R20 (push cline-dev first); the changelog's Fixed gains the OWN-014/015/016 session's three

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, the changelog entry, and the publish script

create-nodegx pins @nodegx/mcp 0.2.0 (its test holds the pin equal to the checkout). The lockfile's
three workspace versions follow. CHANGELOG "npm packages 0.2.0" passed the content-authoring
offline check. scripts/own/publish-npm.sh publishes tested tarballs in order (preview, mcp,
create-nodegx), skipping versions already on the registry; Richard runs it (2FA).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…8-flash-tts, WAV), and --real runs it against Google

Richard's key is a Gemini key, so the fixture's function now sends generateContent with
responseModalities AUDIO and returns inlineData (audioContent + mimeType). The stand-in mimics
that shape. --real reads GEMINI_TTS_API_KEY from the checkout's .env: from the 0.2.0 tarball the
function returned a 146 kB RIFF/WAVE (2.92 s), and the real key was in 0 project files and 0 results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…app.sh (box) and ship-to-vm.sh (local); not yet run

provision-app.sh is P78's todo provision.sh parametrized (APP/PORT/DOMAIN/CADDY=off) plus deployed
cloud functions (<dataDir>/workflows) and an EnvironmentFile of function secrets. ship-to-vm.sh
builds the site with nodegx-deploy, takes dist/backend/cli.js from @nodegx/mcp, and carries the
local backend's workflows and secrets (over ssh stdin). The run against nexus-1 was refused by
the auto-mode classifier as a production deploy; OWN-016 §7 records the box's measured state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e text "false" too, not only on exactly false (P107-C160, R26)

Drag spec v4 with its changes entry and the regression scenario; control (the old === false) red on exactly it.
The export's dragLib.ts still reads === false (P18's).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… checks green (3015 specs, 0 failures); R21 ship 0.4.0 now, P107/P116's next work is 0.4.1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d by cline-dev

origin/main (2023141, Release 0.3.0 #47) has five commits cline-dev lacks: the v0.2.4 merge, 0.3.0's
docs commit, its two release-PR fixes and the merge. A real merge conflicts on 14 files, and every one
resolves to cline-dev's side: each line main added since 484d9d6 is in cline-dev's copy, except in
generated files (ratchet baselines, cloud-node-library.json) where cline-dev holds the newer
regeneration (P112 OWN-013 §6.1). So the tree is cline-dev's exactly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@richardosborne14
richardosborne14 merged commit 2a258a4 into main Oct 12, 2026
9 of 14 checks passed
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