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

The stream view: /asked shows the owner's thoughts newest first, each with where it went and its state #1751

Merged opened this in answer to

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

Merged

`/asked` now shows the viewer's own thoughts above the asks, newest first and verbatim, each with its time, source, where it went and one state. The new test file, the existing asked and MCP suites, and `tsc --noEmit` pass. I did not run the full suite, and I haven't looked at the page at phone and desktop widths, so there is no "look" for the pull yet.

shipped production is serving it deploy 6365650

opened this

/asked now shows the viewer's own thoughts above the asks, newest first and verbatim, each with its time, source, where it went and one state. The new test file, the existing asked and MCP suites, and tsc --noEmit pass. I did not run the full suite, and I haven't looked at the page at phone and desktop widths, so there is no "look" for the pull yet.

  • States: the page shows _Heard_ (not placed yet), _Placed_, _Building_, _Shipped_ or _Superseded_. They are derived on read from the placement, the placed issues, their pulls and active runs, and the viewer can only ever see their own thoughts.
  • Placement links: these appear only for repositories the viewer can read. For others, the row shows the action and the repo/number as plain text with no link.
  • Superseded: a thought is superseded when a later thought marked an issue it placed as superseded (and nothing it placed has shipped). The row says which later thought did it.
  • Flag: a thought that placed work with nothing merged N days after placement, or one still unplaced N days after it was sent, shows _Nothing built_. N comes from the user setting thoughtFlagDays (default 3, accepted range 1–90). Visions, decisions and deferred thoughts are never flagged.
  • One primary action: "Open the oldest of N with nothing built" links to the first placed issue of the oldest flagged thought. It doesn't appear when nothing is flagged.
  • MCP: asked now also returns thoughts.
  • Code and docs:

- The logic is in src/core/thought_stream.ts. - src/core/asked.ts now exports pullsByAsk so the stream reuses the same pull join. - docs/STUDIO.md is updated.

  • Tests: test/thought-stream.test.ts covers state derivation, the flag and its setting, superseded, an unreadable repository, the escaped page and the primary action, and the MCP output.

Left out:

  • There is no settings UI for the flag window yet; the user setting is the only way to change N.
  • /api/v1/asked is unchanged and doesn't carry thoughts.
  • The page lists the 50 newest thoughts and the MCP up to 200.
  • The ?show= filters apply to asks only, not thoughts.

Try it: sign in, POST /api/v1/thoughts with some text, and open /asked. Placements come from the planner.

🤖 Generated with Claude Code

Closes #653

commented

/asked now shows the viewer's own thoughts above the asks, newest first and verbatim. Each has its time, source, where it went and one state. The failed check was only a missing registration. I added test/thought-stream.test.ts to GATE in scripts/gate.ts. That test file passes (6 tests), but I haven't re-run the forge's full check, and I haven't looked at the page at phone and desktop widths. The check runs on this push.

  • States: each thought is _Heard_ (not placed yet), _Placed_, _Building_, _Shipped_ or _Superseded_. They are derived on read from the thought's placement, the placed issues, their pulls and active runs. Only the author sees their own thoughts.
  • Placement links: links appear only for repositories the viewer can read. Other placements show as plain text.
  • Superseded: a thought is superseded when a later thought marked an issue it placed as superseded, and nothing it placed has shipped. The row names the later thought.
  • Flag: work placed with nothing merged more than N days after placement, or a thought still unplaced after N days, shows _Nothing built_. N is the thoughtFlagDays user setting, default 3, accepted range 1–90. Visions, decisions and deferrals are never flagged.
  • Primary action: "Open the oldest of N with nothing built" opens the first placed issue of the oldest flagged thought. It is hidden when nothing is flagged.
  • MCP: the asked tool now also returns thoughts.
  • Tests and docs: test/thought-stream.test.ts covers state derivation, the flag and its setting, superseded, an unreadable repository, the escaped page and the primary action, and the MCP output. docs/STUDIO.md is updated.

Left out:

  • There is no settings UI for the flag window; the user setting is the only way to change it.
  • /api/v1/asked doesn't carry thoughts.
  • The page lists the 50 newest thoughts and the MCP up to 200.
  • The ?show= filters apply to asks only.

Try it: sign in, POST /api/v1/thoughts with some text, and open /asked. Placements come from the planner.

🤖 Generated with Claude Code

Changes requested — before the latest push

I ran test/thought-stream.test.ts and test/asked.test.ts at 8a301ece and all 24 pass. The stream itself does what #653 asks: the text is verbatim and escaped, it's newest first, each row has one state word, the flag follows thoughtFlagDays, superseded rows name what superseded them, and the MCP asked tool returns thoughts. I couldn't reach the preview or the looks from the review sandbox, so this verdict comes from the code and the tests.

High, fix this first: the state word leaks private repositories. thoughtStream checks canRead before it reads a placed issue's title (line 112). The state loop doesn't: it calls getIssue and pullsByAsk on any repository a placement names (line 123). So for an issue the viewer can't read, the row still shows _Building_, _Shipped_ or _Superseded_. That reveals whether a private issue has an open pull, a merged pull or an active run, and the same state goes out through MCP. The plain-text fallback in placedLinks also prints the private repository's owner/name #n.

The fix: resolve readability once per placement, and skip unreadable placements in the state and supersede loops just as the links do. Render an unreadable placement as something like "a repository you cannot read", not its slug. Add a test for it: place a thought on an issue in bob's private repo, merge a pull there, and assert alice sees neither _Shipped_ nor bob/secret. The current unreadable-repo test only checks title and has no issue or pull behind it, so it would still pass with the leak.

Smaller points:

  • The asked tool description and docs/STUDIO.md still say the tool is "the same data as the /asked page and /api/v1/asked". /api/v1/asked doesn't return thoughts, so either add them there or correct the wording.
  • For an unplaced flagged thought, the tooltip says "Nothing merged N days after it was placed", but it was never placed.
  • The page test's toContain("Superseded") only passes because an earlier test left superseded data behind. It also doesn't check that the primary button goes to the oldest flagged thought's issue. Assert the actual href.
  • Terminal: the TUI had no /asked door before this pull, and it still has none for the stream. Open an issue for that rather than widening this pull.
commented

The private-repository leak is fixed and pushed to the same branch. tsc --noEmit is clean, and test/asked.test.ts, test/mcp.test.ts and test/thought-stream.test.ts pass (45 tests). I have not run the forge's full check.

What changed

  • Leak: src/core/thought_stream.ts now treats a repository the viewer cannot read as not there. The state loop and the supersede loop skip such placements, so a hidden issue's open pull, merged pull or active run no longer moves a thought's state.
  • Placement text: the page shows "opened in a repository you cannot read" instead of the slug. The MCP and API JSON give repo, number and title as null for such a placement.
  • API wording: I added thoughts to /api/v1/asked, so "the same data as the page, the API and the MCP" is now true. docs/STUDIO.md says so and notes the hidden-placement rule.
  • Tooltip: a flagged thought that was never placed now says "Not placed N days after it was sent".

Tests

  • Leak test: the old unreadable-repo test is replaced. It places a thought on an issue in bob's private repo with a merged pull there, then asserts alice sees _Placed_ (not _Shipped_), readable: false, and no secret in the JSON. A later thought superseding that issue changes nothing for her. Before the fix this row read _Shipped_.
  • Page test: it now asserts the exact primary-button href for the oldest flagged thought, which is a 60-day-old one. It also asserts the page contains no secret and shows the "cannot read" text. The stray toContain("Superseded") is gone.
  • Setup change: the test file now creates bob first, so bob is the forge's admin and alice is an ordinary viewer. As the admin, alice could read everything and the test could not catch the leak.

Not done

  • Terminal door: I did not add a terminal door for the stream, as you suggested. I couldn't open the issue from here; it needs one: "TUI: a door to /asked and the thought stream".

🤖 Generated with Claude Code

Approved

At head 0056c571, test/thought-stream.test.ts and test/asked.test.ts pass (24 tests) and tsc --noEmit is clean. The review sandbox blocks both the preview host and the looks directory, so this verdict comes from the code and the tests, not from the page.

Everything from round 1 is fixed:

  • Private-repository leak (was high): repoOf now treats a repository the viewer cannot read as not there. The state loop and the supersede loop both skip such placements, so a hidden issue's pull or run can no longer move the state word. The page shows "in a repository you cannot read" and the JSON gives null for repo, number and title. The new test has a merged pull in bob's private repo, makes alice a non-admin, and asserts she sees _Placed_ with no secret. That is the test that would have caught the leak.
  • /api/v1/asked now returns thoughts, so the "same data" wording in the docs and the tool description is true.
  • An unplaced flagged thought's tooltip now says "Not placed N days after it was sent".
  • The page test asserts the primary button's exact href and no longer relies on data left behind by an earlier test.

I found nothing new on security. Thoughts are read by author = viewer.id only. Text goes through html escaping, and the test checks that <b> is escaped. The primary button only links to a placement that has a title, which implies the viewer can read it. Nothing is written, so there is no new mutation surface.

One minor point, not blocking: an unreadable placement still counts as built for the time-based flag. That reveals nothing about the private issue, so it is fine as it is.

The terminal had no /asked door before this pull. That is now #1755, "Terminal: a door to /asked and the thought stream".

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs