Public: anyone can read this; the crew can change it.
A tell sent from the forge pill carries its context: app, page, build and the last console errors #10840
Check · Review · Land · Live done — Landed and live in production.
A note sent from the forge button now carries its page path, the build the app was serving and up to five recent console errors. The issue shows them in a fold at its foot, not in the title. The new tests and the toolbar and note tests I ran pass, and so do `bun scripts/gate.ts` and `bun x tsc --noEmit`. I did not run the full suite, and I did not try it on a running preview.
shipped production is serving it deploy 95ed903
A note sent from the forge button now carries its page path, the build the app was serving and up to five recent console errors. The issue shows them in a fold at its foot, not in the title. The new tests and the toolbar and note tests I ran pass, and so do bun scripts/gate.ts and bun x tsc --noEmit. I did not run the full suite, and I did not try it on a running preview.
What a pill note carried before: the text, the page as origin plus path only, the viewport and a timestamp. They sat in an issue titled From the app: …, labelled from-app and attributed to the sender. tell_words.ts only holds the empty and failure strings. tell_context.ts belongs to the studio, terminal and MCP tells, not the pill.
What changed
src/actions/note_context.ts(new):parseNoteContextkeeps the path (no query or fragment) and up to five errors, each cut to 200 characters. Anything malformed is dropped and the note still goes.contextFoldrenders the fold under "Where this came from".- Secret scrubbing: a message is stripped of control characters, URL queries, bearer tokens,
key=valuesecrets, JWTs and long hex runs. Only the message text is kept. - Build id: it comes from the runtime (
liveFor(...).commitOid), passed throughfeedback.ts, never from the client. It is empty when the environment isn't up. toolbar.ts: the pill records the last five errors fromerror,unhandledrejectionandconsole.error, and sends them with the note.app_notes.tsandfeedback.ts: the checked context is appended to the issue body.web/markdown.ts: the renderer had no fold, so I added one. A line<details><summary>Title</summary>opens it and</details>closes it. The title is limited to a safe character set, and an unclosed fold is closed at the end.test/note-context.test.ts: it covers parsing, truncation, the five-error cap, scrubbing and hostile input. It also checks that a stored note keeps the context under the fold with HTML escaped, not in the title, and that the toolbar sends the errors.
Left out
- Errors thrown before the toolbar script loads are not captured.
- The scrub is pattern-based, so a secret with no recognisable shape can still get through.
- The new test calls
sendNotedirectly. It does not exercise the HTTP route end to end. keep_planwas not available in this session, so there is no plan on the issue.
To try it: open a preview, run console.error("boom"), send a note from the forge button, and open the issue it files.
This pull adds a fold to a note sent from the forge pill. The fold is titled "Where this came from" and holds the page path, the build and the recent console errors. The check failed on a test that was flaky, not on that change, and I've fixed the test.
The pill-note change
src/actions/note_context.tscollects the path, build and recentconsole.errorlines, andsrc/apps/toolbar.tssends them with the note.src/web/markdown.tsnow renders a<details><summary>Title</summary>…</details>block. It is the only extra tag it emits, and the title is checked.test/note-context.test.tscovers the new behaviour.
The failing test
test/keys-contribute.test.ts("each welcomed issue is a row…") failed in about 2 of 8 runs on my branch, and passed alone only by luck.- The test sorted the hrefs in place. It then took the page to return to from the sorted first entry, while the page draws the rows in database order, and the two issues are created in the same second.
- When the order differed, Esc on the opened page had nothing to return to.
- The test now sorts a copy for the set check and walks the rows in the order drawn.
- It passed 10 of 10 runs,
tsc --noEmitis clean, and the fix is pushed.
Not done
- I did not re-run the full suite locally. The forge runs it on push.
To try it
- On the preview (https://lab--10840--forge-trevato-apps.open-platform.sh), open the forge button.
- Send a note after a
console.error. - Read the issue's "Where this came from" fold.
🤖 Generated with Claude Code
I merged main into the branch and pushed it. The only conflict was in test/keys-contribute.test.ts, where main had made the same row-order fix I made earlier. I kept the main code and my one-line comment explaining why the rows are walked in the order the page draws them.
After the merge, tsc --noEmit is clean and the two test files I ran pass: keys-contribute and note-context. I did not run the full suite; the forge runs it on the push.
This pull adds a "Where this came from" fold to a note sent from the forge pill. The fold holds the page path, the build and the recent console errors. src/web/markdown.ts now renders that fold, and test/note-context.test.ts covers it.
To try it, open the forge button on the preview (https://lab--10840--forge-trevato-apps.open-platform.sh) and send a note after a console.error. Then read the fold on the issue.
🤖 Generated with Claude Code
Mostly sound: the build id comes from the runtime, errors are scrubbed and capped, and the fold escapes. Changes needed, all in one round:
- (first) Document it: docs/features/forge-toolbar.md and docs/features/notes.md do not mention the context a pill note now carries. Add it there, not in README.
- The fold repeats the path. The issue body already has a
- Page:line with the path, so- Path:says it twice (principle 1). Drop one. - The test for the pill is only
toolbarJstoContain strings. Nothing posts to the feedback route witherrorsand a hostile body, so the wiring in feedback.ts and proxy.ts (build from liveFor, request cap) is untested. Add a route-level test that posts a note with context and with oversized/hostile context and asserts what is stored, truncated and escaped. The issue box asks for this. - The test/keys-contribute.test.ts edit is an unrelated comment; remove it from this pull.
- markdown.ts: any author can write the fold lines in an issue, and the fold is flat (one level only). Add a test for an unclosed or nested fold in user text and one for a title with disallowed characters, so the renderer rules are pinned. The fold has no styling check; confirm the summary is keyboard-operable and legible on a 360px phone with the existing CSS.
I did not exercise the preview beyond reading the diff.
All five requested changes are made and pushed.
- Docs:
docs/features/forge-toolbar.mdanddocs/features/notes.mdnow describe what a pill note carries. That is the build and up to five scrubbed console errors, under a closed fold. The notes page also states the flat-fold rule for markdown. - Repeated path: I dropped
- Path:. The fold now holds the build and the errors only, andparseNoteContextno longer takes a page. The- Page:line says the path once, and a test asserts that. - Route-level test:
test/apps-feedback.test.tsnow posts to/__forge/notewith hostileerrorsand a client-suppliedbuild. It asserts that the stored build is the forge's, that<img>, backticks andapi_key=values are gone, and that errors are capped at five and about 200 characters. It also asserts that baderrorsstill file the note and that an oversized body gets a 400. keys-contribute.test.ts: the comment is gone, and the file now matchesorigin/main.- Markdown fold:
- New tests in test/note-context.test.ts pin the renderer. An unclosed fold is closed, a nested one is text, a stray </details> is text, and titles with ", <, &, ', a backtick, nothing at all, or more than 80 characters are text.
- The global reset hides the disclosure marker, so the summary had no visible arrow. I added .prose details and .prose summary rules in src/web/style/content.ts: a chevron, a 44px-tall target, small muted type, and wrapping inside the list.
- A native <summary> is focusable and opens with Enter or Space, and the global :focus-visible ring applies to it.
- I only read the CSS for this, with no phone-width or keyboard check in a browser, so please look at the fold on the preview at 360px.
The new route test uses its own context app, because the per-app note limits are shared and sending from notes broke two existing limit tests. tsc, the gate and design-lint are clean. I ran 90 tests across the note, toolbar, feedback and design-lint files, and all pass. fast.ts called the diff too broad to name because of the CSS change, so I did not run the full suite; the forge does that on push.
The check failed on the header-width rule. A comment in src/web/style/content.ts (line 539, the fold-in-prose note I added earlier) was over 160 columns. I wrapped it onto two lines, and the comment text is unchanged.
test/header-width.test.ts and bun scripts/design-lint.ts both pass locally. I didn't run the rest of the suite; the forge runs it on this push. It's pushed as dc96947c5.
One other item in the check log: shard 68 was killed after 28 s while running test/tui-limbs-replanned.test.ts. That didn't fail the check, and I didn't investigate it.
All earlier asks are met: the docs name what a pill note carries (forge-toolbar.md, notes.md, no README change), the repeated Path line is gone, a route-level test posts hostile errors and a client-supplied build, the fold renderer has tests for unclosed, nested, stray and bad-title cases, and the fold has chevron, 44px target and token colours with no new accent. The latest change only wraps a CSS comment to pass header-width. Next two things: (1) Non-blocking: check the fold at 360px and with the keyboard on the preview, since it was only read from CSS. (2) Non-blocking: capture errors thrown before the pill script loads, or say in the docs that they are not captured.
A note sent from the forge button now carries its page path, the build the app was serving and up to five recent console errors. The issue shows them in a fold at its foot, not in the title. The new tests and the toolbar and note tests I ran pass, and so do
bun scripts/gate.tsandbun x tsc --noEmit. I did not run the full suite, and I did not try it on a running preview.What a pill note carried before: the text, the page as origin plus path only, the viewport and a timestamp. They sat in an issue titled
From the app: …, labelledfrom-appand attributed to the sender.tell_words.tsonly holds the empty and failure strings.tell_context.tsbelongs to the studio, terminal and MCP tells, not the pill.What changed
src/actions/note_context.ts(new):parseNoteContextkeeps the path (no query or fragment) and up to five errors, each cut to 200 characters. Anything malformed is dropped and the note still goes.contextFoldrenders the fold under "Where this came from".key=valuesecrets, JWTs and long hex runs. Only the message text is kept.liveFor(...).commitOid), passed throughfeedback.ts, never from the client. It is empty when the environment isn't up.toolbar.ts: the pill records the last five errors fromerror,unhandledrejectionandconsole.error, and sends them with the note.app_notes.tsandfeedback.ts: the checked context is appended to the issue body.web/markdown.ts: the renderer had no fold, so I added one. A line<details><summary>Title</summary>opens it and</details>closes it. The title is limited to a safe character set, and an unclosed fold is closed at the end.test/note-context.test.ts: it covers parsing, truncation, the five-error cap, scrubbing and hostile input. It also checks that a stored note keeps the context under the fold with HTML escaped, not in the title, and that the toolbar sends the errors.Left out
sendNotedirectly. It does not exercise the HTTP route end to end.keep_planwas not available in this session, so there is no plan on the issue.To try it: open a preview, run
console.error("boom"), send a note from the forge button, and open the issue it files.Closes #10827