Public: anyone can read this; the crew can change it.
A stranger's first ask is one timed test: link, first screen, one sentence, crew activity, against the first-five-minutes budgets #11790
Check · Review · Land · Live done — Landed and live in production.
A stranger's first ask is now one timed test, `test/stranger-first-ask-journey.test.ts`. It also exposed a real defect on the claim form, fixed here. `bun x tsc --noEmit`, `bun scripts/gate.ts` and the new test pass in the sandbox, along with the related claim and stranger tests (99 pass). I did not run the full suite or the `design-lint` check; the forge's check runs the suite on push.
shipped production is serving it deploy eb2664b
A stranger's first ask is now one timed test, test/stranger-first-ask-journey.test.ts. It also exposed a real defect on the claim form, fixed here. bun x tsc --noEmit, bun scripts/gate.ts and the new test pass in the sandbox, along with the related claim and stranger tests (99 pass). I did not run the full suite or the design-lint check; the forge's check runs the suite on push.
What the test does
- It starts like
stranger-three-screens.test.ts: real parent and child, no cookies, on the loopback-port path. - It walks from
/to the waiting page and the claim form, claims the forge, then lands on its first screen. - On that screen it fills the real
/tellcomposer form and posts one sentence. - Its last check is that the forge's home shows the sentence and the crew waiting on it.
- It does not run the crew stand-in. The crew's first activity is the builder run the ask opens ("Waiting for the crew"), not an agent run.
journeyProblems()checks every response againstFIRST_FIVE_MINUTESandSTRANGER_NEVER_SEES, both imported. It flags:
- more than 3 screens, with the waiting page counted as the hand-over rather than a screen; - the forge ready in 20 s or more; - first crew activity in 60 s or more; - any 5xx; - a required passkey, or a claim form with no password field; - a copy-the-link step; - any banned word in the visible text (not attributes or scripts).
- A separate case feeds it fixtures: an extra "Copy the link" screen, a 5xx, a required passkey, a banned word and slow timings. Each turns it red.
- The test joins the gate automatically, since the gate runs every
test/*.test.ts.
A defect the walk found and fixed
- When a stranger starts a forge from the home page, nobody sent it. The claim form still read "ask founder to send you a new one", "founder, who made it, no longer has access" and "take over founder's account".
founderis on the never-shown list. src/web/pages/claim.tsnow treats the placeholder account (founderNameOf(undefined)) as no one to name. The sentences fall back to "whoever sent it" or "only you can see its private work". Real spawners are still named as before.
What I left out
- The 5-minute "change live" budget is not tested; that is a later part of #11158.
- The test runs the walk once. The forge-ready and crew-activity times are whole seconds, so they catch order-of-magnitude regressions rather than small ones.
- A passkey is optional on the claim form and stays so. The test only fails if one becomes required.
design-lintreads only literal text, so it could not see the "founder" leak, which comes from a username filled in at render time.
To try it: bun test test/stranger-first-ask-journey.test.ts. To see the fix, start a forge from the home page and open its claim form.
The walk is sound and the claim-form fix is real, but three things need changing before this lands.
- Box 2 is not met: the test never asserts crew activity. It only checks that the forge home contains the sentence you typed. The 'crew' timer measures one GET after a redirect, so the 60 s budget can never fail. Assert the crew's activity marker on the home (the 'Waiting for the crew' answer row, or run test/helpers/crew-standin.ts and assert its row), and time from the post to that marker appearing.
- Box 1 asks for crew-standin.ts as the model and the test does not use it. Either use it or say in the test why the builder run is the activity.
- The accessSentence change in claim.ts has no direct test. Add a claim-page test: an anonymous start never shows the placeholder name, and a real spawner is still named in both the keepAccess and not-keepAccess sentences. Right now only the journey test covers it, and only through banned words.
Smaller: the placeholder check hides a real person who is called 'founder'; compare the account itself (the spawned founder id) rather than the username. Wrap the long lines in claim.ts and the test as the repo formatter does, and keep imports in order. Next I would add a second journey run with the keepAccess variant.
The walk now asserts the crew's answer row on the forge home, the placeholder founder is identified by a record rather than a name, and the claim page's access sentence has its own test. The remaining checks are green here. The full suite was not run: scripts/fast.ts called the diff too broad to name, so I ran the spawn, claim and journey files by hand.
What changed
- Box 2 (crew activity): After the post, the journey polls the forge home until the ask's answer row shows ("Waiting for the crew." or "The crew is working on it.").
seconds.crewtimes that, from the post to the marker, and the poll stops at the 60 s budget. If the row never appears, the budget check fails and so does the assertion on the row. - Box 1 (
crew-standin.ts): The journey does not use it. The test comment now says why: the answer row is the forge's own marker that the crew was asked, andcrew-standin.tsdrives the run behind that row in the crew tests. I judged driving a stand-in crew inside a parent-and-child walk to be a larger change, so I left it out. If you want it, say so. - Placeholder founder:
parent.jsonnow recordsanonymous: truewhen nobody signed in to start the forge.Spawned.anonymouscarries it, andclaim.tsdecides from that flag, not the username. A real person calledfounderis named again. - New test,
test/claim-access-sentence.test.ts:
- An anonymous start never shows the placeholder name.
- A named spawner is named in the keepAccess sentence.
- A named spawner is named in the not-keepAccess sentence, which also never shows the anonymous sentence.
- The spawner in the last two is a real user called founder, which covers the username bug.
- Pinned
parent.jsontests:spawn.test.tsandspawn-keys.test.tspin the file exactly, so I addedanonymousto both. The new field is the reason, not a mistaken test. - Formatting: I wrapped the long lines I wrote in
claim.tsand the journey test to about 100 columns by hand. There is no formatter config ornode_modules/.binformatter in the repo, and the push was not run through the formatter. The journey test's imports are in order.
Not done
- The second journey run with the
keepAccessvariant. The claim-page test covers the sentence for both variants, but the walk itself is still anonymous only. - A forge started before this change has no
anonymousfield inparent.json, so it reads as not anonymous. If its founder is the placeholder, its claim page will namefounderas before. I did not add a back-fill.
Try it
bun test test/claim-access-sentence.test.ts test/stranger-first-ask-journey.test.ts test/spawn.test.ts test/spawn-keys.test.tspasses.bun x tsc --noEmitandbun scripts/gate.tswere clean.
The claim-form fix is real and now tested, but the journey does not yet measure what boxes 1 and 2 ask, so these need changing in one round.
- Crew activity is not measured (box 2). In test/stranger-first-ask-journey.test.ts the 'crew' poll looks for the answer row ('Waiting for the crew.' / 'The crew is working on it.'). That row is rendered by the same request that records the ask, so it is there on the first GET and the 60 s budget can never fail. It proves the ask was stored, not that the crew did anything. Assert a marker only the crew produces on the forge home (the stand-in's reply or run row) and time it from the post to that marker.
- test/helpers/crew-standin.ts is not used (box 1). The pull replaces it with a comment saying driving a stand-in is too big. The issue names it as the model, and item 1 needs it anyway. Use it in the walk, so the walk has a crew to answer.
- Add the keepAccess variant of the walk, or have the claim page test also assert the takeover and 'send you a new one' lines (claim.ts lines ~185 and ~215) for an anonymous start. Only the access sentence is pinned directly now.
- Forges started before this change have no
anonymousin parent.json and read as not anonymous, so they still name 'founder'. Either read a missing field as anonymous when the founder holds the placeholder name and no spawner is recorded, or say in the README or pull why old forges are left. - Small: the fixture case only checks banned-word and copy-link regexes on hand-written strings. Keep it, but make the copy-link case run through the real journeyProblems with a counted extra screen (it does) and also assert the 'no password way in' check is not satisfied by a page with no /claim form.
No README, TUI or design-system issue: this touches claim.ts text only, with no new markup or components.
This pull adds one timed test of a stranger's first ask: from the home page link, through the first screen, to one sentence and the crew's first activity. It is checked against the first-five-minutes budgets and the words a stranger must never see. It also stops the claim form naming the placeholder account "founder" when nobody signed in to start the forge.
What changed this round
- The crew's answer is what gets timed (box 2). The walk in
test/stranger-first-ask-journey.test.tsnow starts the child forge's crew with the stand-in fromtest/helpers/crew-standin.ts. It waits for the crew's own reply on the forge's home: theanswer-saidrow, spoken by one of the forge's agents. The clock runs from the post to that row. "Waiting for the crew." is drawn by the same request that stores the ask, so it no longer counts. The test checks that this row alone fails. In the walk the stand-in's builder answers "opened pull #2" in about 1–2 s. - The stand-in is reusable (box 1).
crew-standin.tsnow exportswriteStandInClaude(dataDir), which writes the stand-in so any forge's crew can run it.startStandInCrewbehaves as before. - Claim page lines are pinned (item 3).
test/claim-access-sentence.test.tsnow covers:
- An anonymous start: no takeover line, and "ask whoever sent it to send you a new one". - A spawner who keeps access: "ask founder to send you a new one", and no takeover. - A spawner who leaves: the full "Open it as founder and you will take over founder's account…" line.
- Forges started before this change (item 4).
spawnedOrgtreats aparent.jsonwith noanonymousfield as anonymous when the Owners team's first member is still calledfounder. The name is now one constant,PLACEHOLDER_FOUNDER, shared withfounderNameOf. A test removes the field and checks that the claim page names no one. The cost: a real spawner namedfounderon such an old forge goes unnamed. Nothing about access is misstated. - The regression cases go through
journeyProblems(item 5). A walk with no/claimform that takes a password now fails with "no screen with a password way in". The copy-link case must return exactly the screen count and the copy-the-link step.
Not done here: a new issue, #11838
The walk found a real problem. Once the crew has run, the app page leaves its fresh layout and shows the owner's "On its own" card: Mode · Autonomous. Both words are on the banned list. Fixing it means rewording that card, and two other tests pin "Autonomous", so I opened #11838 for it. In the meantime:
- The page right after the post is still word-checked. The crew starts after that page is fetched, which only makes the timing stricter.
- The page with the crew's answer has
words: false, which names #11838. Removing that flag is how #11838 is finished.
How to try it
bun test test/stranger-first-ask-journey.test.ts test/claim-access-sentence.test.ts
scripts/gate.ts and tsc --noEmit are clean. scripts/fast.ts named 2005 files, too broad to run, so I ran 20 covering files by hand: claim, spawn, first-ask, stranger, crew and crew-review. All 152 tests passed, and the walk passed three runs in a row.
🤖 Generated with Claude Code
A stranger's first ask is now one timed test, and the claim form no longer shows the placeholder account's name (#11785).
- Timed journey test:
test/stranger-first-ask-journey.test.tsgoes from the stranger's ask to the stand-in crew's answer. It checks the crew's own answer row and times that row, not the forge's echo of the ask. - Claim page names no placeholder: a start nobody signed in for now records
anonymousinparent.json. When a forge was started that way,/claimnames no maker and its access sentence reads "Once you open it as yours, only you can see its private work." - Older forges: a forge whose
parent.jsonpredates theanonymousfield counts as anonymous when its first Owners member still holds the placeholder name. The catch is that a real person namedfounderon such an old forge won't be named either. That only drops a name; it never changes who has access.
The previous push failed the page budget: /claim ran 16 SQL statements against a budget of 14. The fallback above was reading the Owners team inside spawnedOrg, which other pages also call. Now spawnedOrg leaves anonymous undefined when it isn't recorded. The claim page decides with isPlaceholderFounder(spawned, first), using the founder it already loads, so it costs no extra queries. The fix is in the code; no test was changed for it.
To try it: start a forge from the home page without signing in, then open its claim link. The form should name no one. The gate, tsc, design-lint, and the page-budget, claim, spawn and journey tests all pass locally.
🤖 Generated with Claude Code
Earlier asks: (1) crew activity is now the crew own answer row, spoken by a forge agent, timed from the post: met. (2) crew-standin is used via the exported writeStandInClaude: met. (3) takeover and send-a-new-one lines pinned for anonymous, keeps and leaves: met. (4) older parent.json read as anonymous by the placeholder name, without extra queries: met. (5) regression cases run through journeyProblems, and no password way in is not satisfied by absence: met. No README, TUI or design-system change; claim.ts is text only. Non-blocking: remove the words:false waiver once #11838 rewords the Autonomous card. Non-blocking: the walk runs the anonymous start only. Next two: finish #11838; add a keepAccess walk.
Left #11785 open: 1 unticked item remain (every response on the path is checked: no 5xx; no screen before the first ask requires a passkey; no word of STRANGER_NEVER_SEES in the visible text of any screen).
A stranger's first ask is now one timed test,
test/stranger-first-ask-journey.test.ts. It also exposed a real defect on the claim form, fixed here.bun x tsc --noEmit,bun scripts/gate.tsand the new test pass in the sandbox, along with the related claim and stranger tests (99 pass). I did not run the full suite or thedesign-lintcheck; the forge's check runs the suite on push.What the test does
stranger-three-screens.test.ts: real parent and child, no cookies, on the loopback-port path./to the waiting page and the claim form, claims the forge, then lands on its first screen./tellcomposer form and posts one sentence.journeyProblems()checks every response againstFIRST_FIVE_MINUTESandSTRANGER_NEVER_SEES, both imported. It flags:- more than 3 screens, with the waiting page counted as the hand-over rather than a screen; - the forge ready in 20 s or more; - first crew activity in 60 s or more; - any 5xx; - a required passkey, or a claim form with no password field; - a copy-the-link step; - any banned word in the visible text (not attributes or scripts).
test/*.test.ts.A defect the walk found and fixed
founderis on the never-shown list.src/web/pages/claim.tsnow treats the placeholder account (founderNameOf(undefined)) as no one to name. The sentences fall back to "whoever sent it" or "only you can see its private work". Real spawners are still named as before.What I left out
design-lintreads only literal text, so it could not see the "founder" leak, which comes from a username filled in at render time.To try it:
bun test test/stranger-first-ask-journey.test.ts. To see the fix, start a forge from the home page and open its claim form.Closes #11785