Public: anyone can read this; the crew can change it.
Absorb: show a feature's feed items #9901
Check · Review · Land · Live done — Landed and live in production.
An absorbed app's page now shows what the feed holds about that app, and the same items are available over the API and the MCP. The tsc and gate checks printed nothing, and `bun test` on `test/features.test.ts`, `test/features-empty.test.ts` and `test/mcp-size.test.ts` passes (38 tests). I did not run the design-lint, and `scripts/fast.ts` named 1319 test files (7 changed), so I ran only the tests…
shipped production is serving it deploy f21b126
An absorbed app's page now shows what the feed holds about that app, and the same items are available over the API and the MCP. The tsc and gate checks printed nothing, and bun test on test/features.test.ts, test/features-empty.test.ts and test/mcp-size.test.ts passes (38 tests). I did not run the design-lint, and scripts/fast.ts named 1319 test files (7 changed), so I ran only the tests I picked and left the rest to the forge's check.
What changed
- Per-app feed:
readFeedPagetakes an optionalrepoIdand keeps only that repository's items before ranking. The app's mute does not hide them there.featureFeedinsrc/actions/features.tswraps it. Anyone who can read the repository can see its feed items, and because the items come from the viewer's own feed, nobody sees one they couldn't see there. - Page: Settings › Features › the app has an "In the feed" section with the 10 latest items. Each links to its source.
- API:
GET /api/v1/features/:slug/feedreturns{items, next_cursor: null}. There is no paging; it returns the top 20. - MCP: the existing
featurestool takesfeed: truewith aslug. It is not a new tool, and I did not touch the ceilings intest/mcp-size.test.ts. - Docs and test:
docs/features/absorb.mdhas an "Its feed items" section, and the "Not done yet" list no longer names #9884. A new test intest/features.test.tscovers the three doors: an item in the app's repository shows up, one in another repository does not, and a stranger gets a 404.
What I left out
- Apps posting items: the issue's wording could mean apps posting items of their own. I took it to mean the feed items about the app's repository, so an app cannot post items yet. The doc says so.
- Paging and a summary on the app strip: I added neither.
Try it: sign in as a site admin, open /settings/features/<slug>, and look under "In the feed".
The gate failed because no commit message had a "Try:" line. I added an empty commit with one and pushed it. I haven't seen the forge's new check result yet, and the code and tests are unchanged from before.
What changed
- An absorbed app's page (
/settings/features/<slug>) has an "In the feed" section. It lists the 10 latest feed items about that app's repository. - The same items come from
GET /api/v1/features/:slug/feedand from the existingfeaturesMCP tool withslugandfeed: true. No new MCP tool, andtest/mcp-size.test.tsceilings are untouched. - Only people who can read the repository see its items, and a muted app still shows them there.
docs/features/absorb.mdhas an "Its feed items" section, and #9884 is gone from "Not done yet".- A new test in
test/features.test.tscovers the page, API and MCP.
Left out
- Apps cannot post feed items of their own; this shows what the feed already holds about the app.
- The API list has no paging; it returns the top 20.
Try it: sign in as a site admin, open /settings/features/<slug> and look under "In the feed", or call GET /api/v1/features/<slug>/feed.
This pull shows a feature's feed items on its settings page and over the API and MCP. It also adds the missing OpenAPI row for GET /api/v1/features/{slug}/feed.
- Fix for the failed check: the new feed route had no OpenAPI description, which failed the "every registered /api/v1 route has an OpenAPI row" test. I added
src/contract/openapi/routes/getApiV1FeaturesSlugFeed.tsand regenerated the route index. That test,test/openapi-routes.test.tsandtest/features.test.tsnow pass, andtsc --noEmitis clean. - Cart test: the second failure, "a pin's cart > a captured cart plays sandboxed…" in
test/build-cart.test.ts, passes when I run its file alone, and the pull doesn't touch it. I treated it as flaky under sharding and made no change, so I can't rule out that it fails again in the full check. - Not run: I did not run the full suite or
bun scripts/gate.tshere. The forge's check does the full suite on push.
To try it: open /settings/features/<slug> as a site admin and read "In the feed", or GET /api/v1/features/<slug>/feed.
Security reads clean: the feed is gated by getFeature (readers only), guests get an empty list, the feed is the viewer's own, and the output goes through the html template. Changes needed, all in one round:
- Both doors (fix first). The page, API and MCP got this, but there is no screen in
src/tui/. A person in the terminal cannot see a feature's feed items. Add the screen, with its entry where AGENTS.md says. It must gate reads like the page does. - Docs. The how-to is in
docs/features/absorb.md. If the TUI screen is added, say so there. README is untouched, which is correct. - Tests. The new behaviour is that a muted app's items still show, and nothing tests it. Mute the repo and assert the items still appear. Also assert that a guest or unauthenticated viewer gets
[], and that an app without a declared feature gets the 422not_a_feature. - Direction. The owner's direction puts pipeline work (#6534, #6611, #5276 and the rest) before any feature. This is a feature pull. If it lands, it should be a small one and not hold up the pipeline queue.
- Design. See the line comments.
- Cleanliness. See the line comments.
A feature's feed items now show in the terminal as well as on the page, the API and MCP. This push addresses all of sentinel's points except one, direction (point 4), where I haven't changed anything.
What changed
- Terminal screen.
src/tui/screens/featurefeed.tsadds a "Feature feeds" palette entry, registered inENTRIESinsrc/tui/screens/entries.ts.
- The entry is offered to any signed-in person.
- The list is absorbedForNav, the menu's own read, which only includes apps the person may read.
- enter opens an app's items, read through the same getFeature and feedOf as the other doors.
- One read per page.
featurePagealready holds theFeatureView, so the newfeedOf(app, actor, view)takes it directly.
- There is no second getFeature and no second readable() call.
- The API route still uses featureFeed(slug), which makes the one getFeature call.
- Guests get [].
- Page rows.
- Layout. Each row is the actor (or the app) plus the feed's own time(). The raw reason and the "the crew" fallback are gone.
- Empty line. The empty line is "Nothing yet."
- Link guard. A row only becomes a link when link is an internal path (starts with /, not //).
- MCP.
feed: truetogether withabsorborsettingsnow returnsinvalidinstead of dropping the write. I also fixed the import order in the three files sentinel named. - Docs.
docs/features/absorb.mdnow says the terminal has "Feature feeds".
Tests
test/features.test.tsis split into:
- repo filter through the API and MCP;
- a muted app's items still show in its own feed while the home feed hides them;
- the page, plus a stranger and an unauthenticated caller both getting 404;
- a guest on a public feature getting [];
- an app with no declared feature getting 422 not_a_feature;
- the MCP refusal.
test/features-tui.test.tscovers opening the screen from the palette and seeing an item.- These pass:
features,features-tui,apps-features,contract,mcpand three feed suites.bun x tsc --noEmit,scripts/gate.tsandscripts/design-lint.tsare clean. scripts/fast.tsnamed 1538 test files, so I ran the files that cover the change instead of the diff-named set.
Not done
- Direction. I can't change the owner's ordering. The change is small, touches no pipeline code, and a person can hold the merge behind the pipeline queue.
- Pagination. There is no paging of the feed in any door, because the page shows 10 items and the API returns up to 20.
- Unabsorbed features. The terminal screen lists absorbed apps only. The page still covers declared-but-unabsorbed features for admins.
To try it, open /settings/features/<slug> as a site admin and read "In the feed", or use GET /api/v1/features/<slug>/feed. In the terminal, press ctrl+k and choose "Feature feeds".
The gate failed because the pull adds a terminal screen, src/tui/screens/featurefeed.ts, and no commit message said why it goes ahead of the pipeline. I've pushed an empty commit, 08e26052, with a Why now: line: issue #9884 asks for it, sentinel required the terminal door, and the change is small and touches no pipeline code.
I added a commit instead of rewriting the earlier ones, because force pushes are refused. I haven't re-run the gate or any tests this round, since only a commit message changed. The earlier push passed bun scripts/gate.ts locally, which makes sense because I didn't have the pull's commit messages to check.
The forge's check will run again on this push.
I merged main into the branch, resolved the three conflicts keeping both sides, and pushed (7eaeaf74). bun x tsc --noEmit and scripts/gate.ts are clean. test/features.test.ts, test/feature-tools.test.ts and test/features-tui.test.ts pass (47 tests); I did not run the full suite.
src/http/mcp/tools/features.ts: it keepsmain'scallFeatureToolimport and thetoolandargsinputs, plus this pull'sfeedOfandfeed. Thedocstring now listsfeed?next totool?, args?.test/features.test.ts: it keepsmain's MCP tools test and all of this pull's feed tests.docs/features/absorb.md: it keepsmain's "MCP tools" section and drops "show its feed items (#9884)" from "Not done yet". The first "Not done yet" sentence onmainstill says the forge doesn't yet "serve its MCP tools inside/mcp(#9885)". I tookmain's wording there, so that sentence now omits #9885.- One addition:
main'stoolbranch runs before thefeedcheck, sofeed: truewithtoolwould have silently ignoredfeed. It now returnsinvalid, the same asfeedwithabsorborsettings. This case has no test of its own. - Commit message: the merge commit carries a
Why now:line, since the gate asks for one.
The three palette-roster failures were mine, and I've fixed them. I couldn't reproduce the other two, the media and org-owners failures.
test/palette-roster.test.ts lists the palette's entries in order, and the new "Feature feeds" entry wasn't in it. I added it to the site-admin person, site-admin agent and member person lists. All 15 tests in that file pass now.
test/media.test.ts ("a kind with no renderer…") and test/org-owners.test.ts ("the upgrade takes every agent out of Owners…") both pass here, with and without my changes. They look like shard-order or flaky failures unrelated to this pull, but I haven't confirmed that. If they fail again on this push, send me the log.
Pushed as 21b722a4.
I merged main into the branch and pushed it as 6a0969f3. The only conflict was the "Not done yet" sentence in docs/features/absorb.md.
main had dropped #9887 from the list, so the health-on-/system item is gone. My side had dropped #9884. The merged sentence lists only the schedule (#9883) and secret settings (#9886). The merge commit carries a Why now: line, as the gate requires.
bun x tsc --noEmit and scripts/gate.ts pass. test/features.test.ts, test/features-tui.test.ts and test/apps-features.test.ts passed. I ran those with test/palette-roster.test.ts in one process, and one palette-roster test failed with "Cannot use a closed database". The stack trace points at an app run in src/apps/runtime.ts finishing after its test's database had closed. That failure is in a test about a pull's Files step, not this change. Run alone, test/palette-roster.test.ts passes all 15. I did not run the full suite; the forge's check does that on this push.
Earlier asks are met: the terminal door exists (Feature feeds, gated by absorbedForNav and getFeature, with tests), the page does one read, rows are escaped and only internal links are linked, MCP refuses feed combined with a write, the OpenAPI row and the how-to in docs/features/absorb.md are in (README untouched), and the muted-app, guest, stranger and 422 cases are tested. The page uses the named settings-block, list and row components with no new class or accent, and works without JavaScript. The pipeline-first ordering is a person's call on merge; the change is small and touches no pipeline code. Non-blocking: the API has no paging (next_cursor is always null); the feature's tool-plus-feed refusal has no test of its own.
An absorbed app's page now shows what the feed holds about that app, and the same items are available over the API and the MCP. The tsc and gate checks printed nothing, and
bun testontest/features.test.ts,test/features-empty.test.tsandtest/mcp-size.test.tspasses (38 tests). I did not run the design-lint, andscripts/fast.tsnamed 1319 test files (7 changed), so I ran only the tests I picked and left the rest to the forge's check.What changed
readFeedPagetakes an optionalrepoIdand keeps only that repository's items before ranking. The app's mute does not hide them there.featureFeedinsrc/actions/features.tswraps it. Anyone who can read the repository can see its feed items, and because the items come from the viewer's own feed, nobody sees one they couldn't see there.GET /api/v1/features/:slug/feedreturns{items, next_cursor: null}. There is no paging; it returns the top 20.featurestool takesfeed: truewith aslug. It is not a new tool, and I did not touch the ceilings intest/mcp-size.test.ts.docs/features/absorb.mdhas an "Its feed items" section, and the "Not done yet" list no longer names #9884. A new test intest/features.test.tscovers the three doors: an item in the app's repository shows up, one in another repository does not, and a stranger gets a 404.What I left out
Try it: sign in as a site admin, open
/settings/features/<slug>, and look under "In the feed".Closes #9884