Repository navigation
console inbox and home: the Activity feed lists platform events (JWKS keys, data migrations, owner backfills, every login) to business users — the feed reads the app's business objects only #12530
Description
Filing gate ① (a product defect, measured on main), filed on the maintainer's word (chat, 2026-10-15, session session_019YFzJp4o8Ck4NsT1toC4UP, seat account os-bill): the seat ran a functional audit of the HotCRM console on objectui main 720f6d4 (report, private to the maintainer: https://claude.ai/artifact/XPrn5eZMsQaKUgasMJEMmR, finding F5, figure 13), and the maintainer answered 「同意开卡,使用 pm-dispatch 创建卡片」. Quota note: the audit's cards exceed the filing gate's three-per-fire quota on that word, which approved the report's card list as a set.
Parent: objectstack-ai/objectstack#25945. Blocks nothing; nothing blocks it.
Reader who acts: the domain:ui lane's next dispatch (seat post #9800), one dev round in @object-ui/app-shell (hooks/sharedUserFeeds.ts, layout/ActivityFeed.tsx, the console home's feed), size S–M.
Dedupe (MCP search_issues, scope objectstack-ai/objectui, all states, read 2026-10-15T14:14Z): "activity feed shows system events JWKS key data migration sys objects inbox notifications filter business objects only" → 0. None is this card.
What happens
The bell's Activity feed tab and the console home's Activity feed card show, to the HotCRM admin a minute after a fresh seed: 「Dev Admin Created JWKS Key "QdKJl6LTc2jHmOx3…"」 (JWKS Key), 「Created Data Migration "adr-0104-value-shapes"」 and 「"adr-0104-file-references"」 (Data Migration), 「Owner: ∅ → Dev Admin · Knowledge Article」 twice, then the user's own 「Updated User "Dev Admin"」 on every login (the last_login_at write). A sales user reading this feed sees platform plumbing named after ADRs and key ids; the business events (a lead created, an opportunity's stage changed) are below them or off the page of twenty.
Readings (objectui origin/main 720f6d4, read 2026-10-15T14:15Z)
packages/app-shell/src/hooks/sharedUserFeeds.tsline 10 (the feed table) and line 685 (ACTIVITY_OBJECT = 'sys_activity'): the recent-activity feed isfind('sys_activity', top 20, sort -timestamp)with no filter onobject_name; lines 622–645 map rows to items bytype.packages/app-shell/src/layout/ActivityFeed.tsxlines 55–70 and 122: the only filter isisKindEnabled(notificationPreferences, a.type)(the user's per-kind switches); nothing filters by object.packages/app-shell/src/hooks/useHomeInbox.tsfeeds the console home's card from the same reading.- API reading (HotCRM backend, admin):
GET /api/v1/data/sys_activity?top=20&sort=-timestampreturns rows withobject_namesys_user(every login writes one),sys_jwks_key,sys_data_migration,knowledge_article, thencrm_lead; thesys_*rows carryorganization_id: null.
What to build
- The user-facing feeds (the bell's Activity tab, the console home card) read business activity only: rows whose
object_nameis an object of an installed app the user can open, which excludes the platform'ssys_*objects and system-only writes (anobject_namethe app roster does not list). The exclusion is derived from the object roster the shell already holds, not a hand-written list of names. - The user's own
sys_userlogin write (last_login_at) is never feed material, whatever the roster says. - The platform events stay where an admin looks for them: the Setup / audit surfaces read the same
sys_activityunfiltered, as today. The query'stopapplies after the filter (a filtered read, not a client-side drop that empties the page).
Acceptance
sharedUserFeedstests: with a roster of two app objects, the activity reading's filter names those objects and the mapped items contain nosys_*row; asys_userlast_login_atupdate row is excluded even whensys_useris in the roster; the Setup audit reading is unchanged.- Browser reading by the landing seat on HotCRM after a fresh seed: the bell's Activity feed lists leads, accounts, opportunities and tasks; no JWKS key, migration or login row.
pnpm testfrom the repo root green on the affected files; a changeset (minor) for@object-ui/app-shell.
Out of scope
- The Notifications and Approvals tabs; the per-kind preference switches; the writer side (objectstack) of
sys_activity.
Attribution: written by the domain:ui seat in session session_019YFzJp4o8Ck4NsT1toC4UP (account os-bill) on the maintainer's words quoted above.
Screenshots (added 2026-10-15T14:40Z, on the maintainer's word 「专用分支存图,卡里嵌 raw 链接」)
The images live on the attachment branch claude/ui-audit-2026-10-15-shots of objectstack-ai/objectui (commit 918a251, never merged; index at https://github.com/objectstack-ai/objectui/blob/918a2510a54bd6875b90eb6996d098c3ec0451dd/docs/design/console-audit-2026-10-15/README.md). Full set and figure numbers: the audit report linked above.
The bell's Activity feed on HotCRM: JWKS Key, Data Migration and Knowledge Article rows ahead of any lead

Activity
objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 1
Session:
session_01XVp19E1MYUTFM3dnkwz8gBAccount:
baozhoutao(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12530-activity-business-onlyWorktree:
objectui-issue-12530Domain:
domain:uiSeat:
domain:ui#2(the card names the lane's next dispatch under seat 3's post #9800, which is vacant)File surface:
packages/app-shell/src/hooks/sharedUserFeeds.ts(thesys_activityreading's query and row mapping),packages/app-shell/src/layout/ActivityFeed.tsxandpackages/app-shell/src/hooks/useHomeInbox.tsonly if the filter must be passed through them, the module that holds the shell's object roster only to read it, the tests beside each, and one.changeset/12530-*.md(stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus(default tier;dispatch-gates --tier --repo objectstack-ai/objectuireads no path-derived mandate)Clause-②: no
Responsibility:
objectui's shared activity feed, which reads sys_activity with no object filter | the per-kind preference switches filter by type only; nothing filters by object | every business user who opens the bell's Activity tab or the console home's Activity card (measured on HotCRM after a fresh seed: JWKS keys, data migrations and logins ahead of any lead)Thread-read: none
Serial constraints cleared: at
main521817ce9, no in-flight claim or open PR touchessharedUserFeeds.ts,ActivityFeed.tsxoruseHomeInbox.ts. objectui#12496 (this seat,ResourceEditPage.tsx) is disjoint. This seat's objectui#12439 (sharedUserFeeds.ts's hidden-tab poll stop) landed as27411ea; this card edits that file's activity reading, not the poll cadence.Generated by Claude Code
fix(app-shell): the bell's and Home's activity feed reads the app obj…
fix(app-shell): read the activity roster's app list through the provi…
refactor(app-shell): the activity roster memo depends on the gate cal…
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{ "issue": 12530, "status": "done", "branch": "claude/issue-12530-activity-business-only", "pr": "https://github.com/objectstack-ai/objectui/pull/12542", "session": "session_01XVp19E1MYUTFM3dnkwz8gB", "premise_still_valid": true, "summary": "useSharedActivityFeed (the one reading behind the bell Activity tab, global:notifications and Home's activity card) now reads sys_activity with $filter {object_name: {$in: roster}} beside $top 20. The roster is derived in activityObjectRoster from filterActiveApps(apps) and every type:'object' nav entry (areas + navigation, every depth), each read through navEntriesShownToMember with the sidebar's useNavMemberGates; no name list and no sys_ prefix is written. The roster string is part of the feed key (A1 measured: a new key resets and refetches). No key until the app type settles; an empty roster is ready+empty with no request. isLoginStampRow drops an updated sys_user row whose metadata.new names only last_login_at/last_login_ip (A2 measured in objectstack e4ec6ec1: AuthManager.stampLastLogin + plugin-audit diff); this is the only post-read cut, because no queryable column separates a sign-in from a profile edit. Setup/audit surfaces (object view, RecordDetailView) are untouched. ActivityFeed.tsx and useHomeInbox.ts needed no change.", "tests": "HEAD 84b8f5632. app-shell type-check (tsc --noEmit + tsconfig.test.json) after the '@object-ui/app-shell^...' closure build: exit 0. vitest over 14 feed files (sharedUserFeeds*, sharedInboxFeed*, ActivityFeed*, AppHeader.inboxVariant): Test Files 14 passed (14), Tests 133 passed (133), exit 0. Wider set at 95510b3bf (84b8f5632 only destructures the memo deps), covering app-shell src/hooks/, src/layout/, src/console/home/, views/RecordDetailView*, views/record-preview/ and apps/console record-block-record-reach: Test Files 222 passed (222), Tests 1626 passed (1626), exit 0. Before the AppHeader fixture fix the same set read 9 failed in AppHeader.inboxVariant. Ablation via objectstack scripts/ablation-replace.mjs: the $filter line was deleted (anchor x1 -> x0, marker x0 -> x1) and the new pin read 5 failed | 13 passed (18); restore was proven (blob c9fb21067517 == HEAD, git diff HEAD empty). Pre-tsc battery all exit 0: phantom-deps, self-import, unreferenced-sources, doc-example-readers, handler-key-reads, side-effects-array, element-data-source-declaration, spec-symbols, action-forward-parity, designer-field-key-parity, icon-record-names, i18n-keys, i18n-drift, i18n-designer-parity, control-bytes. Also exit 0: check-changeset-fixed, check-changeset-presence, check:new-line-citations (0 new), check:test-path-roots. Narrowed eslint: population is eslint.config.js files **/*.{ts,tsx} minus ignores, all 7 changed files inside; --format json counts 7 files with 0 errors; the warnings are pre-existing AppHeader test lines, and sharedUserFeeds.ts is clean after 84b8f5632; no parserOptions.project, so not type-aware and untouched verdicts are invariant. check-hand-rolled-comment-mask.test.ts not run (no test reads source). NOT MEASURED: HotCRM browser reading, reason: no backend in this run (seat/maintainer per card).", "mcp_calls": "0", "api_writes": "2 relay writes (fleet-write dispatch): pr_create POST /repos/objectstack-ai/objectui/pulls (+ POST /issues/12542/assignees), and this os-dev-report comment POST /repos/objectstack-ai/objectui/issues/12530/comments. Plus 3 git pushes of the branch. 0 label writes (objectui: none named).", "open_questions": [], "out_of_scope_findings": [], "deviations": [ "File surface: layout/__tests__/AppHeader.inboxVariant.test.tsx was edited outside the claim list. It pins the bell's activity reading through AppHeader and went red (9 cases) under the roster rule; only its MetadataProvider stub (one app listing hr_contract) and one grant payload changed.", "One existing assertion was reversed by ruling: sharedUserFeeds.activityGate-7476 'no MetadataProvider at all' asserted an unfiltered read and now asserts no read (no roster).", "New shared test fixture hooks/__tests__/activityRoster.tsx, outside the named test files." ], "files_changed": [ ".changeset/12530-activity-business-only.md", "packages/app-shell/src/hooks/sharedUserFeeds.ts", "packages/app-shell/src/hooks/__tests__/activityRoster.tsx", "packages/app-shell/src/hooks/__tests__/sharedUserFeeds.activityRoster-12530.test.tsx", "packages/app-shell/src/hooks/__tests__/sharedUserFeeds.activityGate-7476.test.tsx", "packages/app-shell/src/hooks/__tests__/sharedUserFeeds.activityGrant-12081.test.tsx", "packages/app-shell/src/hooks/__tests__/sharedUserFeeds.activityType-6730.test.tsx", "packages/app-shell/src/layout/__tests__/AppHeader.inboxVariant.test.tsx" ], "gates": "type-check 0; feed vitest 0 (133/133); wider vitest 0 (1626/1626 at 95510b3bf); ablation red then restored; pre-tsc battery 15x exit 0; changeset-fixed 0; changeset-presence 0; new-line-citations 0; test-path-roots 0; eslint narrowed 0 errors" }Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Review: ACCEPT — objectui PR #12542 ·
domain:uiseat 2 ·session_01XVp19E1MYUTFM3dnkwz8gB· 2026-10-15T16:31ZReviewed on GitHub at head
84b8f56, against the card's What to build 1–3 and Acceptance, the seat's rulings in the dispatch and the claim (6195718220), not against the dev's report.main. Its body opensFixes objectstack-ai/objectui#12530, the only closing keyword, followed byClause-②: no. 8 files.useSharedActivityFeedis the one reading behind the bell's Activity tab,global:notificationsand Home's activity card. It now readssys_activitywith$filter: { object_name: { $in: roster } }beside$top: 20, sotopapplies after the filter. The$inshape is the one this package already sends:sharedUserFeeds.ts' own notification read,useApproverDirectoryandRecordDetailView.activityObjectRosterwalksfilterActiveApps(apps)and everytype: 'object'navigation entry, inareasandnavigationat every depth.navEntriesShownToMemberwith the sidebar'suseNavMemberGates, so the feed follows the same member gates the sidebar shows (the one predicate objectui#12426 shared).sys_prefix rule.appmetadata type settles, so an unfiltered read never goes out.isLoginStampRowdrops anupdatedsys_userrow whosemetadata.newnames onlylast_login_at/last_login_ip. The dev measured the writer as objectstackAuthManager.stampLastLoginplusplugin-audit's diff. This is the one cut made after the read, because no queryable column separates a sign-in from a profile edit. It drops every user's sign-in stamp, not only the reader's own; a sign-in is not business activity for anyone.RecordDetailView) readsys_activityunchanged (card item 3).sharedUserFeeds.activityGate-7476's "no MetadataProvider at all" case now asserts no read instead of an unfiltered read. That follows the ruling: no roster means no read. A standalone bell embed with noMetadataProvidernow shows an empty activity feed.AppHeader.inboxVariant.test.tsx(outside the claim's list) gets an app roster in itsMetadataProviderstub, because it pins the bell's activity reading.sys_*row is mapped, a login stamp is dropped even withsys_userin the roster, and a business row shows (CONTROL). Deleting the$filterline fails 5 of 18 pins. Restore was proven by blob hash.@object-ui/app-shell: minor, as the card asks.hooks/,layout/,console/home/,RecordDetailView*,record-preview/and the console's record-block reach. The battery exits 0.sys_user,sys_organizationorsys_teamstill sees edits to those records, because the roster follows the apps the user opens, as the card asks.filter=latest, 37 runs), all 16 test shards included.Next:
pr_ready+automerge_enable.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Landed ·
domain:uiseat 2 ·session_01XVp19E1MYUTFM3dnkwz8gB· 2026-10-15T16:50Zmain1f23d5b. The card closed by the PR'sFixes objectstack-ai/objectui#12530, which is the only closing keyword.origin/main, all 8 files the PR touches are blob-identical to the accepted head84b8f56. They aresharedUserFeeds.ts, the roster fixture, the five activity pins,AppHeader.inboxVariant.test.tsxand.changeset/12530-activity-business-only.md.pm:dispatchedis removed in the same stroke as this comment.Generated by Claude Code
fix(app-shell): the bell's and Home's activity feed reads the app obj…