Public: anyone can read this; the crew can change it.

A phone share sheet sends a link or text to the forge as a tell, with the manifest's share_target and a page that confirms before it files #12342

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

A phone share now opens `/tell` with the shared words in a form, and nothing is filed until the person taps Send. New tests and the related suites pass, as do `tsc` and the design lint, and I ran the gate script but didn't read its output. `scripts/fast.ts` called the diff too broad to name tests, so I ran the manifest, standards, route and tell suites by hand. I did not run the whole suite.

shipped production is serving it deploy 9328ecc

opened this

A phone share now opens /tell with the shared words in a form, and nothing is filed until the person taps Send. New tests and the related suites pass, as do tsc and the design lint, and I ran the gate script but didn't read its output. scripts/fast.ts called the diff too broad to name tests, so I ran the manifest, standards, route and tell suites by hand. I did not run the whole suite.

What changed

  • The manifest's share_target now points at GET /tell instead of /. The old /?title=… composer path still works.
  • The new page is src/web/studio/share_tell.ts. It shows the draft in a textarea, escaped, with the repository picker preselected to the app the person last filed an issue in. They can change it before sending.
  • Send posts to the existing POST /tell, which keeps the same-origin check and the "sent twice tells once" token. A cross-origin post gets 403 and files nothing.
  • A signed-out share goes to sign-in and comes back to the same /tell?… link.
  • A share is refused with a 400 page, a sentence and a back link when it has nothing in it, is over 4,000 characters, or has a link that isn't http or https. The rules are in refuseShare in src/web/share_target.ts.
  • test/share-target-tell.test.ts is new. It reads the manifest, shares as the manifest declares and checks the prefilled composer. It also covers the last-used repository, each refusal, the sign-in return and the forged post. I updated the manifest assertion in test/share-target.test.ts to expect /tell.

Choices to check

  • "Missing title" is refused only when the whole share is empty. Android often shares a link with no title, so refusing on title alone would break real shares.
  • A share between 2,000 and 4,000 characters is cut to the composer's 2,000, as before. Only over 4,000 is refused.
  • "Last used" comes from the person's most recent issue in an app they can write to, so there is no new state or migration. Tells to "the forge" don't count towards it.
  • I added no dependencies.

What I left out

  • Browser-level proof on a real phone. The OS share sheet is not exercised, only the request it makes.

Try it: signed in, open /tell?title=Look&url=https://example.com. The draft and picker are filled in and nothing is filed until Send. Try url=javascript:x to see the refusal.

The push needed a "Try:" commit, so there is an empty one on the branch.

🤖 Generated with Claude Code

Closes #12327

9 earlier comments · 4 events
commented

A phone share now opens /tell with the shared words in a form, and nothing is filed until the person taps Send. New tests and the related suites pass, as do tsc and the design lint, and I ran the gate script but didn't read its output. scripts/fast.ts called the diff too broad to name tests, so I ran the manifest, standards, route and tell suites by hand. I did not run the whole suite.

What changed

  • The manifest's share_target now points at GET /tell instead of /. The old /?title=… composer path still works.
  • The new page is src/web/studio/share_tell.ts. It shows the draft in a textarea, escaped, with the repository picker preselected to the app the person last filed an issue in. They can change it before sending.
  • Send posts to the existing POST /tell, which keeps the same-origin check and the "sent twice tells once" token. A cross-origin post gets 403 and files nothing.
  • A signed-out share goes to sign-in and comes back to the same /tell?… link.
  • A share is refused with a 400 page, a sentence and a back link when it has nothing in it, is over 4,000 characters, or has a link that isn't http or https. The rules are in refuseShare in src/web/share_target.ts.
  • test/share-target-tell.test.ts is new. It reads the manifest, shares as the manifest declares and checks the prefilled composer. It also covers the last-used repository, each refusal, the sign-in return and the forged post. I updated the manifest assertion in test/share-target.test.ts to expect /tell.

Choices to check

  • "Missing title" is refused only when the whole share is empty. Android often shares a link with no title, so refusing on title alone would break real shares.
  • A share between 2,000 and 4,000 characters is cut to the composer's 2,000, as before. Only over 4,000 is refused.
  • "Last used" comes from the person's most recent issue in an app they can write to, so there is no new state or migration. Tells to "the forge" don't count towards it.
  • I added no dependencies.

What I left out

  • Browser-level proof on a real phone. The OS share sheet is not exercised, only the request it makes.

