Skip to content

Link the built-with-nanoodle showcase from the product - #374

Merged
mikkel merged 6 commits into
mainfrom
growth/link-the-showcase
Jul 30, 2026
Merged

Link the built-with-nanoodle showcase from the product#374
mikkel merged 6 commits into
mainfrom
growth/link-the-showcase

Conversation

@mikkel

@mikkel mikkel commented Jul 29, 2026

Copy link
Copy Markdown
Member

What breaks today

The showcase at built-with-nanoodle
is an orphan. Nothing on nanoodle.com links to it. Its only mention in this
repo is one row of the README ecosystem table, which no visitor reads. The
showcase is the strongest available proof that the app builder makes real,
shippable things, and it sells nothing to anybody.

What this change does

Two in-product links, at the 2 moments where they convert.

1. The share popover in play.html. One line, last in the popover, after
the link row and the share targets. That is the one moment the reader already
holds the #a= link that a showcase entry needs:

Proud of it? add your app to the showcase ↗ — it needs the full link, not a shortened one.

The line is owner-only. body.recipient #sm-showcase{display:none} hides it
for a share-link recipient, who did not build the app they are re-sharing.
This is the same gate the mobile bar already uses for #export. The editor's
✨ Create app modal embeds this same play.html chrome, so the create-app
success moment gets the link from the same element.

The trailing clause is not decoration. "Make it shorter — optional" sits a few
lines above in the same popover, and a click there calls setShareUrl(short),
which replaces the link box with the short link. The showcase decodes the app
straight out of the #a= fragment, so its checker rejects a shortened entry.
Read top to bottom, the popover walked the reader into the one link form the
showcase cannot take. Naming the form in the invite closes that dead end before
the copy happens. guide/share-links.html says the same thing in its showcase
paragraph, which also sits directly under a shortening section.

2. The Examples panel note in index.html. Beside the existing
awesome-noodles link, in the same sentence shape:

Made an app from one? built-with-nanoodle — the showcase. Every app on it is
still one of ours; make yours the first that isn't. One line.

The gallery answers "what can I start from". The showcase answers "does this
make anything real".

Every entry in the showcase today is a nanoodle-team demo, and its own README
says so. So the copy says so too, in all 5 places this PR touches: the Examples
note above, the guide's "Elsewhere" list, the share-links guide, llms.txt,
and the README ecosystem row. An empty-of-outsiders showcase is an invitation,
not a wall of other people's work, and a reader who clicks through now gets
what the copy promised.

llms.txt was the last of the 5 to say it. Until the final commit here it
described the showcase but named neither the "still ours" fact nor the
full-link rule, so an agent that reads 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. Both sentences are in it now.

The README ecosystem row is fixed here too. It read "Showcase of apps people
shipped"; it now reads "Showcase of finished apps, every one still ours — use
when you want to see what nanoodle makes, or to put yours on the list".
llms-full.txt is generated from that README, so it carries the same one-line
change. scripts/gen-llms-full.mjs does not read llms.txt, so the final
commit left llms-full.txt byte-identical.

