Drop the retired draw node from the agent-facing docs - #372
Merged
Conversation
The 🎨 Draw node retired on 2026-07-22 when its Gemini omni model left the NanoGPT catalog. The runtime is correct: the editor skips the type and both headless executors warn on load, then refuse the run. The documentation was not correct. It still listed `draw` as a current node type, so an agent that read these pages wrote a graph that cannot run. - llms-full.txt is generated by scripts/gen-llms-full.mjs and was stale. The 3 `draw` mentions came from index.html and from the 2 library READMEs. All 3 sources dropped `draw` already. Regenerated the file. The regeneration also picks up unrelated README drift that had built up since the last run. - guide/graph-format.html: removed the `draw` table row and the "LLM/Draw" port note. Added a "Retired type" bullet under Loader rules instead. That page documents the file format, so an author who holds a graph from before 2026-07-22 needs to know what the `draw` node in the JSON is and how to repair it. Deletion alone would leave that author with no answer. - guide/run-headless.html: removed `draw` from the runnable node list. - growth/i18n-drafts/zenn-comfyui-ja.md: the unposted Japanese draft advertised Draw, and described it wrongly as a hand-drawing input. Replaced it with Join. Historical records stay untouched: changelog.html, updates.json, feed.xml, docs/ and proof/ still record that the node existed and then retired. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
nanoodle | 571cedf | Commit Preview URL Branch Preview URL |
Jul 30 2026, 12:15 AM |
…ness Two agent-facing guide pages said the opposite of what the executors do, and llms-full.txt in the same commit said the truth. guide/run-headless.html: "Browser-only media-processing nodes (resize, vframes, combine, soundtrack, trim, extractaudio) are not supported headlessly: workflows containing them load with a warning and fail fast at run(), before any network call." guide/graph-format.html: "the headless executors reject them up front, before any paid call." Both are false. The truth, read off the executors: - nanoodle-js src/local-media.mjs implements all 6 ops headlessly: a pure-JS path first (PNG resize through its own PNG codec, PCM-WAV trim, MP4CAT lossless mp4 concat), then ffmpeg/ffprobe on PATH for everything else. - nanoodle-py src/nanoodle/local_media.py has no pure path. All 6 shell out to ffmpeg. - Neither library marks any node type unsupported. nanoodle-py's graph.UNSUPPORTED_TYPES evaluates to the empty tuple; nanoodle-js lists all 6 as local:true in graph.mjs. UnsupportedNodeError is raised only for an UNKNOWN node type (workflow.mjs:183-188, workflow.py:208-215) — which is what a legacy `draw` node is, and the only case the up-front refusal covers. A false "not supported" costs an agent a capability it has, so run-headless.html now carries a per-node, per-language table (which of the 6 are pure, which need ffmpeg), and says when the missing-ffmpeg error actually arrives: at that node's turn, after any upstream paid node has already spent. graph-format.html's "runs" column reads "local media" instead of "browser-only" and points at that table. Freshness guard. Nothing stopped llms-full.txt from going stale again: no check-*.mjs mentioned it and gen-llms-full.mjs was not in the hook. Worse, its clone() returned the cached .tmp-libs/<repo> whenever README.md existed and never re-fetched, so a regeneration could silently reproduce a months-old library README. Today's file was correct by luck. - gen-llms-full.mjs now hard-resets the cached clone to the remote default branch on every run, and fails loudly (naming the fix) when it cannot. --offline reuses the cache and says so; --check regenerates and diffs instead of writing; NANOODLE_JS / NANOODLE_PY read a local checkout instead of cloning. Its parts are exported so a guard can rebuild them. - scripts/check-llms-full.mjs is offline and deterministic. It regenerates every section sourced from this repo (llms.txt, README.md, index.html) and diffs byte for byte, then asserts every node type named in the two libraries' supported-node tables is still registered in index.html — the exact check the three stale `draw` rows would have failed. - .githooks/pre-commit runs it when llms-full.txt, llms.txt, README.md, index.html or either script is staged. - .github/workflows/checks.yml checks out nanoodle-py as well and runs gen-llms-full.mjs --check against both fresh siblings, covering the library sections the offline guard cannot rebuild. llms-full.txt itself is unchanged: it already matched. That is now proven, not lucky — `node scripts/gen-llms-full.mjs --check` passes against freshly fetched clones of both libraries. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mikkel
added a commit
that referenced
this pull request
Jul 29, 2026
The other four places this branch touches — the Examples note, the guide's Elsewhere list, the share-links guide and the README ecosystem row — all say that every app on the showcase is still a nanoodle-team demo, and all but the README also say the entry needs the full link. llms.txt said neither. llms.txt is the agent-facing index, so an agent reading only that file would describe the showcase as a collection of third-party apps, and would hand a reader the shortened link the showcase cannot decode. Say both there too. scripts/gen-llms-full.mjs does not read llms.txt, so llms-full.txt is byte-identical after this commit and the merge with #372 is unaffected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-refs * origin/main: growth: refresh the 4 slipped launches, remove the stale r/mcp gate, add a launch checklist (#373) # Conflicts: # .githooks/pre-commit
nanoodle-py 0.5.0 landed (#11, #12) after this branch generated the file, and it rewrote two README sections the generator copies verbatim: "Quickstart (library)" and "CLI". The committed file described 0.4.0, so the new CI step would have gone red the moment this branch merged. Regenerated with `node scripts/gen-llms-full.mjs`, which re-fetched both cached clones first (nanoodle-py 91741de -> deea945, nanoodle-js a900ed1 -> bff73bb), so this is the current content of both default branches, not the cache. New content, all from the nanoodle-py README: - the breaking input-key rename in 0.5.0, with the per-workflow key table - optional input nodes - run-wide `timeout=` releasing its workers, and media staying fetchable after it - prompt length caps (UTF-16 code units, learned caps, no rewriting) - `--json` printing the same envelope on a failed run, and on a pre-run failure No hand edits. `node scripts/gen-llms-full.mjs --check` passes, both against fresh clones and through the NANOODLE_JS / NANOODLE_PY paths CI uses. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mikkel
added a commit
that referenced
this pull request
Jul 30, 2026
Conflict: llms-full.txt line 144, the built-with-nanoodle ecosystem row. Kept this branch's wording, because that row is copied verbatim from README.md and this branch is the one that changes README.md line 140. #372 leaves README.md alone, so its row was the pre-change text. Re-ran scripts/gen-llms-full.mjs on the merged tree to prove it: the generator reproduces the kept row, and it also pulls in the nanoodle-py 0.5.0 README sections that landed after this branch was measured. The merged file now differs from origin/main on that one line only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mikkel
added a commit
that referenced
this pull request
Jul 30, 2026
* Link the built-with-nanoodle showcase from the product
The showcase had no inbound link from nanoodle.com. Its only mention in this
repo was one row of the README ecosystem table, which no visitor reads. The
best proof that the app builder makes shippable things sold nothing to anybody.
Two in-product links, at the 2 moments where they do work.
1. play.html share popover, last line, after the link and the share targets.
That is the one moment the reader already holds the #a= link a showcase
entry needs. The line is owner-only: body.recipient hides it, because a
recipient did not build the app they are re-sharing. The popover is the
same one the editor's Create app modal embeds, so the create-app moment is
covered by the same element.
2. index.html Examples panel note, beside the existing awesome-noodles link.
The gallery answers "what can I start from". The showcase answers "does
this make anything real".
Both new strings go through i18n like their neighbours: 2 PLAY_I18N_B keys and
2 index.html keys, each in all 5 languages. Verified in a real browser in en,
de and ja.
Also indexed for readers who are not in the app:
- llms.txt gains an Examples section naming both the gallery and the showcase.
- guide/index.html "Elsewhere" and guide/share-links.html get the same 2 links.
- llms-full.txt is regenerated. It is generated, never hand-edited, and it had
drifted: it was missing the whole Ecosystem table (where the showcase row
lives), and it still listed the retired Draw node.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Say what the showcase actually is, and fix the German word for it
Two copy defects in the previous commit.
- The in-product copy sold the showcase as work other people shipped
("finished apps people shipped", "Want to see finished apps built from
graphs like these?"). Every entry there is a team demo, and the showcase
README says so. A reader who clicked through from the editor landed on our
own seed apps. The Examples note, the guide's ecosystem list and the
share-links guide now say the entries are still all ours, and turn that
into the invitation it is: yours would be the first from outside.
- German translated "showcase" as "Galerie" (gallery), the word the same
sentence already uses for awesome-noodles — one word for two repos. Both
German strings now say "Schaufenster", matching es "vitrina", fr/pt
"vitrine" and ja "ショーケース".
The 2 new Examples-note keys replace the 2 old ones in all 5 language maps.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Say what the showcase is in the README ecosystem table too
The table row still read "Showcase of apps people shipped". No third-party app
has landed there yet, and the showcase README says so itself. This is the same
overclaim this branch already removed from the in-product copy, the guide and
llms.txt, so the last place it survived now matches: the apps are all still
ours, and the row invites the reader to add the first one that is not.
llms-full.txt is regenerated from the README, so it carries the same one-line
change and nothing else.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Say which link form the showcase takes, next to the shorten button
The share popover reads top to bottom: copy the link, "Make it shorter —
optional", share to a social site, then this branch's showcase invite. A
contributor who works down that list clicks nanolink first, and setShareUrl()
replaces the link box with the short link. The showcase decodes the app out of
the #a= fragment, so a shortened entry is rejected — the popover walked the
reader straight into the one link form the showcase cannot take.
Name the form in the invite itself, before the copy happens: "it needs the
full link, not a shortened one". Own text node, own PLAY_I18N_B key, all 5
languages, exactly like its neighbours. guide/share-links.html says the same
thing in its showcase paragraph, which also sits right under a shortening
section.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Say in llms.txt too that every showcase entry is still ours
The other four places this branch touches — the Examples note, the guide's
Elsewhere list, the share-links guide and the README ecosystem row — all say
that every app on the showcase is still a nanoodle-team demo, and all but the
README also say the entry needs the full link. llms.txt said neither.
llms.txt is the agent-facing index, so an agent reading only that file would
describe the showcase as a collection of third-party apps, and would hand a
reader the shortened link the showcase cannot decode. Say both there too.
scripts/gen-llms-full.mjs does not read llms.txt, so llms-full.txt is
byte-identical after this commit and the merge with #372 is unaffected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
mikkel
added a commit
that referenced
this pull request
Jul 30, 2026
…e moved surfaces #374 and #376 both changed index.html and play.html, so the two artifacts this guard pins had to be re-derived rather than merged. Nothing here loosens a rule. Baseline. The guard reported exactly one change and it is #376's: `async genVideo(model, prompt, opts, imageDataUrl, onProgress){` gained a second copy in index.html at 9046, the DEMO_CTX override that lets a signed-out sample reach a video result. The distinct count held at 893, nothing entered the shared set and nothing left it, so the only ratchet movement is index.html occurrences 957 -> 958 (and multi-occurrence lines 74 -> 75). The refresh also recorded 2 new look-alike hashes, both from the same commit: index.html:9022 `if(!r.ok) throw new Error(String(r.status));` and index.html:9025's FileReader line. 796 -> 798 hashes over 219 lines. Every delta is attributable to #376; none is a one-sided edit. Sandbox matrix. Its ranges are absolute and #376 pushed index.html down by up to 111 lines and #374 pushed play.html down by 19, so all 25 ranges moved. Each was re-derived and checked byte for byte against its pre-merge content. Left stale, 9 of the 16 cases return the wrong verdict — measured, not assumed. All 16 now pass, row 8 still reporting the same 5 one-sided deletions and 1 occurrence drift at their new positions. docs/twin-drift.md and scripts/twin-drift-worklist.mjs carry the same ranges, so all 216 file:line references in them moved too; the work-list table is unchanged (893, and rows 1 to 6 still free 255 lines). Hook. #372 landed touches_llmsfull and #373 landed touches_launchfacts on the same early-exit line this PR adds touches_twindrift and touches_twindriftcases to. All 4 are unioned in, and each was proved to fire by staging one file for it and watching which check ran; an unrelated file still early-exits. The 2 new checks are renumbered 43 and 44, main's launch-facts and llms-full keeping 41 and 42. Re-measured, and figures corrected where the merge moved them: timings (30 samples per case, load average 3.19-5.68), the baseline byte sizes, the raw unblanked occurrence count, and the guard-suite total, now 55 of 55 passing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two separate untruths on the same agent-facing pages, plus the guard that stops them coming back.
1. The retired
drawnodeThe 🎨 Draw node retired on 2026-07-22. Its Gemini omni model left the NanoGPT catalog.
The runtime is already correct. The editor does not register the type. Both headless executors treat it as an unknown type: they warn on load and throw
UnsupportedNodeErroratrun(), before any network call. The documentation was not correct. Four live surfaces still presenteddrawas a current, usable node type. An agent that reads those surfaces writes a graph that cannot run.llms-full.txt:158listeddrawin the node catalog.llms-full.txt:259andllms-full.txt:378listeddrawin both NanoGPT node tables.guide/graph-format.html:132said "(LLM/Draw vision references)".guide/graph-format.html:158was a full table row for thedrawtype and its fields.guide/run-headless.html:135listeddrawamong the nodes that run headlessly.growth/i18n-drafts/zenn-comfyui-ja.md:81advertised Draw. That draft is unposted, so the claim never shipped. It also described Draw wrongly, as a hand-drawing input.(Line numbers are as of
552b41f^, the tip before this branch.)llms-full.txt is generated by
scripts/gen-llms-full.mjs, and the header says "do not edit by hand". The file was stale. All 3drawmentions came from sources that droppeddrawalready:index.htmlfor the node catalog, and thenanoodle-jsandnanoodle-pyREADMEs for the 2 tables. I regenerated the file instead of editing it. The regeneration also picks up unrelated README drift that built up since the last run, so the diff is larger than thedrawfix alone.guide/graph-format.html loses the
drawrow and the "LLM/Draw" port note. It gains a "Retired type" bullet under Loader rules. That was a deliberate call. The page documents the file format, not the menu. An author who holds a graph saved before 2026-07-22 opens the JSON, finds adrawnode, and comes to this page for an answer. Deletion alone gives no answer. The new bullet states the retirement date, states that old graphs still load, and names the repair:imageto generate,editto edit a wired image.growth/i18n-drafts/zenn-comfyui-ja.md names Join instead of Draw.
Historical records stay untouched.
changelog.html,updates.json,feed.xml,docs/andproof/still record that the node existed and then retired. The changelog entry that announces the retirement is correct and stays.Legacy compatibility is untouched. No runtime code changed.
2. "Not supported headlessly" was false
Review of the first commit caught a second untruth in the same paragraphs, and llms-full.txt in that same commit contradicted it.
Both are false. The truth, read off the executors:
nanoodle-js/src/local-media.mjsimplements all 6 ops headlessly. It runs a pure-JS path first (PNG resize through its own PNG codec, PCM-WAV trim, MP4CAT lossless mp4 concat) and shells out toffmpeg/ffprobeonPATHfor everything else.nanoodle-py/src/nanoodle/local_media.pyhas no pure path. All 6 shell out to ffmpeg.nanoodle-py'sgraph.UNSUPPORTED_TYPESevaluates to the empty tuple;nanoodle-js'sgraph.mjslists all 6 aslocal: true.UnsupportedNodeErroris raised only for an unknown node type (workflow.mjs:183-188,workflow.py:208-215) — which is what a legacydrawnode is, and the only case the up-front refusal covers.nanoodle-mcp/src/ffmpeg-check.mjswarns at startup when ffmpeg is missing, for exactly these 6 types. It would have nothing to warn about if they could not run.A false "not supported" costs an agent a capability it has. A false "supported" costs a failed paid run. So the fix states it per node and per language:
resizetrimcombinevframessoundtrackextractaudioguide/run-headless.htmlcarries that table plus one thing the old text got backwards in the reader's favour: a missing-ffmpeg error does not arrive before any paid call. It arrives when that node's turn comes, so paid nodes upstream have already spent. The page says so and tells you to prove your setup with a media-only graph first.guide/graph-format.html's "runs" column now readslocal mediainstead ofbrowser-onlyand links to that table.3. Nothing stopped the staleness returning
No guard checked
llms-full.txt: none of the 51scripts/check-*.mjsmentioned it, andscripts/gen-llms-full.mjswas not in.githooks/pre-commit. Worse, itsclone()returned the cached.tmp-libs/<repo>wheneverREADME.mdexisted and never re-fetched, so a future regeneration could silently reproduce a months-old library README. The file was correct by luck.scripts/gen-llms-full.mjsnow hard-resets the cached clone to the remote default branch on every run, and fails loudly (naming the fix) when it cannot.--offlinereuses the cache and says so.--checkregenerates and diffs instead of writing.NANOODLE_JS/NANOODLE_PYread a local checkout instead of cloning. Its section builders are exported so a guard can rebuild them.scripts/check-llms-full.mjs(new) is offline and deterministic. It regenerates every section sourced from this repo (llms.txt,README.md,index.html) and diffs byte for byte, then asserts every node type named in the two libraries' supported-node tables is still registered inindex.html— the exact check the three staledrawrows would have failed..githooks/pre-commitruns it whenllms-full.txt,llms.txt,README.md,index.htmlor either script is staged..github/workflows/checks.ymlchecks outnanoodle-pyas well and runsgen-llms-full.mjs --checkagainst both fresh siblings. That covers the library sections the offline guard cannot rebuild, and it needs no network beyond the checkouts.How I verified it
grep -rniE "🎨|\bdraw\b" .over the whole repo, minus.git,vendor/andproof/. The only remaining hits are canvas code (drawImage,drawFinderPattern, "draw rect"), theimagenode's search keyword string atindex.html:6042, and the historical records listed above.node scripts/gen-llms-full.mjs, thengrep -n draw llms-full.txtreturns nothing.node scripts/gen-llms-full.mjs --checkpasses against freshly fetched clones of both libraries, and again through theNANOODLE_JS/NANOODLE_PYlocal-checkout path that CI uses.drawcell into a supported-node row fails it with the node-key message. Renaming a catalog entry fails it with a byte diff naming the line. Editing a cached library README and regenerating fails both the generator's--checkand the guard.for f in scripts/check-*.mjs; do node "$f"; done→ 53 pass, 0 fail. (51 at the branch point, 52 after growth: refresh the 4 slipped launches, remove the stale r/mcp gate, add a launch checklist #373 addedcheck-launch-facts.mjs, 53 withcheck-llms-full.mjs.)scripts/check-js-parity.mjsruns, and does not skip, againstnanoodle-jsat itsmaintip: 24 scenarios match.--no-verify.I did not touch
index.html,play.html,growth/shares.md,growth/launch-checklist.md,README.md, or the built-with-nanoodle links.Updated to
mainbefore mergemainmoved to 89f998c (#373) after this branch was written. I merged it in (no rebase, no force-push). Two resolutions:.githooks/pre-commit— #373 and this branch both edited the same one-line early-exit chain, and both appended a guard numbered41). I unioned them, so the chain now carriestouches_launchfactsandtouches_llmsfull, and #373's guard keeps41)while this branch's becomes42). #373's 3-line comment above the chain is kept.A dropped clause is a guard that never runs, so I proved the union instead of reading it. Staging one modified file into a scratch index and running the real hook with a
nodethat only names the script it was handed:growth/launch-checklist.mdcheck-launch-facts.mjsllms-full.txtcheck-llms-full.mjsREADME.md(in both patterns)check-launch-facts.mjs,check-llms-full.mjsllms-full.txt—nanoodle-py0.5.0 landed (nanoodle-py#11, nanoodle-py#12) after this branch generated the file, and it rewrote two sections the generator copies verbatim:## Quickstart (library)and## CLI. The committed file described 0.4.0, so the new CI step would have gone red on merge. Regenerating adds 88 lines: the breaking input-key rename in 0.5.0 with its per-workflow key table, optional input nodes, run-widetimeout=releasing its workers, prompt length caps, and--jsonprinting the same envelope on a failed run.That regeneration is also the proof the
clone()fix works. The cache held stale checkouts, and the run refreshed both before reading them —nanoodle-py91741de → deea945,nanoodle-jsa900ed1 → bff73bb. The oldclone()would have re-emitted 0.4.0 and said nothing.Related
Same false claim, different repo: nanoodlecom/nanoodle-skill#5 fixes
references/graph-format.md, which called the 6 media nodes browser-only while that skill's ownSKILL.mdsaid the opposite.