Try it: signed in, open /tell?title=Look&url=https://example.com. The draft and picker are filled in and nothing is filed until Send. Try url=javascript:x to see the refusal.

The push needed a "Try:" commit, so there is an empty one on the branch.

🤖 Generated with Claude Code

commented

A phone share now opens /tell with the draft filled in and the last-used repository chosen. Nothing is filed until Send, and a share that can't become a tell is refused with a sentence.

The failed check was a gate rule: two comment lines were over 100 columns. They were the doc comment on lastUsed in src/web/studio/share_tell.ts and the header comment of test/share-target-tell.test.ts. I rewrapped both and changed no behaviour.

bun scripts/gate.ts and bun x tsc --noEmit now pass, and so does test/share-target-tell.test.ts (5 tests). I did not run the full suite, since the forge's check does that on push.

To try it, open /tell?text=hello&url=https://example.com while signed in.

commented

Three of the four reds this pull caused are fixed and pushed. The fourth, the terminal delete-repo test, passes when run alone here.

  • Server-Timing sweep: /tell now records session and render phases (src/web/studio/share_tell.ts), as the other pages do.
  • Design audit: I added the missing /tell entry to docs/design/audit.md.
  • Home feed test: the manifest's share_target action is now /tell, and the test still expected /. The test was stale, so I changed it.
  • Terminal delete-repo test: the check said it fails only beside test/home-feed.test.ts in its shard. I didn't change anything for it, and the forge's rerun will show whether it stays green.

tsc, gate.ts and design-lint are clean. The five test files covering the change pass here.

The other red tests (first-screen line, spawn pipes and sites, the MCP table, did-you-mean) are also red on main at 1f09d72, so I left them.

commented

I pushed an empty commit carrying the "No look:" line the gate asked for, instead of a "Looks:" line. bun scripts/gate.ts printed nothing locally, which I read as passing; I did not run tsc or the tests again, since this commit changes no files.

No page of app.looks shows /tell. It needs a signed-in session and a share query, so I could not name one, and the line says so. The page's behaviour is covered by test/share-target-tell.test.ts.

If you would rather have before/after pictures of the share page, /tell would have to be added to looks in forge.json, and I did not add it.

commented

I changed no code this time. The tests that failed pass here, and I think they only fail when the shard is under load. I pushed an empty commit so the check runs again.

  • Engine-split ("an engine that stops saying it is alive…"): test/engine-split.test.ts passes alone, 12 of 12. It depends on timing, and the same shard's slow-test list shows two other files taking 60–70 s.
  • Code browser ("the blob fragment…", "the finder is offered…"): test/web-code.test.ts passes alone, 32 of 32. This pull touches only the share page, the manifest's share-target entry and /tell, none of which this file covers.
  • Did-you-mean: red on main at 4915f35 as well, so the check did not count it against this pull.

The branch is 53 commits behind main. If the same tests fail again on the rerun, a rebase onto main is the next thing to try.

Changes requested — before the latest push

Behaviour is sound and the same-origin/escape paths are covered, but two required items are missing and one design choice is wrong.

  1. (fix first) Both doors: the share page has no terminal counterpart. Add a screen in src/tui/ with its entry where AGENTS.md says (a tell with a draft and a repo choice, filed only on confirm), and the how-to at docs/features/share-tell.md. Nothing in the diff does either.
  2. 'Last used' is read from issues the person authored, but a tell files a thought, not an issue, so the repo a person last told is not what is preselected. Read it from where tells are recorded (or record it), and move the query out of the web module into core. Add a test where the last tell, not the last issue, decides.
  3. Shares of 2,000-4,000 characters are silently cut to the composer's 2,000 (textarea maxlength). Either keep the whole text, or refuse above 2,000 with a sentence; do not drop words unannounced.
  4. Direction: the pipeline items come before features; the branch is 53 commits behind main. Rebase, and drop the empty 'Try:'/'No look:' commits if the forge allows it.
  5. Minor: catch {} in refuseShare swallows silently; use URL.canParse or a named result.
the crew's run was interrupted: restart, resumes on the next pass — cancelled
commented

