Repository navigation
REGRESSION: the console header bell panel is dead again — "No notifications" under both Unread and All while /api/v1/notifications returns 10 rows (#4110 / #4156 back on console 09987b68) #4230
Description
Activity
CLAIM — session
session_017Qqyix2QcnpUC9XeYVDzx3(objectui whole-repo PM seat), dispatching now.Branch:
claude/issue-4230-bell-panel-regression. Worktree-first.Dispatch framing:
- Premise re-check first: the repro is from console pin
09987b68, and main has since landed [console] The bell's Approvals and Activity tabs are still dead outside an app page (sameisAppgate as #4110) #4197 (sharedUserFeeds.ts— the bell/home feed store refactor on this exact data path). Step 1 is establishing whether the symptom still reproduces at current main; if it does not, the deliverable becomes the falsification evidence plus the missing tripwire pin, not a fix. - Both open branches from the card stay open until measured: poll-not-running vs panel-filters-good-payload-to-empty ([console] InboxPopover 通知页签恒为空:sys_inbox_message 有未读行、REST 正常返回、Home 卡片可渲染,弹层打开不发请求且过滤成空(rc.5 回归,rc.3 正常) #4110's conclusion last time).
- Regression discipline: fix(app-shell): the bell polls the inbox on every console surface, not only inside an app (#4110) #4199 claimed this fix once — the dev must explain why it didn't hold (regressed vs only-one-path), and land a tripwire test pinning the three-way oracle (API rows present ⇒ bell renders them) so a third recurrence goes red in CI instead of in QA.
- Surface:
packages/app-shell/src/layout/InboxPopover.tsx,AppHeader.tsx, and the [console] The bell's Approvals and Activity tabs are still dead outside an app page (sameisAppgate as #4110) #4197 shared-feed hooks if implicated. The console HOME header renders a second, different inbox polling the wrong source —sys_inbox_messageinstead of/api/v1/notifications, no badge, "You're all caught up" with 9 unread #4235 (home-header second inbox) stays a separate card — the dev reports a one-fix-or-two verdict instead of expanding scope. No conflicts with in-flight holds ([P3] objectuiActionDef's[key: string]: anyabsorbs every retired and misspelled action key — removing a spec key produces no compile error #4046 core/actions, [P2] objectui: every package type-checks its tests or declares why not — and guards must prove they can fail (measured: 355 errors across 19 packages) #4040 small packages, Internaltype: 'form'actions: nest/forms/:name(mode=internal) inside the console shell; internal submit defaults to redirect-to-created-record #4109 apps/console, objectui: pivot bucket id 把 null 维度值编码成占位符 U+2205,与「值恰好等于该占位符」的行仍会撞 #4056/Analytics: the dataset widget's client-side dimension-label resolution is disabled for table / pivot / metric widgets, so a dotted dimension there still renders raw stored values #4263 plugin-dashboard).
Generated by Claude Code
- Premise re-check first: the repro is from console pin
ACCEPT(premise falsified)+ 处置裁决 — session
session_017Qqyix2QcnpUC9XeYVDzx3。结论:非回归。 证据链决定性:QA 环境行 vendored console
09987b68= objectui09987b680d53(2026-08-09 02:35Z),而 #4199 的修复7b0783232合入于 2026-08-10 20:39Z——晚 42.1 小时;git merge-base --is-ancestor非零,且09987b68..7b0783232区间内 #4199 是唯一铃铛路径提交。QA 构建上的 drop point 就是 #4110 的原根因(isApp门,AppHeader.tsx:338 于该 pin);当前 main 上无 drop point,十行载荷在 Unread/All 双过滤下全部渲染。卡片的第二分支("面板把好载荷过滤成空")在 main 上实测为 FALSE。#4197 的 sharedUserFeeds 重构只共享 approvals + sys_activity,未触 inbox 读取,不可能重破。PR #4284 验收通过:它落的是 #4199 自己缺的绊线——既有 pin 全是单行且从未驱动 Unread/All 子过滤,无法区分"Unread 空因为全已读"与"面板拿到空"。8 个新用例反向验证精确(恢复
isApp门 ⇒ 恰好 8 红、variant="app"对照全绿)。oracle 边界如实声明(真组件 + fake adapter,不钉 REST 层)。Flipping ready + auto-merge。#4230 处置:裁 B —— 保持 open,等一次在 post-#4199 console pin 上的 QA 复测把论证转成观测后再关。理由采纳 dev 的:此症状家族已有两次"靠推理关闭后来被读作弱证据"的前科(#4110/#4156,#4235 明说),第三次不重蹈。已去
pm:dispatched(无 dev 在工),转决策收件箱等待位。QA 侧已在源 run objectstack#7517 留言请求复测并提示 vendoring 时滞(同一过期 pin 已产出 ≥3 张卡)。#4235 判定记录:TWO(独立修),且注意 dev 的警示:铃铛读
sys_inbox_message+receipts 是 ADR-0030/#1429 有意为之,"wrong source"对 home 头部收件箱是设计问题而非自明缺陷;其自带的跨 run 矛盾(同 pin 一见正常一见空)仍归 #4235 解决——且现在多了一层怀疑:它的报告同样来自过期 vendored console。派发时须 premise-check-first。新 finding #4289(
notificationsUnavailableRef单次 404 永久闩锁,可从完全不同的原因复现三方签名)留 finding 池,巡检候选名单已记。
Generated by Claude Code
State hygiene (seat
repo:objectui, sessionsession_01RnQd8iMMUwXQEV1crFmQiQ, 2026-08-15): label correctedpm:queue→pm:blockedand the stale assignee cleared, to match the standing disposition above (2026-08-11, ruling B): the "regression" premise was falsified — the QA repro ran a vendored console pin (09987b68) that predates #4199's fix by 42h, the tripwire landed in PR #4284, and this card stays open only awaiting one QA re-test on a post-#4199 console pin (requested on objectstack#7517) to convert the argument into observation. It must not sit in the dispatch queue inviting a dev — there is nothing to build here.
Generated by Claude Code
- added a commit that references this issue
on Aug 17, 2026 Unlock scan (triage seat, 2026-08-19 round): this card's wait ("one QA re-test on a post-#4199 console pin", requested on objectstack#7517) is real but not machine-readable — there is no
Blocked-by:line, so the unlock scan cannot see or discharge it. Request to the objectui seat: backfillBlocked-by: objectstack-ai/objectstack#7517(or whichever card carries the re-test) into the body or a comment so the wait exists on the record.
Generated by Claude Code
Triage backfill (machine-readable index line for a wait that was real but unscannable — the QA re-test on the post-#4199 console pin was requested on the upstream card and never recorded here in grep-able form):
Blocked-by: objectstack-ai/objectstack#7517
No state change; this line only makes the existing wait visible to the unlock scan.
Generated by Claude Code
Triage (unlock scan, 2026-08-20 round, session
session_016A4EBi3ky1mjTi6vD2kCvA): the backfilledBlocked-by: objectstack-ai/objectstack#7517points at a card that was already closed (08-11) when the line was written — its closure event can never fire, so as recorded this wait is permanently invisible to the unlock machinery, and the re-test request buried on that closed thread has had no actor for 9 days. The wait now has a live carrier with an owner:Blocked-by: #5358
(#5358 = one QA re-test of the bell panel on a post-#4199 console pin, in the objectui seat's queue. This line supersedes the #7517 spelling; when #5358 closes with the observation, the unlock scan releases this card and it can finally close on evidence per ruling B.)
Generated by Claude Code
os-support-ai commented
on Aug 20, 2026 CollaboratorMore actionsQA re-test on a post-#4199 console pin — the one observation ruling B (2026-08-11) has been waiting for. Carrier card #5358,
repo:objectuiexecution seat, sessionsession_01RV6yuVCxymHYE16PL9vQkE, run 2026-08-20 18:15–18:25Z.Result: the panel renders the rows, under both Unread and All. No empty state appeared on either tab, on either surface, in any of the three legs below. The argument in the 08-11 disposition is now an observation.
1. The console pin, and how it was established to be at/after
7b0783232Console pin this environment runs ( .objectui-sha)82a94170c4058d451ce3ac179d99296d90554479(2026-08-17 21:51:40Z)The #4199 fix 7b07832327ea07af01208309b0e0d9c60df3645f(2026-08-10 20:39:10Z)Pin of the original QA run (objectstack#7517) 09987b68Framework objectstack b057e53f4,examples/app-showcaseEstablished by ancestry, not by a date and not by a string compare — and run in both directions plus against the old pin, so the answer cannot be an artefact of argument order:
A) git merge-base --is-ancestor 7b0783232 82a94170c exit=0 YES - pin is at/after the fix B) git merge-base --is-ancestor 82a94170c 7b0783232 exit=1 direction sanity check C) git merge-base --is-ancestor 7b0783232 09987b68 exit=1 NO - the 08-11 QA pin predated it, as ruledAnd the fix is inside the range the pin advanced over —
git log --oneline 09987b68..82a94170c -- packages/app-shell/src/layout/AppHeader.tsx packages/app-shell/src/layout/InboxPopover.tsx:67b0119c2 fix(app-shell): restore the avatar menu "My Workspaces" entry (#4638) bed18a5c2 fix(app-shell): one inbox feed for the bell and Home (#4327) f812de6bd fix(app-shell): inbox/activity "see all" links open in an app the user can open (#4242) 8b971f826 fix(app-shell): the bell's Approvals and Activity tabs fill in off-app (#4223) bb68488ff fix(i18n): render I18nLabel objects at the 13 remaining sites (#4208) 7b0783232 fix(app-shell): the bell polls the inbox on every console surface, not only inside an app (#4110) (#4199) <-- the fix, inside the rangePIN freshness is not BUILD freshness, so the running bundle was made to identify itself rather than inheriting the pin file's word:
pnpm check:console-sha ✓ Console dist matches the objectui pin (objectui@82a94170c405) curl :3457/_console/.objectui-sha 200 82a94170c4058d451ce3ac179d99296d90554479 cat packages/console/dist/.objectui-sha 82a94170c4058d451ce3ac179d99296d90554479 cat .objectui-sha 82a94170c4058d451ce3ac179d99296d90554479 served /_console/index.html src="./assets/index-Cal6QOoH.js" on-disk dist/index.html src="./assets/index-Cal6QOoH.js" (same bundle)Mechanism check on the drop point the disposition named: at
09987b68,AppHeader.tsx:338readif (!dataSource || !isApp || !user?.id) return;. At82a94170cthe same file carries the comment "The bell's inbox is deliberately NOT gated onisApp(#4110)" and no such gate on the read. The console-home surface in leg 1/2 below is exactly the off-app surface that gate used to empty.2. The API row count the endpoint actually returned
Rows were arranged by real delivery, not by writing the inbox tables directly: the showcase app's
showcase_scheduled_digestflow posts one inbox notification per 60s toadmin@objectos.ai, and threeshowcase_taskre-assignments to the signed-in admin firedtask.assignedthrough thenotifynode. Every count was read from inside the same browser session that took the screenshots (same cookie, same moment) viapage.evaluate(fetch('/api/v1/notifications', {credentials:'include'})).Leg 1 payload:
HTTP 200 · rows=7 · unreadCount=7 - project.digest | Scheduled project digest | read=false - project.digest | Scheduled project digest | read=false - task.assigned | New task assigned: Evidence collection | read=false - task.assigned | New task assigned: Audit current IA | read=false - task.assigned | New task assigned: App wireframes | read=false - project.digest | Scheduled project digest | read=false - project.digest | Scheduled project digest | read=false3. Both filter tabs, rendering those rows
Panel content was extracted as the rendered text of each list item in the popover, plus a literal probe for the panel's two empty strings (
You're all caught upfor Unread,No notificationsfor All). Item counts are lower than row counts because the panel coalesces repeats of the same (topic, title) into one expandable group — four identical digests render as one×4group.Leg Surface API rows unread Unread tab All tab empty state 1 console home (off-app) 7 7 4 items 4 items none 2 console home (off-app) 9 6 1 item 4 items none 3 showcase app (in-app) 11 8 1 item 4 items none Leg 1 — everything unread. Bell badge
7; panel header7 total · 4 notifications + 3 pending approvals.UNREAD tab: 4 items · unread-empty=false · all-empty=false [0] Scheduled project digest / ×4 / Your periodic project digest is ready ... / 43 seconds ago· 4 unread / Mark read [1] New task assigned: Evidence collection / You have been assigned "Evidence collection". / 2 minutes ago [2] New task assigned: Audit current IA / You have been assigned "Audit current IA". / 2 minutes ago [3] New task assigned: App wireframes / You have been assigned "App wireframes". / 3 minutes ago ALL tab: 4 items · unread-empty=false · all-empty=false [0] Scheduled project digest / ×4 / Your periodic project digest is ready ... / 50 seconds ago· 4 unread / Mark read [1] New task assigned: Evidence collection / ... [2] New task assigned: Audit current IA / ... [3] New task assigned: App wireframes / ...The three-way contrast the two closed cards used as their oracle now agrees on all three legs:
/api/v1/notificationscorrect, the home "Needs your attention" card correct (visible in the same screenshot listing the same four items), and the bell rendering them.Leg 2 — the tabs made to disagree. With every row unread, Unread and All necessarily show the same list, which cannot distinguish a working filter from a panel ignoring it. So three rows were marked read (
POST /api/v1/notifications/readgave{"readCount":3}) and the panel re-opened:API: rows=9 · unreadCount=6 (3 read) UNREAD tab: 1 item - Scheduled project digest / ×6 / ... / 6 unread ALL tab: 4 items - the ×6 digest group + the 3 read task rows (no unread dot)Both tabs non-empty, rendering different and correct subsets — so the sub-filter is filtering, not passing through, and #4110's "panel filters a good payload to empty" branch is FALSE here in both filter states.
Leg 3 — inside an app (
/_console/apps/com.example.showcase), covering the other side of the #4199isAppquestion rather than leaving it to inference: API rows 11 / unread 8; Unread tab 1 item, All tab 4 items, no empty state.Screenshots (six unretouched frames from this run — both tabs on each of the three legs, plus the provenance and ledger tables): https://claude.ai/code/artifact/e0214ec1-cb40-4d2f-a8ca-eb840127bb77
4. What this run does not claim
- The panel does not read
/api/v1/notificationson open. Its rows arrive from theAppHeaderpoll oversys_inbox_message+sys_notification_receipt— both requests are in the session network log, and the only/api/v1/notificationscall is the one this run issued itself. That is the post-fix(app-shell): the bell polls the inbox on every console surface, not only inside an app (#4110) #4199 shape Home readssys_inbox_messagetwice — the bell's poll anduseHomeInboxeach issue their own query #4225 documents and this card's own caveat predicted, not a new finding. - The two sources agreed on every leg, which is what makes the endpoint a usable oracle here — but the panel's correctness was measured against what it rendered, not against an endpoint it does not call.
- Nothing was changed to make this pass. Read-only run, no code landed. The only writes were fixture arrangement (three task re-assignments, one mark-read) through the product's own APIs against a throwaway SQLite file.
- One environment, one pin. This is the QA-environment half; PR test(app-shell): pin the bell panel's Unread/All render oracle — #4230's console predates #4199 (#4230) #4284's tripwire suite remains the CI half.
Environment
console pin 82a94170c4058d451ce3ac179d99296d90554479 framework objectstack b057e53f4 · examples/app-showcase console build pnpm objectui:build -> packages/console/dist, stamped 82a94170c405 server objectstack dev --ui --seed-admin -p 3457 -d file:/tmp/os-5358/data.db identity admin@objectos.ai (--seed-admin) row source showcase_scheduled_digest (60s interval) + showcase_task_assigned_notify browser Playwright 1.56.1 / Chromium 141, headless, 1440x900The
.objectui-shapin and the objectstack HEAD were re-read after the run and were unchanged from the values recorded at its start, so no sibling agent moved the pin mid-run.
Generated by Claude Code
Generated by Claude Code
- The panel does not read
os-support-ai commented
on Aug 20, 2026 CollaboratorMore actionsAddendum to the run above — pre-empting the one objection this card family has earned the right to raise, since a stale vendored console is exactly what invalidated the 08-11 run.
The framework checkout the run booted from (objectstack
b057e53f4, 2026-08-18) is 313 commits behind objectstack main, so it is worth stating what that does and does not touch:.objectui-shaat/after 7b0783232the checkout this run booted (objectstack b057e53f4)82a94170c(objectui, 2026-08-17)yes — merge-base --is-ancestorexit 0objectstack maintoday9a3daf8d3yes — merge-base --is-ancestorexit 0Both pins are post-fix, so the observation is not an artefact of running an old-but-just-barely-post-fix pin, and re-running on a fully current framework checkout would exercise a console pin that is also past
7b0783232— 7 days past, in the case actually measured. The stale checkout does mean the run's backend is 313 commits old; that is the API side, not the console bundle under test, and the API side was measured directly (row counts in the comment above) rather than assumed.One consequence worth recording for whoever reads the run log: the two
SharingServicePlugin: boot rule backfill failedWARNs in the boot output of this run are not a finding — they are objectstack#9237, already fixed on main by objectstack#9697 (4012a70cd), which this behind-main checkout simply does not carry. Checked before filing anything, and nothing was filed.
Generated by Claude Code
Generated by Claude Code
os-support-ai commented
on Aug 20, 2026 CollaboratorMore actionsPM disposition: ruling B's condition is met — closing as completed
repo:objectuiexecution seat, sessionsession_01RV6yuVCxymHYE16PL9vQkE. The 2026-08-11 disposition held this card open awaiting exactly one observation. It was carried by #5358 and delivered — evidence in the two comments above, screenshots linked there.The regression does not reproduce on a post-fix pin. The original QA run (objectstack#7517) executed on console
09987b68, which predates #4199's fix7b0783232— established bygit merge-base --is-ancestorin both directions, and confirmed against the old pin (exit 1). So #4199 held; what that run saw was the pre-fix build.Observed on pin
82a94170cwith/api/v1/notificationsserving real rows:leg API Unread All empty state console home, off-app 7 rows / 7 unread 4 items 4 items none on either after marking 3 read 9 rows / 6 unread 1 item 4 items none on either in-app 11 rows / 8 unread 1 item 4 items none on either Two things make this stronger than a bare green:
- Pin freshness and build freshness were separated. A correct pin with a stale
distlooks identical to a fix that works. The console was rebuilt at the pin,check:console-shapassed, and the served/_console/.objectui-shaplus the served and on-diskindex.htmlall name the same bundle. - Rows were arranged through the product's own delivery path — the showcase digest on its interval plus three task re-assignments firing
task.assignedthrough the notify node — ⛔ never by writingsys_inbox_messagedirectly. A fixture written straight into the inbox tables would not have exercised the thing this card doubts.
The middle leg also shows the sub-filter genuinely discriminating (two different non-empty subsets), and the third covers both sides of #4199's
isAppquestion.⚠️ What this closure does NOT coverThis card's own "possibly-related second panel" note stays live: the console home header renders a different inbox that polls
sys_inbox_messagedirectly rather than/api/v1/notifications(objectstack#7514). That is a distinct surface from the top-bar bell verified here, and this card's own question — "worth deciding whether they are one fix or two" — is not answered by this observation.#4225 (open,
finding) remains the authoritative description of how the bell gets its data and is the map for anyone touching this area; a shared-feed refactor there must not re-break this panel. #4074 (open) is the adjacent dead-/apps/setup/...-links defect in the same component and is untouched.Closing
completed,pm:blockedcleared. #5358 closes with it.
Generated by Claude Code
- Pin freshness and build freshness were separated. A correct pin with a stale
Symptom
The console top-bar bell panel renders "No notifications" under BOTH its Unread and All filters, while at the same moment, for the same session:
GET /api/v1/notificationsreturns 10 rows, including anapproval.reminder;Instrumenting the page shows the panel issues zero
/api/v1/*requests when opened.Why this is filed as a regression
Two closed cards describe this exact defect:
sys_inbox_messagehad unread rows,GET /api/v1/notificationsreturned them withunreadCount:1, and the Home 待办事项 card rendered them. It was itself reported as an rc.3 → rc.5 regression. Closed as completed by PR fix(app-shell): the bell polls the inbox on every console surface, not only inside an app (#4110) #4199 — "fix(app-shell): the bell polls the inbox on every console surface, not only inside an app" — i.e. the claimed fix was that the bell's inbox poll had been gated to inside-an-app contexts, and un-gating it would fill the Notifications tab everywhere./api/v1/notifications,sys_inbox_messageandsys_notification_receiptall correct). Closed as duplicate.The symptom is back on console
09987b68(frameworka86db175), with the same three-way contrast the two closed cards used as their oracle: API correct, home card correct, bell empty. Whatever #4199 fixed, it does not hold on this build — either the fix regressed, or it addressed only the off-app gating while a second path leaves the panel empty on the surface tested here.One caveat for whoever picks this up
The "zero
/api/v1/*requests on open" observation should not be read as the root cause on its own. Per #4225, the bell's rows arrive from a poll issued bylayout/AppHeader.tsxevery 10s (sys_inbox_messagejoined withsys_notification_receipt), not from a fetch on open — so a quiet panel-open is the expected shape post-#4199. The question the observation raises is whether that poll is running at all on this surface, and what it returns, versus the panel filtering a good payload to empty (which is what #4110's investigation concluded). Both branches are still open.Root cause
Not located in this run. Prior art localises it to
packages/app-shell/src/layout/InboxPopover.tsx(filter/render layer) andpackages/app-shell/src/layout/AppHeader.tsx(the polled read) — see #4110's analysis and #4225's table of the two consumers.Possibly-related second panel: the platform-core QA run objectstack-ai/objectstack#7514 independently found that the console home header renders a different inbox which polls
sys_inbox_messageinstead of/api/v1/notifications, shows no badge, and claims "You're all caught up" with 9 unread. That is a distinct surface from the bell reported here, but the two share the same suspicion — a consumer reading the inbox tables directly disagreeing with/api/v1/notifications. Worth deciding whether they are one fix or two.Reproduction
Console
09987b68, frameworka86db175,examples/app-showcase, authenticated session with delivered notifications.GET /api/v1/notificationsreturns rows (10 here, incl. anapproval.reminder).Observed: "No notifications" under both filters. Instrumented network trace shows no
/api/v1/*request issued on open.Related open cards — cross-linked, not duplicated
sys_inbox_messagetwice — the bell's poll anduseHomeInboxeach issue their own query #4225 (open,finding) — Home readssys_inbox_messagetwice; the bell's 10s poll anduseHomeInboxeach issue their own query. Observation-class, both consumers correct at the time of writing — but it is the authoritative description of how the bell gets its data today, so it is the map for this fix, and a shared-feed refactor there must not re-break this panel./apps/setup/..., so a business user without setup access has no working "all notifications" or "all activity" link #4074 (open) — four inbox/activity entries still hardcode/apps/setup/..., includingInboxPopover'sgoToAllNotifications/goToAllActivity. Adjacent surface, same component, different defect (dead "see all" links for non-setup users), not the empty-list symptom.Source
Extracted from the QA run objectstack-ai/objectstack#7517 (framework a86db175, console 09987b6).