I deliberately did not touch the editor's own Share popover. That link is a
workflow (#g=), and the showcase takes apps.

Where the full-link rule comes from

The rule is already on the showcase repo's main, in CONTRIBUTING.md
lines 8–9, inside step 2 "Get the link", which starts on line 7:

Use the full-length link, not a shortened one — the checker (and future
embeds) need the payload in the fragment.

I read that from main today through the contents API, not from a branch.

So the copy this PR ships is true today, and this PR is independently
mergeable
. It is not contingent on nanoodlecom/built-with-nanoodle#1.
That PR does edit CONTRIBUTING.md, but those 2 lines appear in its diff as
unchanged context — it adds a "no third-party app has landed yet" note, a
browser-edit link and a longer npm test paragraph around them. An earlier
revision of this description sourced the rule to #1. That citation was wrong.

Related, but not a dependency: nanoodlecom/built-with-nanoodle#1 fixes 2 dead
links inside the showcase itself.

i18n

Every new string goes through the existing path, like its neighbours.

  • play.html: 3 new PLAY_I18N_B keys — "Proud of it?", the link label, and
    the full-link clause — each with all 5 translations.
  • index.html: 2 new keys in each of the 5 language maps (10 added lines),
    split at the anchor exactly like the awesome-noodles line above it, so the
    repo name stays data-no-i18n and each surrounding text node is its own key.

One correction inside this branch, for the record: commit 0892685, the first
commit here, wrote the German word for the showcase as "Galerie" in both files
(2 occurrences, 1 per file). Commit d46e9a1, the very next commit and still on
this branch, removed both and wrote "Schaufenster". Nothing shipped in between
— "Galerie" appears 0 times in index.html and 0 times in play.html, both on
main and at this branch's head. This was a defect this branch introduced and
this branch fixed, not a pre-existing one. "Schaufenster" matches es "vitrina",
fr/pt "vitrine" and ja "ショーケース".

scripts/check-i18n-coverage.mjs passes, so all 5 maps stay at parity.

Agent-facing and docs index

  • llms.txt gains an Examples section naming the gallery and the showcase,
    with a line each on when to use which.

  • guide/index.html "Elsewhere" list gains both repos.

  • guide/share-links.html gains a "Where shared apps end up" section. A share
    link is the whole distribution step, so that page is where a reader asks the
    question the showcase answers.

  • llms-full.txt is regenerated with node scripts/gen-llms-full.mjs. That
    file is generated and its header says not to hand-edit it.

    Re-measured against the current base. The 126-added / 22-removed figure an
    earlier revision quoted here was measured against dbd4543, when this file was
    missing the whole Ecosystem table and still listed the retired 🎨 Draw node.
    Drop the retired draw node from the agent-facing docs #372 has since landed and its own regeneration corrected both of those. So
    against main as it stands (6d7c065) this PR changes exactly 1 line of
    llms-full.txt
    — line 144, the showcase row. diff between the merged file
    and main's reports that 1 row on each side and nothing else.

    "Ecosystem" appears 1 time in the merged file and "Draw" 0 times, as before.
    Every line comes from committed sources, and re-running the generator on the
    merged branch leaves the working tree clean, so the committed file is exactly
    what the generator produces.

The llms-full.txt collision with #372 — resolved, no longer a prediction

#372 has landed (6d7c065). The collision an earlier revision of this
description simulated is now merged for real, in commit 924ca03 on this branch:
git merge origin/main, run with rerere disabled so the genuine conflict
appeared.

What conflicted. 1 file, llms-full.txt. 1 hunk. Markers at lines 144,
146 and 148 — one line on each side, the built-with-nanoodle row of the
Ecosystem table. The other 15 paths the merge brought in (from #372 and #373)
merged clean, and neither .githooks/pre-commit nor
.github/workflows/checks.yml conflicted: this branch never touched either
file, so main's unioned versions came through as-is. Both new clauses are
present in the merged hook and both fire — see the guard trace below.

Why this branch's row wins, checked in the generator rather than assumed.
localParts() in scripts/gen-llms-full.mjs pushes embed(mainReadme, …)
under the heading ## nanoodle (the site), and embed() only demotes
headings, drops images and absolutizes links. So the Ecosystem row in
llms-full.txt is a verbatim copy of the row in README.md. This PR is the
one that changes that README row (line 140); #372 leaves README.md alone, so
#372's row was simply the pre-change text carried forward. Keeping this
branch's row is the only resolution the generator can agree with.

Corrected: keeping the row is not the same as keeping the file. The earlier
revision said "the resolved file is then identical to this branch's
llms-full.txt". That was true when it was measured and is false now.
nanoodle-py 0.5.0 landed (nanoodlecom/nanoodle-py#12) and #372's regeneration
already carries its new README sections, which this branch's copy of the file
predates. Re-running the generator on the merged tree therefore rewrote 88
lines into the file (71 of them non-blank): the optional-inputs paragraph, the
"Breaking in 0.5.0" section, the timeout= paragraphs, the prompt-caps section
and the --json failure section. Taking this branch's file whole would have
left llms-full.txt stale against the sibling READMEs and failed #372's own
--check.

So the rule for llms-full.txt is the second half of the old sentence, not the
first: re-run node scripts/gen-llms-full.mjs. Keep this branch's row to
clear the markers, then regenerate.

Result. The merged llms-full.txt differs from origin/main's on exactly
1 line, line 144, and node scripts/gen-llms-full.mjs --check is clean on it.

#372's 2 new guards both pass on the merged branch:

  • scripts/check-llms-full.mjs → "✓ check-llms-full — locally-sourced sections
    match; 6 supported-node rows name only registered types"
  • node scripts/gen-llms-full.mjs --check → "✓ llms-full.txt matches the
    current sources"

The --check ran twice: once letting the generator make its own shallow clones
of the 2 library repos, and once CI-style with NANOODLE_JS / NANOODLE_PY
pointed at fresh checkouts of them, which needs no network. Both green. The
checkouts were at nanoodle-js bff73bb and nanoodle-py deea945, the current
default-branch heads. Do not point those variables at a long-lived local
clone
— the one on this machine still sat at nanoodle-py 0.4.0 and would have
reported a false failure.

CI

All 3 checks on the current head, 924ca03 — the merge commit, so CI ran
against main as it stands
— pass. Read back from the GitHub check-runs API
after the push. All times are UTC on 2026-07-30:

Check Result Completed
Workers Builds: nanoodle success 00:25:14Z (build 6bd62281)
parity success 00:25:44Z
checks success 00:26:33Z

The checks job is the one that matters most here: it is where #372's
node scripts/gen-llms-full.mjs --check now runs, against fresh checkouts of
nanoodle-js and nanoodle-py.

mergeable is MERGEABLE and mergeStateStatus is CLEAN. The earlier
revision's figures for head f2a5c4e are superseded; that head predates the
merge.

One reading note for whoever checks this: the legacy
/commits/<sha>/status endpoint reports pending for this commit. It has 0
entries in statuses — all 3 checks are check-runs, not commit statuses — and
that endpoint returns pending for an empty set. gh pr checks and the
check-runs endpoint are the ones to read.

Across the 6 commits of this branch there are 18 check-runs, 3 per commit. 17
are green.
The 1 red is Workers Builds: nanoodle on the earlier head commit 57cfd3d, at
05:10:36Z (build 78e1e5e1).
checks and parity were green on that same commit. The failure was not in
the diff:

I could not read Cloudflare's own build log. The API token in the repo .env
does work for the deploy path — it lists /workers/scripts and the nanoodle
version history — but it returns 12006 Invalid token on every
/accounts/{id}/builds/... path, re-checked today. (It also fails
/user/tokens/verify with code 1000, which is a separate permission and does
not affect the calls that do work.) A token with "Workers Builds
Configuration: Read" would close that gap for the next person; the deploy path
itself is unaffected.

Correction: the "asset files" figure

An earlier revision of this description said the dry-run "reads 643 asset
files". That number is not an asset count, and quoting it as one was
misleading. Both halves are re-measured on this machine at the final commit,
with wrangler@4.115.0:

  • Clean detached worktree of f2a5c4e, env -u CLOUDFLARE_API_TOKEN npx wrangler@4.115.0 deploy --dry-run: 346, on 5 of 5 runs.
  • The same command in my development worktree: 647, on 5 of 5 runs.

Both figures are stable — no spread across the 5 samples on either side. Both
depend on the tree they are read in, not on the machine
: 346 is what any
clean checkout of this commit gives; 647 counts scratch content only my
worktree holds.

The 643 came from that same development worktree, at a moment when it held
less scratch content. The worktree carries 2 gitignored directories a clean
checkout does not: .tmp-libs/ (226 files in 71 directories — the scratch
clones of nanoodle-js and nanoodle-py that scripts/gen-llms-full.mjs makes)
and proof/showcase-note/ (3 files in 1 directory — this PR's browser
harnesses, added after the 643 reading). 346 + 226 + 71 = 643 exactly, and
adding the harnesses' 3 files and 1 directory gives the 647 I read today.

The figure counts directories as well as files, and counts paths that
.assetsignore excludes
. Two probes on the clean worktree, each read twice,
each returning to 346 after cleanup:

  • 11 new files in 2 new directories moved 346 → 359, that is +13, not +11.
  • 5 new files inside proof/, which is in .assetsignore, moved 346 → 351.

With WRANGLER_LOG=debug the line prints its list: exactly 346 path entries,
and they include /proof (a directory on disk) and /proof/COMPARISON.md. So
the figure is a census of the directory tree, not a count of what deploys.

What deploys is a different walk. In the wrangler 4.115.0 bundle the asset
walker getFilesInFolder skips ALWAYS_IGNORE, which is
Set(["node_modules"]), and skips every entry whose name starts with .
unless it is in HIDDEN_FILES_TO_INCLUDE, which is Set([".well-known"])
then .assetsignore filters what is left. Confirmed against a local
wrangler dev on the clean worktree, with a scratch
.tmp-libs/nanoodle-js/README.md and a non-hidden control directory in place:

Path Status
/llms.txt 200
/zzscratch/x.txt (control: not hidden, not in .assetsignore) 200
/.tmp-libs/nanoodle-js/README.md 404
/proof/REPORT.md (in .assetsignore) 404
/scripts/gen-llms-full.mjs (in .assetsignore) 404
/README.md (in .assetsignore) 404
/wrangler.jsonc (in .assetsignore) 404

So a scratch clone cannot reach production through this path, and no
.assetsignore change is needed. The dry-run figure should be read as "the
config validates and the command exits 0", which is all this PR ever needed
from it.

How I verified it

Every figure below was re-measured on the merged branch head, 924ca03, not on
the old base.
The counts moved, because #372 and #373 each added a guard.
Sample counts are stated wherever a figure can vary.

Guards, all green:

  • I staged the branch's full diff against origin/main (7 files: README.md,
    guide/index.html, guide/share-links.html, index.html, llms-full.txt,
    llms.txt, play.html54 insertions, 3 deletions) and ran
    .githooks/pre-commit over it. Exit 0, 305 lines of output, 267 of them
    carrying , 0 carrying . The diff is much smaller than the 179/24 an
    earlier revision quoted because main now carries Drop the retired draw node from the agent-facing docs #372's regeneration of
    llms-full.txt, so this PR's share of that file is down to its 1 row.
  • A bash -x trace of that same run shows which guards fired: the hook can
    invoke 53 scripts, and 51 ran. The 2 the staged file set does not trigger
    are check-sw-routing and check-updates. The 51 include both clauses the
    merge brought in — check-llms-full (from Drop the retired draw node from the agent-facing docs #372) and check-launch-facts
    (from growth: refresh the 4 slipped launches, remove the stale r/mcp gate, add a launch checklist #373) — plus check-html-js, check-hidden-css,
    check-i18n-coverage, check-policy, check-lang-pages, check-export,
    check-share-link, check-create-app, check-drifted-model,
    check-bridge-consent, check-share-samples, sync-examples --check and
    check-prompt-caps. Most guards print prose rather than their own filename,
    so the trace is the evidence, not the output.
  • Separately, every scripts/check-*.mjs guard runs clean: 53 of 53 pass, 0
    fail
    (51 last time; Drop the retired draw node from the agent-facing docs #372 added check-llms-full.mjs and growth: refresh the 4 slipped launches, remove the stale r/mcp gate, add a launch checklist #373 added
    check-launch-facts.mjs). The 2 in that glob the hook never invokes are
    check-example-models and check-lora-models, which are the monthly cron
    guards. The sweep bumps lora-models.json. I ran it in a throwaway worktree,
    so the mutation never reached this branch — git status on the branch
    worktree is empty and lora-models.json is in no commit here.
  • check-i18n-coverage on the merged tree reports: index.html 512 keys;
    play.html runtime 82 + builder 72 keys × 5 languages, chrome covered.

Real browser, headless Edge over CDP, serving the worktree from a Node static
server. No key seeded, no shorten button clicked, so no network call leaves the
machine and no API spend. Both harnesses were re-run on the merged tree
(924ca03)
, so the German strings are read back after the merge, not only
before it. Both exited 0.

  • play.html, English, app loaded, share popover open. #sm-showcase reads
    back exactly "Proud of it? add your app to the showcase ↗ — it needs the full
    link, not a shortened one.", display: block, href is the showcase.
  • play.html, German. Same popover, read back in full: "Stolz darauf? füge
    deine App dem Schaufenster hinzu ↗ — dafür braucht es den vollständigen Link,
    keinen gekürzten." "Schaufenster" present, "Galerie" absent, no English left.
  • The shorten section is in the same popover in both runs — its heading reads
    back as "MAKE IT SHORTER — OPTIONAL" / "KÜRZER MACHEN — OPTIONAL" — so the
    2 lines a contributor reads in sequence were checked together, not apart.
  • play.html loaded from a real #a=u… share link: body.recipient is true
    and #sm-showcase computes to display: none. The recipient gate holds.
  • index.html with noodle_lang=de, Examples panel open, full #examplesnote
    read back: "…diese stammen aus awesome-noodles — remixe eins und schick uns
    deins. Daraus eine App gebaut? built-with-nanoodle — das Schaufenster. Alle
    Apps darin sind noch unsere eigenen; mach deine zur ersten, die es nicht ist.
    Eine Zeile." No "Galerie", no English left in the note.
  • 9 of 9 browser assertions pass in the play.html run, 5 of 5 in the
    index.html run.

curl -sL -o /dev/null -w '%{http_code} %{time_total}' on
https://github.com/nanoodlecom/built-with-nanoodle returns 200 on 5 of 5
samples, in 0.45 s to 1.24 s from this machine. The timing is network-dependent
and will differ elsewhere; only the 200 matters.

Not verified

  • The README ecosystem row, llms.txt and the regenerated llms-full.txt are
    text only. No browser check applies to any of them.
  • es, fr, pt and ja renderings of the new play.html line are checked in the key
    map only. de and en were read back in the browser.
  • The Cloudflare build log for 78e1e5e1 itself. The token cannot read it, so
    the cause of that one red build is inferred from the version-upload gap, not
    read from the log.
  • curl against the showcase URL was measured on the earlier head and not
    repeated on 924ca03; this branch does not change that URL.
  • No paid workflow ran. No API spend in this change.

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>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
nanoodle 924ca03 Commit Preview URL

Branch Preview URL
Jul 30 2026, 12:25 AM

mikkel and others added 5 commits July 28, 2026 22:30
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>
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>
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>
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>
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
mikkel merged commit fd9b459 into main Jul 30, 2026
3 checks passed
@mikkel
mikkel deleted the growth/link-the-showcase branch July 30, 2026 00:29
mikkel added a commit that referenced this pull request Jul 30, 2026
* origin/main:
  Link the built-with-nanoodle showcase from the product (#374)
  Drop the retired draw node from the agent-facing docs (#372)
  growth: refresh the 4 slipped launches, remove the stale r/mcp gate, add a launch checklist (#373)
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>
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