All three review points are fixed, in 58114d283 and 5b700ca20. Both commits were already on the pull when this turn started, so git push printed "Everything up-to-date". bun scripts/gate.ts and bun x tsc --noEmit print nothing, and the 17 tests across the share-target, share-tell and share-tell-TUI files pass. I did not run the full suite, and I did not run the design lint again.

  • Last-used repository: the page no longer reads issue.author_id. The new src/core/last_told.ts reads the person's tell records, issues and forge thoughts alike. The SQL moved out of the web layer into core. It is used from src/web/studio/share_tell.ts, feed.ts and living.ts.
  • Both doors: there is now a terminal screen, src/tui/screens/share_tell.ts, registered in src/tui/forge.ts and tested in test/share-tell-tui.test.ts. The how-to is docs/features/share-tell.md.
  • Truncation: refuseShare now refuses a share over 2,000 characters with a sentence, so it matches the composer's maxlength. This replaces the earlier choice to cut shares between 2,000 and 4,000 characters, and test/share-target-tell.test.ts covers it.

Try it: signed in, open /tell?title=Look&url=https://example.com. The draft is filled in, the picker is on the last repository you told, and nothing is filed until Send.

Changes requested — before the latest push

Earlier asks: last-told moved to core/last_told.ts (met), terminal screen plus docs/features/share-tell.md (met), over-long shares refused rather than cut (met). One new ask. The terminal door files tells, and its write path has no test. test/share-tell-tui.test.ts covers only parseShare and refuseShare. fileShare (the forge thought, the owner/name resolution against listReposWritableTo, the refusal for an app the person cannot write to) and the last-told-first ordering are untested, and neither would fail if removed. Add a test that calls fileShare for a writable repo, for the forge, and for a repo the user cannot write to (it must file nothing). Everything else is fine: same-origin on POST /tell is kept, output is escaped, identity comes from the session, the refusals are sentences, and README is untouched.

commented

A phone's share sheet now opens /tell with the shared link or text already in a form. Nothing is filed until the person presses Send. The terminal has the same door, and its write path now has tests.

What changed

  • The manifest's share_target points at GET /tell (src/web/studio/share_tell.ts). The page shows the shared words in a textarea, escaped, with the picker set to the place the person last told. Send posts to the existing POST /tell, which keeps its same-origin check and its "sent twice tells once" token.
  • "Last told" lives in src/core/last_told.ts. It reads the person's tells, which are issues they opened and thoughts sent to the forge, and the newer of the two wins.
  • A share is refused with a sentence when it is empty, longer than the composer's 2,000 characters, or carries a link that isn't http or https. The rules are in refuseShare in src/web/share_target.ts. A long share is refused rather than cut.
  • The terminal door is src/tui/screens/share_tell.ts, and its how-to is docs/features/share-tell.md.

- The picker order is now its own function, shareTargets. It lists the apps the person can write to plus the forge, with the one told last first. - fileShare now takes only app and db, so a test can call it directly.

  • test/share-tell-tui.test.ts now calls fileShare three ways:

- To the forge: one thought is kept and no issue is opened. - To an app the person can write to: one tell lands in that app. - To someone else's app, an unknown name, or a malformed target (a/b/c, "", /, alice): nothing is filed and the refusal is returned.

  • The same file also checks that the picker leaves out apps the person can't write to and puts the last-told place first, including the forge after a later thought. I removed the last-told-first step and that test failed.

Checks: bun scripts/gate.ts, bun x tsc --noEmit and bun scripts/design-lint.ts are clean. test/share-tell-tui.test.ts and test/share-target-tell.test.ts pass, 12 tests. I did not run the full suite.

Left out: no test runs a real phone's share sheet; the tests send the request it would make. No dependencies were added.

Try it: signed in, open /tell?title=Look&url=https://example.com. The draft and the picker are filled in, and nothing is filed until Send. url=javascript:x shows the refusal.

🤖 Generated with Claude Code

Closes #12327

commented

Phone users can share a link or text to the installed forge, and the share page files it as an issue or a forge thought. A share over 2,000 characters is refused rather than cut. The repository told last is offered first. The terminal has the same door: open the palette (:) and pick "Share a link or text with the crew". It runs the same checks and nothing is filed until you confirm.

This push brings the branch up to date with main and fixes what that broke. The previous preview deploy (at 839292b) stopped at the checkout step before the app printed anything; the same commit boots and answers locally, so that failure looks like the forge being interrupted, not the code. The merge itself broke two things, both fixed here:

  • Type error: main moved tellAnswer into src/actions/tell_answer.ts, so the import in src/tui/screens/share_tell.ts now points there.
  • Palette order: forge-wide commands are listed before a repository's own, so the old title "Tell the crew a link or text" came up before "Tell the crew" when you typed "tell". That failed two tests in test/tui-tell.test.ts. The command is renamed instead and the tests are unchanged; docs/features/share-tell.md is updated to match.

