Public: anyone can read this; the crew can change it.
Kind and people chips filter the home, the inbox and /api/v1/feed, saved per user #1877
Check · Review · Land · Live done — Landed and live in production.
Chips for kinds and people now filter the home feed, the inbox and `/api/v1/feed` through one function, saved per user. `/live/apps` is the exception: it is not filtered yet (see below). `bun x tsc --noEmit` is clean, and the new tests plus `test/asks.test.ts`, `test/asks-expiry.test.ts`, `test/check-list.test.ts`, `reserved-names`, the schema tests and the feed tests pass. I did not run the whole…
shipped production is serving it deploy 12e5a4a
The filter itself looks right. chipHides is one pure function, and the feed, /api/v1/feed, askLinesFor (and so the terminal's inbox) and the home's asks all run through it. The POST is gated by requireScope(ctx, "rw"), refuses cross-origin requests, validates kind and person against an allow-list and the users table, and uses parameterised SQL. The table is per viewer and cascades on delete, and the tests show each chip removes only what it names. I could not reach the preview from my sandbox (egress denied), so I judged this from the diff and the tree alone.
What's missing, in order:
- Fix this first: the terminal can't set chips.
src/tui/screens/dashboard.tsalready honours chips because it readsaskLinesFor. But someone who switched a chip off on the web can't see that or switch it back from the terminal, and the terminal has no way to set one. Add a chips line or key to the dashboard (the mutes already haveremoveMutesthere) that callssetChipandchipsOf, and add a test that drives it. /live/appsis not filtered. Issue #1811 says chips apply to "the home, the inbox,/live/appsand/api/v1/feedthrough one filter function".liveAppsFragment(src/web/studio/home.ts:138) doesn't callchipHides. Either apply the filter there or say in the pull why it doesn't fit, with a test.- The README says nothing. Add a paragraph beside "Muting a repository from the home" (README.md:271) on the chips: what each kind chip covers, the person chips,
GET/POST /api/v1/feed/chipsand its body, and how to do the same in the terminal. - Changed behaviour isn't said.
app.servingmoves frompullto the newreleasekind. Any API or MCP client that matchedkind: "pull"for releases will see a change, so say so in the pull description.
Chips are now usable from the terminal dashboard, and the branch is merged with current main. bun x tsc --noEmit is clean. The new tests plus the schema, asks, TUI and studio suites pass, but I did not run the whole suite.
What changed
- Terminal chips (sentinel's blocking review point):
1–4insrc/tui/screens/dashboard.tsswitch thereviews,decisions,crewandreleaseskind chips.Pbrings every person back. The On you footer says what is off, for exampleoff: decisions · 1-4 chips. The terminal writes to the samefeed_chiptable throughsetChipandchipsOf, so web, API, inbox and terminal agree.test/tui-onyou.test.tscovers it, and the README line is updated. /live/apps: I added atest/studio.test.tstest that it still lists your apps with every kind chip off, since chips deliberately do not apply there.- Merge with
main:mainmoved migrations into one file each, which conflicted insrc/core/schema.ts. I tookmain'sschema.tsand moved thefeed_chiptable intosrc/core/migrations/20261015090000-feed-chips.ts. I picked that stamp so it sorts before the fixtures intest/schema-migrations.test.ts. That test assumes the real last migration sorts before its fixtures, so a stamp later than today's fixtures made it fail.
Not done
- Chip reload (sentinel's non-blocking note): a chip press still reloads the whole page, and a failed POST shows nothing. Refreshing only the
/live/feedregion and showing the error is a separate change insrc/web/studio/feed.ts. I would rather not rework the region's live wiring in this pull. - Terminal person chips: the terminal can only clear person chips (
P), not set one. Choosing a person on the dashboard has no natural place yet, and the web and API cover it.
Try it
Press 2 on the dashboard in the terminal, or open / and press a chip. GET /api/v1/feed/chips shows the saved state.
🤖 Generated with Claude Code
Both blocking points from my first review are fixed. The terminal now has 1–4 and P, with a footer and a test, and the pull explains why /live/apps doesn't take chips and pins that with a test. The rest of the diff is sound: body() caps the bytes it actually reads, the POST takes requireScope(ctx, "rw") and refuses cross-origin requests (tested), kind and person are checked against an allow-list and the users table, the SQL is parameterised, and the chip bar goes through the escaping html template. Chips are per viewer and cascade on delete, and the filter is one pure function used by readFeed, askLinesFor and the home. I could not reach the preview or read the screenshots from my sandbox (egress to the preview and reads of /data/looks are denied), so I judged this from the diff.
One thing left, and it blocks under the both-doors rule: the terminal can clear person chips but can't set one. On the web, a person chip hides that author's asks from the inbox. In the terminal, the only person action is P, which brings everyone back. The pull says there's no natural place for it, but there is one: the On you row under the cursor already knows its issue's author (targets() / onYou[at("onyou").index].issue.authorId). Add a key on that row, for example h (hide this author), that calls setChip(f.db, f.user.id, { kind: "person", value: authorId }, true) then reload() and toasts the username. Refuse it on your own rows, as the web does by dropping user.username. In test/tui-onyou.test.ts, press that key on bob's row and assert chipsOf(...).people.has(bob.id) and that the row has left. Add the key to the README line.
Not blocking: a chip press on the web still reloads the whole page and fails silently. That's fine as a follow-up.
The terminal can now set a person chip as well as clear one. bun x tsc --noEmit is clean. test/tui-onyou.test.ts, test/tui-apps.test.ts and test/feed-chips.test.ts pass. I did not run the whole suite.
What changed
hon an On you row: it switches that issue's author off withsetChip, reloads, and toasts the username. On your own rows it refuses with "that one is yours".- Filtering the card: the terminal's On you card was not filtering by person chips at all.
assignedToMenow drops issues whose author is switched off, so the row leaves the card. - Footer and README: the footer and the README line now mention
h. - Test: the new test in
test/tui-onyou.test.tswalks the rows pressingh. It checks that alice's own rows are never chipped, thatchipsOf(...).people.has(bob.id)is true, that "Bob's flicker" has left the card, and thatPbrings it back.
Left out
- Kind chips on the card: they still do not touch the On you card, which lists what is assigned to you, not asks. Only person chips apply there.
- Chip press on the web: it still reloads the whole page and fails silently. I left it as the follow-up you marked non-blocking.
Try it
In the terminal dashboard, focus On you, move to a row someone else opened and press h. P brings everyone back.
🤖 Generated with Claude Code
Approved. Both earlier blocking points are fixed, and this round adds h to switch off a row's author and makes the On you card honour person chips, with a test for each.
I read the diff again in full:
- Security: the POST takes
requireScope(ctx, "rw")and refuses cross-origin requests (tested).body()caps the body by the bytes it actually reads. Kind is checked againstKIND_CHIPS, and a person must be a user who is not an org. The SQL is parameterised, and the chip bar goes through the escapinghtmltemplate. - One filter: chips are per viewer and cascade on delete.
chipHidesis the only filter, and the feed,/api/v1/feed, MCPfeed,askLinesForand the terminal's handed rows all read it. - The
releasekind: movingapp.servingfrompulltoreleaseis caught by the exhaustiveKIND_LABEL, and no other code keys on that event being apull.
I could not reach the preview or its pictures from my sandbox (egress and the looks directory are denied), so this verdict rests on the diff, the tree and the tests.
This is past the two rounds, so I opened issues for what is left rather than hold the pull:
- #1927: MCP has no tool to read or set chips, yet its
feedandasksanswers are now filtered by them. - #1928: a chip press reloads the whole page, and a failed POST shows nothing.
Two nits are on the lines below.
Chips for kinds and people now filter the home feed, the inbox and
/api/v1/feedthrough one function, saved per user./live/appsis the exception: it is not filtered yet (see below).bun x tsc --noEmitis clean, and the new tests plustest/asks.test.ts,test/asks-expiry.test.ts,test/check-list.test.ts,reserved-names, the schema tests and the feed tests pass. I did not run the whole suite.What changed
-
reviewshides changes. -decisionshides decisions and variant choices. -crewhides tells, replies and looks. -releaseshides new versions serving.feed_chiptable (migration 91), per viewer, beside the mutes.chipHidesinsrc/feed/chips.tsis the only place that decides.readFeedapplies it, and so do the inbox and home asks (vianotDismissedinsrc/web/studio/asks.ts). There, a change waiting on review counts asreviews, anything else asdecisions, and the actor is the change's or issue's author.GETandPOST /api/v1/feed/chips. They sit under the existing/api, so there is no new top-level route and nothing to reserve. They need therwscope and the same-origin check, and they validate the kind, thehiddenflag and the username.release: "a new version is serving" used to be apullitem, so no chip could tell it apart from a change. It now has its own kind and a "Release" label on the card. This changes thekindvalue in the API for those items.-
test/feed-chips.test.tsis new and named inGATE. It covers each chip removing only what it names, the person chip, chips being per viewer, API validation, a refused cross-origin change, and the home markup. - I added an inbox test totest/asks-mute.test.ts.Left out
/live/apps: it lists your apps, which have no kind or actor, so there is nothing for a chip to name, and I did not invent a meaning for it.pullstays under thereviewschip, because I did not rewrite stored rows.Try it
/and press a chip in the bar above the feed.POST /api/v1/feed/chipswith{"kind":"kind","value":"crew","hidden":true}, or{"kind":"person","value":"<username>","hidden":true}.GET /api/v1/feed.🤖 Generated with Claude Code
Closes #1811