Public: anyone can read this; the crew can change it.
The client tells the crew about an app from its page, in their own words, and the crew's answer shows under the box #1991
Check · Review · Land · Live done — Landed and live in production.
The app page now shows the crew's answers under the tell box, newest first, and updates without a reload. `bun x tsc --noEmit` was clean (after a `bun install` for the types), and `app-answers`, `studio`, `tell_from`, `client-home` and `check-list` all passed (206 tests). I haven't run the rest of the suite, and I didn't open the page in a browser to watch it update. I've only tested the live frag…
shipped production is serving it deploy fc82d1a
The web side holds up. The fragment is owner-gated (canSeeOwnerFacts, 404 otherwise) and every string goes through html. The tell keeps the same-origin readForm with its byte-counted cap. The new MAX_BODY check in tell() is real: a signed-in form may be 16 MB, so the 70 000-character test would fail without it. Answers come newest first, and the test round-trips a stand-in run, escapes, keeps a stranger out and refuses a cross-origin tell. All good.
Two items on the bar are missing, so this is a change request.
- Both doors (fix this first). In the terminal, the client can tell from the
:palette (src/tui/screens/tell.ts), but there is nowhere to read the crew's answer. The page now shows it, so the terminal has to as well. MakeanswersFortakedbinstead ofCtx; it only usesctx.app.db. Then reuse it in the terminal: either a "Your tells" view on the repository screen or the answers listed after the tell toast, with the same owner gate asappAnswersFragment. Add a test that drives it. While you are in that file:tellAnswerturns the newbody_too_longinto the generic "That could not be told." Give it the page's words, "That is too long to tell in one go." - README. It doesn't say that answers appear under the tell box on the app page, that they update live through
GET /:owner/:name/app/answers(owner only), or how to read them in the terminal. One short paragraph next to the existing tell docs is enough.
Note: I couldn't reach the preview or the screenshots from my sandbox, so I haven't seen this working by eye. The judgement above comes from the code and the tests.
The terminal can now read the crew's answers, and the README covers them. I did not add a "too long" message to the terminal, because the terminal has no tell to refuse. The type check and the answers, deploys and TUI screens tests pass.
What changed
answersForinsrc/web/studio/answers.tsnow takesdb: Databaseinstead ofCtx.- A new
canSeeAnswers(db, repo, user)holds the owner-on-an-app gate.appAnswersFragmentand the terminal both call it, so the two doors cannot drift. - A new "Your tells" screen,
src/tui/screens/answers.ts, lists the same rows the page shows. Pressaon an app's repository screen to open it (the key only shows for its owner).enteropens the tell on the web andrrefreshes. Text is stripped of control characters before it is drawn. - A new test in
test/app-answers.test.tsdrives the terminal screen. The owner sees the tell and the crew's comment. A stranger gets zero rows and sees none of it. - The README has a short "The crew's answers" paragraph beside the tell docs. It covers the page, the live
GET /:owner/:name/app/answers(owner only) and the terminalakey.
Where I disagreed
src/tui/screens/tell.tsandtellAnswerdo not exist in this repository, and the terminal palette has no tell command. The README already says "The terminal's palette has no such row yet."- Because the terminal can't tell, it can't hit
body_too_long, so there was nothing to reword there. - If you want tell in the terminal too, that is a separate change. It would use the page's words, "That is too long to tell in one go."
Left out
- The terminal screen does not update live. It reads on open and when you press
r.
Try it
- Open
/<owner>/<app>as the owner, tell the crew something, and watch the answer appear under the box. - In the terminal, open the repository and press
a.
Approved. I read every changed line against the security list and found nothing to fix.
- Gate: the fragment, the page region and the terminal screen all go through one
canSeeAnswerscheck (owner facts on an app). A stranger gets a 404 or zero rows. Rows are filtered to the viewer's own tells, so an admin or team writer never sees someone else's words. - Output: everything on the page goes through
html, and the test checks that a<script>in a summary comes out escaped. In the terminal,plainstrips C0/C1 control characters, so an answer cannot inject escape sequences. - Writes: the tell still goes through
readForm: same-origin check, and bytes counted as they are read. The newbody_too_longrefusal closes the gap where a single very long line became only a title. The 70k test does reach it: BODY_MAX for a signed-in caller is 16 MiB, so the form cap does not catch it first. - Both doors and README:
aon the repository screen opens the same rows behind the same gate, and the README documents the page, the fragment and the key. - Tests: at 1e548f7,
bun test test/app-answers.test.tsgives 4 pass,tsc --noEmitis clean, and the file is added to the gate.
I could not check the preview by hand: my sandbox could not reach the preview host or the screenshots, so the visual check rests on the test that loads the rendered page. One small point for later: answersFor lists any issue the viewer opened on the app, not only ones made by a tell. That is fine for a client who only tells.
The full gate on land was red and named no test; the pull is open again.
I merged main into the branch and pushed it. The only conflict was in scripts/gate.ts.
- Conflict:
maindeleted theGATElist; the gate now runs everytest/*.test.tsexcept those inEXCLUDED. This branch's only edit there was addingtest/app-answers.test.tsto that list. I tookmain's version of the file. The new test needs no entry, because it is gated as soon as it exists. - Checks I ran: the type check is clean.
test/app-answers.test.tsandtest/check-list.test.tspass (16 tests). I did not run the full suite; the forge runs it on this push. - Unchanged: the feature itself is as before. The app page shows the crew's answers under the tell box, and
aon an app's repository screen in the terminal lists your tells and the answers.
To try it, open the preview at https://lab--1991--forge-trevato-apps.open-platform.sh.
The page side is sound. canSeeAnswers is the one gate for the fragment, the region and the terminal screen. Rows are filtered to the viewer's own tells. Every string goes through html, and the terminal strips control characters. The new body_too_long cap closes a real hole: one long line used to become only a title and skip the body cap. The tests cover the owner, a stranger, cross-origin and both doors for reading.
One miss on both doors: since main was merged in, this branch has src/tui/screens/tell.ts, and the terminal's palette can tell the crew. The pull says it cannot, and that is no longer true. The new body_too_long refusal now reaches the terminal, but tellAnswer falls through to "That could not be told." while the page says "That is too long to tell in one go." Nothing caps the prompt, so a pasted long line hits this.
Fix first: in tellAnswer (src/tui/screens/tell.ts), map body_too_long to "That is too long to tell in one go." Put that sentence in one exported constant that both tellFrom and tellAnswer use, so the two doors cannot drift. Then add a test that would fail without it: tellAnswer(await tell(app, alice, repo, 'x'.repeat(MAX_BODY + 1))) gives those words. Also assert the page's flash words in the existing over-long test, which today only checks that no issue opened.
I could not reach the preview or open the screenshots from my sandbox, so this verdict comes from the code and the tests.
The terminal now gives the same too-long refusal as the page, from one shared sentence. The type check is clean, and test/app-answers.test.ts, test/tui-tell.test.ts and test/tell_from.test.ts pass (17 tests). I did not run the whole suite; the forge runs it on this push.
- Shared sentence:
TOO_LONG_WORDS("That is too long to tell in one go.") is exported fromsrc/actions/tell.tsnext toUNVERIFIED_WORDS. The page flash insrc/web/studio/actions.tsandtellAnswerinsrc/tui/screens/tell.tsboth use it, so the two can't drift.tellAnswernow mapsbody_too_longto it instead of "That could not be told." - Terminal test: in
test/tui-tell.test.ts,tellAnswer(await tell(app, alice, repo, 'x'.repeat(MAX_BODY + 1)))gives those words. - Page test: the over-long test in
test/app-answers.test.tsnow follows the redirect with the flash cookie and checks the page showsTOO_LONG_WORDS, as well as that no issue opened. - Pull description: the claim that the terminal cannot tell the crew is out of date since the merge. The terminal's
:palette can tell the crew, with the same refusals as the page.
Approved. I read the diff against main. The gate is sound: canSeeAnswers (an app plus canSeeOwnerFacts, which is canWrite) is the one check behind the page region, the /app/answers fragment (404 for anyone else) and the terminal screen and key. The rows are the viewer's own tells (author: user.username), so an admin or a teammate sees only what they said. Every string goes through html, and the terminal strips control characters before drawing. SQL stays parameterised. The tell keeps its same-origin check, and the new body_too_long guard closes the one-long-line hole, with one shared sentence for both doors. The tests cover the page, the fragment order, escaping, a stranger's 404, a cross-origin tell, an over-long tell and the terminal screen for owner and stranger. I could not reach the preview or the screenshots from this sandbox (network and /data/looks are denied), so this verdict rests on the code and the tests. Two small notes, neither blocking.
The app page now shows the crew's answers under the tell box, newest first, and updates without a reload.
bun x tsc --noEmitwas clean (after abun installfor the types), andapp-answers,studio,tell_from,client-homeandcheck-listall passed (206 tests). I haven't run the rest of the suite, and I didn't open the page in a browser to watch it update. I've only tested the live fragment by fetching it directly.What changed
/:owner/:name/tell) were already on the app page, owner-only. I kept them.src/web/studio/answers.tsadds a live region,#live-answers, under the box. It has its own fragment atGET /:owner/:name/app/answers. The region updates onrun.andissue.events through the existingliveRegionsJs, the same way Now does.- An answer is the crew's latest comment on the tell, or else its latest finished run's summary. - Until then it reads "The crew is working on it." or "Waiting for the crew." - Answers are escaped and capped at 600 characters. - The fragment returns 404 to anyone who isn't the app's owner.
tell()insrc/actions/tell.tsnow refuses text overMAX_BODY(65,536 characters). The form getsmaxlength, and the flash says "too long" rather than a generic failure. This change applies to every door that tells the crew, not only this box.test/app-answers.test.ts, listed inGATE:- Round trip: a tell is sent, a stand-in run is queued, claimed and finished, and the answer shows on the page and in the fragment. Markup in both the tell and the answer comes out escaped. - Stranger: gets no box, no answers, and the tell opens nothing. - Refused tells: a cross-origin tell and an over-long one open nothing.
What I left out
canSeeOwnerFacts, the same gate the box already used; I added no separate client check.I saw no red tests on main in what I ran.
Try it: sign in as the owner of an app, open
/<owner>/<app>, tell the crew something, and the answer appears under the box as the crew replies.🤖 Generated with Claude Code
Closes #1989