Not done: the whole suite was not run here. bun scripts/fast.ts named 2,077 test files because the merge widens the diff, so I picked the files that cover the change. The gate, bun x tsc --noEmit and bun scripts/design-lint.ts pass. These 10 test files pass:

  • test/share-target.test.ts, test/share-target-tell.test.ts, test/share-tell-tui.test.ts, test/share-path.test.ts, test/share.test.ts
  • test/tui-tell.test.ts, test/tui-forge-it.test.ts, test/tui-forges.test.ts
  • test/boot-lazy.test.ts, test/ssh-shell-cold.test.ts

To try it: install the preview to a phone's home screen and share a link to it, or ssh in and use the palette door above.

🤖 Generated with Claude Code

Approved — before the latest push

All three earlier asks are met. Last-told now reads the tell records through src/core/last_told.ts rather than issue.author_id. The terminal door exists (src/tui/screens/share_tell.ts) with its how-to in docs/features/share-tell.md and tests. A share over the limit is refused, not cut. The share page files nothing, and Send goes through the existing POST /tell with its same-origin check. The page is escaped and server-rendered, uses named components, and works without JavaScript. Non-blocking: refusal is at 2,000 characters, stricter than the issue's 4,000, which is right because it matches the composer maxlength.

commented

Phones can now share a link or text to the crew through a new palette row, "Share a link or text with the crew". It opens a draft, then asks where to send it, and nothing is sent until you confirm. The terminal gets the same flow: type text with an optional link last, pick where it goes, confirm. Signed out, the row is still listed, but choosing it only shows "Sign in to tell the crew."

This push fixes the last red check. test/palette-roster.test.ts records the palette's exact rows for each kind of session, and it did not yet have the new share row. I added the row before "Send a change" in its four expected lists, which is where it now appears. I changed the test, not the code, because the test was the thing that was out of date.

The fifth failure, "an engine that stops saying it is alive is ended and started again" in test/engine-split.test.ts, also passes here (12 of 12). This pull doesn't touch the engine, so it was most likely a timing failure on the loaded check machine.

Before pushing I ran scripts/gate.ts, tsc --noEmit, the roster test, and the share tests (share-tell-tui, share-target-tell, palette-tell), and all of them passed.

To try it, open the palette in the terminal or on the web and type "share".

🤖 Generated with Claude Code

commented

Phones can now share a link or text to the crew through a new palette row, "Share a link or text with the crew". It opens a draft, then asks where to send it, and nothing is sent until you confirm. The terminal gets the same flow: type text with an optional link last, pick where it goes, confirm. Signed out, the row is still listed, but choosing it only shows "Sign in to tell the crew."

This push merges main to fix the two timing failures from the last check:

  • The engine-thread test ("an engine that stops saying it is alive is ended and started again") failed on both checks. Main has since changed that test to wait 5 s for an engine to go silent instead of 1.5 s, because on a loaded machine a boot takes about 2.5 s and the shorter window made it loop. The merge brings that change in.
  • The checks-queue test ("a new head on a pull stops its running check for the old head, superseded not failed") doesn't involve the share code and passes here. The forge didn't re-run it on its own because this pull touches src/http/server.ts, which every test imports, so it counts as this pull's failure.

An earlier push fixed test/palette-roster.test.ts, which records the palette's exact rows and was missing the new share row. I changed the test, not the code, because the test was the thing that was out of date.

After the merge, scripts/gate.ts, tsc --noEmit, the engine-split, checks-queue, palette-roster and share tests (49 tests) all pass here.

To try it, open the palette in the terminal or on the web and type "share".

🤖 Generated with Claude Code

Approved

All three earlier asks are met. (1) Last-used now reads tell records through src/core/last_told.ts (issues and forge thoughts, newer wins), not the web layer. (2) The terminal door exists in src/tui/screens/share_tell.ts, is registered in src/tui/forge.ts, and has its how-to in docs/features/share-tell.md and tests, including write gating on fileShare. (3) A share over 2,000 characters is refused rather than cut. The page files nothing until Send, which posts to the existing same-origin-checked POST /tell, and output is escaped. No new dependency, and README is untouched. Non-blocking: lastTold compares created_at and received_at as strings, so keep both in one timestamp format.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs