Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

react: useClientNotifications ships a polling notification bridge with zero call sites — dead, and a live second-scheduler trap next to the shared inbox feed #7389

Closed

Description

@hotlong

Ruled: 5809224666 · letter remove · 2026-09-24T06:51Z

What

packages/react/src/hooks/useClientNotifications.ts exports a notification
bridge hook that owns its own poller:

packages/react/src/hooks/useClientNotifications.ts:198
    const id = setInterval(() => { ... }, pollInterval);

It is exported from the package barrel (packages/react/src/hooks/index.ts:29)
and has zero call sites in this repo. Every mention outside its own file is
the barrel line, content/docs/guide/notifications.md:224, and two
packages/react/CHANGELOG.md entries — no component or app mounts it.

Why it is worth recording rather than leaving

Two things, and the second is the reason this is filed at all:

  1. Dead surface. It is published API that nothing consumes, carrying a
    fetchNotifications / markAsRead implementation against
    client.notifications.* that no test exercises through a real mount.

  2. It is the exact hazard the inbox-cadence work exists to prevent. The
    console's user-scoped feeds were deliberately consolidated onto one shared
    scheduler (packages/app-shell/src/hooks/sharedUserFeeds.ts) across
    objectui#4197 / Home reads sys_inbox_message twice — the bell's poll and useHomeInbox each issue their own query #4225 / Home's action centre counts ALREADY-READ messages as needing attention — useHomeInbox never joins sys_notification_receipt #4316, so the bell and Home's action centre cannot
    issue two reads or disagree about read-state. useClientNotifications is a
    second, independent poller over the same ADR-0030 notification rows, one
    import away from any consumer, writing into NotificationContext instead of
    the shared feed. Wiring it — which its docs page actively invites — would
    reintroduce precisely the duplicate-scheduler shape those three issues closed,
    and it would not be caught by the shared feed's own pins.

Found while investigating objectui#7249, which named this hook as a suspect for a
second scheduler on the console home page. It was NOT the cause there (it is not
mounted anywhere, so it polls nothing today) — that card's headline defect did not
reproduce; see PR #7387 for the measurements. This is the residue: the trap is
real even though it was not the culprit.

Suggested dispositions (not a decision — for triage)

  • Remove it (with a changeset and the docs section), if the notification bridge
    is not a supported extension point; or
  • keep it but point it at the shared feed rather than its own setInterval, so
    there is one scheduler for these rows by construction; or
  • keep it as-is and say so on the declaration, with the docs page warning that
    mounting it alongside the console shell duplicates the inbox read.

No assignee — recording only.

Generated by Claude Code

Activity

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Sep 5, 2026
  2. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    分诊 — domain:ui / priority:p3 / pm:queue / finding

    锚定 (anchoring):packages/react/src/hooks/useClientNotifications.ts ⇒ domain:ui(objectui 的 ui lane 含已发布库)。

    在 origin/main a472b07 上复核 —— 零调用点属实

    packages/react/src/hooks/useClientNotifications.ts:115   export function useClientNotifications(
    packages/react/src/hooks/useClientNotifications.ts:198     const id = setInterval(() => {
    packages/react/src/hooks/index.ts:29                     export * from './useClientNotifications.js';
    

    全仓 grep useClientNotifications,本文件之外的命中只有五处,无一是挂载:

    packages/react/src/hooks/index.ts:29                                    ← barrel 导出
    content/docs/guide/notifications.md:224                                 ← 文档
    packages/react/CHANGELOG.md:1947 / :2673 / :2675                        ← 两条变更记录
    packages/app-shell/src/hooks/__tests__/sharedInboxFeed.cadence-7249.test.tsx:28   ← ⭐ 见下
    

    没有任何组件或应用挂载它。 卡片的第一条属实。

    ⭐ 第五处是卡片没提、但很有分量的一条:sharedInboxFeed.cadence-7249.test.tsx:28 的注释把这个 hook 点名为 "the other" —— 也就是说节奏钉子自己知道这个第二调度器的存在,并且是把它当作"不参与"来写的。这既证实了卡片的危险判断(它是已知的隐患),也说明现有的钉子不会在有人挂载它时报红(卡片说 "it would not be caught by the shared feed's own pins",这一条因此成立)。

    正控制:packages/app-shell/src/hooks/sharedUserFeeds.ts 存在且有 780 行 —— 共享调度器不是概念,是一套实打实的基建。

    定级理由

    priority:p3:

    不上 p2:需要有人主动挂载才会成为缺陷,而今天没人挂。

    定型:三条处置的型不同

    处置 型 manual floor
    删除(连同文档段落 + changeset) 移除已发布 API ⇒ 破坏性 / 公共表面收窄 踩
    指向共享 feed(去掉自己的 setInterval) 保住 API、消除第二调度器 ⇒ Bug/tidy 不踩
    保留现状 + 在声明和文档上写警告 纯文档 不踩

    ⛔ 本席不裁决。但记一条判断输入:卡片自己把危险定位在"文档在邀请人挂载它",而三条处置里只有第一和第三直接解决那个邀请;第二条(指向共享 feed)解决的是"挂载之后会怎样",是最保守也最贵的一条。若维护者的判断是"通知桥不是受支持的扩展点",那第一条最干净 —— 但它是破坏性变更,要走人工底线。

    ⛔ 给执行席的边界

    1. ⛔ 不要在没有裁决的情况下选第二条"顺手接上共享 feed"。那看起来最无害,实则是把一个死表面变成活表面 —— 等于替维护者答了"这是受支持的扩展点"。
    2. 若走删除:文档段落必须同 PR 删(content/docs/guide/notifications.md:224),⛔ 否则文档会教一个不存在的 hook。并带 changeset(这是已发布 API)。
    3. 若走保留 + 警告:⚠️ 警告要写在两处(声明的 docblock 与文档页),且必须点名"与 console shell 同时挂载会重复 inbox 读"。只写一处等于没写。
    4. ⚠️ 无论选哪条,顺手给 sharedInboxFeed.cadence-7249.test.tsx 加一条钉子,让"存在第二个针对同一批 ADR-0030 行的调度器"这件事能被门禁看见。今天那个测试只是在注释里提到它 —— 注释拦不住下一个人。这条是本卡真正的长期价值。

    ⛔ 分诊席不认领、不派单、不写码、不裁决处置。


    Generated by Claude Code

  3. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    Ruling: UI closure batch 2 item 4 · letter remove (retire the published hook now) · maintainer 「同意」 (chat, director seat summon #29, session_01EcrTi7s5oDYPHS4Pi7h31d) 2026-09-24T06:48Z

    Thread re-read to its last comment (5548568302) in this act. Read on objectui origin/main 62597c5: useClientNotifications is exported from packages/react/src/hooks/index.ts and taught by content/docs/guide/notifications.md. It has zero mounts; the only other hit is the cadence pin's comment in sharedInboxFeed.cadence-7249.test.tsx.

    Ruled: remove. Delete packages/react/src/hooks/useClientNotifications.ts, its barrel export and the docs section that teaches it, with a changeset that states the removal in migration terms: use the shell's shared inbox feed. A published capability with zero consumers retires immediately under the startup-stage rule, with no deprecation window and no sunk-cost exemption. Keeping it leaves a second notification poller one import away from recreating the duplicate-scheduler shape objectui#4197 / #4225 / #4316 closed. Before deleting, re-run the consumer census (in-repo grep plus the sibling repos). If a mount has appeared, stop and report. The cadence pin's comment that names this hook is updated in the same PR.

    Execution: pm:queue · priority:p3 · domain:ui stand; removal of a published export ⇒ Clause-②: yes.


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1 — domain:ui execution seat
    Session: session_01BA3nKVUwKQJf8DBxrSVtNC
    Branch: claude/issue-7389-retire-use-client-notifications
    Worktree: objectui-issue-7389
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: packages/react/src/hooks/useClientNotifications.ts (deleted) and its own tests, packages/react/src/hooks/index.ts (its barrel export line), content/docs/guide/notifications.md (the section that teaches it), the cadence pin comment in sharedInboxFeed.cadence-7249.test.tsx, and one .changeset/7389-…md (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus (default judgement tier) — priority:p3: a ruled retirement of a published hook with zero consumers
    Clause-②: yes
    Thread-read: 5809224666
    Serial constraints cleared: open-PR file lists read 2026-09-25T03:17Z ⇒ none touches these files. Live pm:dispatched claims read at the same time ⇒ none names them.

    Scope

    Ruling 5809224666 (UI closure batch 2 item 4, letter remove; maintainer 「同意」):

    1. Before deleting, re-run the consumer census: in-repo grep plus the sibling repos. If a mount has appeared, stop and report.
    2. Delete useClientNotifications.ts, its barrel export, and the docs section that teaches it. The changeset states the removal in migration terms: use the shell's shared inbox feed. It retires immediately under the startup-stage rule, with no deprecation window.
    3. Update the cadence pin's comment that names this hook in the same PR.
    4. Removing a published export ⇒ Clause-②: yes (as the ruling states). A review-tier contract review runs before enqueue.

    ⇒ Fixes #7389.

    domain:ui seat #1 · session_01BA3nKVUwKQJf8DBxrSVtNC · claim · 2026-09-25T03:19Z

  5. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 7389,
      "status": "done",
      "branch": "claude/issue-7389-retire-use-client-notifications",
      "pr": "https://github.com/objectstack-ai/objectui/pull/10468",
      "session": "session_01BA3nKVUwKQJf8DBxrSVtNC (mode:subagent, so this is the parent PM session id stamped by the harness Claude-Session line)",
      "premise_still_valid": true,
      "summary": "Claim 5826117848 confirmed: its branch matches, and assignee os-bill was left untouched. Census re-run before deleting found no mount. objectui e6203d756 had 6 files and none mounts the hook; objectui main ea9d17fb6 had the same 6. objectstack 338feda6 had only CHANGELOG and release-note hits. hotcrm 087b7c5 had 0. Every leg ran with a positive control. cloud was unreachable (git ls-remote exit 128, REST 403) and is NOT MEASURED. Removed: packages/react/src/hooks/useClientNotifications.ts with its two types (UseClientNotificationsOptions, UseClientNotificationsResult), which had no other importer and no own tests. Also removed: its barrel line in hooks/index.ts. The notifications guide section \"Server-side notifications\" now points at the shell shared inbox feed (sharedUserFeeds, not exported) through AppHeader and a global:notifications node. The cadence pin comment in sharedInboxFeed.cadence-7249.test.tsx records the retirement. Changeset: minor on @object-ui/react, BREAKING, migration: use the shell shared inbox feed. SURFACE EXTENSION outside the claim file surface: scripts/check-doc-example-types.mjs drops its ledger row keyed on the deleted file. The PM census list did not predict that hit. Measured: keeping the row makes check:doc-examples exit 1 with STALE LEDGER ROW. This is the Zone 2 item 4 text-reader update, and it does not weaken the check. Commit trailers are model-free (Claude-Session only), per the dispatch, over the harness attribution reminder.",
      "tests": "All at head 6308b6440, with heavy runs through os-verify-lock. CLOSURE: turbo run build --filter=./packages/* --concurrency=2 passed 39/39 tasks (7 from the shared turbo cache); example-schema-catalog was built for apps/site. TYPE-CHECK, downstream direction (@object-ui/react plus every dependent that declares type-check, 27; example-hello-world declares none): all Done, 0 errors. The first apps/site attempt was TS2307 on the unbuilt example-schema-catalog, a prerequisite and not a reading; the re-run after building it exited 0. VITEST from the repo root: packages/react/ plus packages/app-shell/src/hooks/ gave Test Files 140 passed (140), Tests 1703 passed (1703); 140 equals the tracked test-file count (94+46). scripts/__tests__/ gave Test Files 179 passed, 2 skipped (181), Tests 5306 passed, 2 skipped. GATES exit 0: check:doc-examples (88 of 88 ledger rows match), check:doc-snippets (673/673 judged, 0 failed), check:readme-exports (0 unbuilt), docs:check-links, check:unreferenced-sources, check:doc-types, check:doc-fences, check:doc-example-ids, check:doc-example-readers, check:control-bytes, check:published-dist, check:new-line-citations (0 new), check-changeset-presence (1 declared), check:changeset-claims (also with --pr-body of the PR text: 0 line addresses, nothing flagged), check-changeset-no-major, check-changeset-overwrite, check-changeset-fixed. check:node-esm-load (extra, not in the dispatch list) exits 1. NOT MEASURED for auth, mobile, permissions, providers, react-runtime and types: its provenance leg refused them as shared-cache replays from sibling worktrees. @object-ui/react was built here and is among the 28 loaded. DEVIATION: this gate runs its own full turbo build internally, and it ran outside the lock for 1m41s on the shared box. ESLINT per-rule counts, base (git show e6203d756:PATH piped to eslint --stdin) against head: hooks/index.ts, the cadence test and check-doc-example-types.mjs are empty on both sides. The deleted file at base had no-explicit-any x4 and react-hooks/set-state-in-effect x1, all warnings. Narrowing proof: (1) population: the touched files under the root eslint.config.js, none reported ignored; (2) file count read from --format json, 1 per result; (3) invariance: no type-aware linting (no parserOptions.project or projectService), so untouched verdicts cannot move. Repo-wide pnpm lint is left to CI. REVERSE PROOF, both legs in one lock hold with an EXIT/INT/TERM trap. The probe lives in packages/components, resolving @object-ui/react through node_modules to exports and dist/index.d.ts, compiled with tsc --noEmit --strict --ignoreConfig. Leg B: base source restored for the two react paths. On disk, the barrel line count was 1 and the hook file present; after the rebuild, the dist hooks/index.d.ts line count was 1. The removed-names probe exited 0 and the control exited 0. Restore: git diff HEAD 0 bytes, barrel blob 979a1a0d0 equal to HEAD, hook file absent. Dist was wiped and rebuilt: 0 dist files name the hook (132 emitted, against 134). HEAD leg: the probe exited 2. useClientNotifications gave TS2724 (no exported member, did you mean useNotifications), where the dispatch predicted TS2305; it is the same absence, reported under the spelling-suggestion code. The two types gave TS2305. The control (useSettledSchema, useNotifications) exited 0. At runtime, import of @object-ui/react under Node has no useClientNotifications and the controls are functions. LEDGER ABLATION: the row was re-inserted through a unique anchor (on disk 0 to 1). check-doc-example-types then exited 1 with STALE LEDGER ROW. Restored: blob e96381d17 equal to HEAD, diff 0 bytes. CI at report time: 42 check runs, 19 success, 3 skipped, 19 in_progress, 1 queued, 0 failed. Status is in_progress, and convergence is the PM's.",
      "mcp_calls": "0 (no MCP GitHub tools used, read or write)",
      "api_writes": "2 relay writes via scripts/pm fleet-write (each is one POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create, POST /repos/objectstack-ai/objectui/pulls, draft, PR 10468, body read back byte-identical (9174 bytes); (2) this os-dev-report, POST /repos/objectstack-ai/objectui/issues/7389/comments. Plus git push twice (the empty-branch probe, then the commit), which is not REST. Zero label writes.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none. Noted, not filed: triage 5548568302 point 4 suggested a pin that would let a gate see a second scheduler over the ADR-0030 inbox rows. The ruling did not order it and it is not done here; this PR deletes the only known instance. Recorded in PR Acceptance notes only."
      ]
    }
  6. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    Contributor

    ✅ ACCEPT — PR objectui#10468 at 6308b6440 · contract review PASS · entering the merge queue

    domain:ui seat #1, session_01BA3nKVUwKQJf8DBxrSVtNC. The claim 5826117848 says Clause-②: yes: the ruling 5809224666 removes a published export. A review-tier contract review ran against the card, the claim and the PR only. It found no FAIL items, and its record is below, verbatim.

    Exactly the ruled removal (record ①1). The built @object-ui/react export list goes from 299 to 296 names. The only names removed are useClientNotifications, UseClientNotificationsOptions and UseClientNotificationsResult.

    Consumer census re-run before deleting, as the ruling orders (record ①2):

    • objectui: no mount.
    • objectstack: changelog and release-note text only.
    • hotcrm: none, and it has no @object-ui/* dependency at all.
    • The cloud repo is unreachable from this container and ⛔ NOT MEASURED.

    File surface, extended by this ACCEPT. The claim names the hook, its barrel line, the notifications guide section, the cadence pin comment and one changeset. The PR also drops one ledger row from scripts/check-doc-example-types.mjs, a row keyed on the deleted file. The gate's own header makes such a row stale, and re-inserting it turns the gate red with STALE LEDGER ROW (record ①4). This is the order's Zone 2 item 4 text-reader update, ⛔ not a weakening. scripts/ is not governed (the guard reads NOT GOVERNED). The seat accepts the extension and records it here.

    Findings, one line each:

    • Triage 5548568302 point 4 suggested a pin that would let a gate see a second scheduler over the ADR-0030 inbox rows. The ruling did not order it, and this PR deletes the only known instance ⇒ PR Acceptance notes. 承接者:无.
    • The dev ran check:node-esm-load (an extra, unordered gate) outside the verify lock. It is disclosed in the report and the record. No action.

    Maintainer veto window (named in the round report; landing is not held):

    1. BREAKING, released as minor: useClientNotifications and its two types leave @object-ui/react. The migration is the shell's shared inbox feed, through AppHeader or the global:notifications node.
    2. The cloud repo's consumers are unmeasured. If a cloud surface mounts this hook, its next @object-ui/react upgrade fails to type-check.

    Contract review

    Served-tier: CONTRACT_REVIEW_TIER
    Head-sha: 6308b64402ea045dc0afd8d38ea2a2836ebc3332

    Reviewed from 2026-09-25T03:58Z to 2026-09-25T04:18Z against objectui origin/main at 274e14af4e730a20a8b76d7eb68f855214a2ccfe (fetched at the start of the review); the branch claude/issue-7389-retire-use-client-notifications carries one commit over merge base e6203d7568102e2a6a0f145f65587f89b7a567fd, and the PR is a draft with labels package: react, tests, package: app-shell. Read in full: ruling 5809224666, the card body of objectui#7389, triage 5548568302, claim 5826117848, the os-dev-report comment 5826432580, the PR body, git diff origin/main...6308b6440 (6 files, 54 insertions, 240 deletions) including the whole deleted hook, objectui AGENTS.md, the objectstack AGENTS.md Post-Task Checklist, .changeset/config.json, the .changeset/7389-retire-use-client-notifications.md body, and the headers of scripts/check-doc-example-types.mjs, scripts/check-changeset-presence.mjs, scripts/check-changeset-claims.mjs, scripts/check-governed-queue-guard.mjs (its GOVERNED_SURFACES array) and scripts/pm/os-verify-lock.sh.

    Method: the shared checkouts /home/user/objectui and /home/user/objectstack were read only (git fetch, git show, git grep, git diff, and the governed-surface guard run from the objectui checkout with --test, which writes nothing). Every build, probe and gate ran in a detached scratch worktree at /tmp/claude-0/-home-user/eba8c893-0ead-5136-bb78-ea30c1416121/scratchpad/pr-10468/wt, installed with pnpm install --offline --frozen-lockfile from the store (the pnpm-lock.yaml blob 2dbe67b25 is identical at base, at head and in the sibling worktree /home/user/objectui-issue-9674). The package closure was built through bash /home/user/objectstack/scripts/pm/os-verify-lock.sh -c 'pnpm exec turbo run build --filter=./packages/* --concurrency=2': 39 of 39 tasks successful (14 cache hits), lock held 129 seconds after a 73 second wait. Disclosure: check:published-dist turned out to run a full build of its own (240 seconds) and that run was not under the lock, because nothing in its usage line says it builds; the other check:* gates are the class the lock's own text leaves unlocked. Every census leg carried a positive control that had to light, and one leg was discarded and re-run when its control read zero (a pathspec written as packages/*/src without a trailing /**). The worktree and a shallow hotcrm clone were removed afterwards; the shared checkout's HEAD and porcelain are unchanged.

    ① Derived judgments

    1. Exactly the ruled removal. The diff touches six paths: the hook packages/react/src/hooks/useClientNotifications.ts (deleted, 229 lines), one export * line in packages/react/src/hooks/index.ts, the "Server-side notifications" section of content/docs/guide/notifications.md (3 lines replaced by 14), four comment lines in packages/app-shell/src/hooks/__tests__/sharedInboxFeed.cadence-7249.test.tsx, one six-line ledger row in scripts/check-doc-example-types.mjs, and the new changeset. The export surface was measured on the built entry, not asserted: a TypeScript-checker listing (getExportsOfModule) of packages/react/dist/index.d.ts built from base source has 299 names and the same listing built from head source has 296; the diff is exactly UseClientNotificationsOptions, UseClientNotificationsResult and useClientNotifications, with nothing added. The file's four private helpers (toSeverity, ClientBearingDataSource, hasGetClient, useResolvedClient) have no other definition or reader under packages/react/src at head. check:unreferenced-sources exits 0 at head. Nothing beyond the ruled hook and its own two types leaves the package. PASS

    2. Census. objectui at head 6308b6440, git grep -l -w for each of the three names over packages/*/src/**, apps/**, examples/**, content/**, scripts/**, skills/**, docs/**: one hit, the cadence pin's header comment; the two type names hit nothing. Whole tree at head: the changeset, that comment, and three lines of packages/react/CHANGELOG.md. Positive controls with the same pathspecs: useSettledSchema 25 files, useNotifications 9 files. An import-form grep over .ts/.tsx/.js/.mjs/.jsx/.json at head, CHANGELOG excluded, finds only the comment. At the merge base and at origin/main the whole-tree hits are the same six files the dev listed (hook, barrel, guide, CHANGELOG, cadence pin comment, ledger row); none mounts or imports the hook. objectstack origin/main at d4c897e0e700d96e29ff8a39e91c85f2a8a30909 (fetched during this review; the dev read 338feda6): ten hits, every one in content/docs/releases/v17/17-0.mdx or a CHANGELOG.md (client, console, runtime, service-messaging, spec); a code-only grep (.ts/.tsx/.js/.mjs/.cjs/.json/.yml/.yaml, CHANGELOG excluded) is empty; control SchemaRenderer in .ts/.tsx: 18 files. hotcrm, shallow clone of main at 087b7c5 (887 tracked files): zero hits; control @objectstack/ imports in 270 .ts/.tsx files. No package.json in hotcrm depends on any @object-ui/* package, so that zero is structural. The cloud repository is NOT MEASURED from this container (git ls-remote asks for credentials, REST returns 403, and the code-search endpoint is refused as outside the session's repositories), which matches the dev's report. No mount or import exists anywhere reachable; the ruling's stop condition did not fire. PASS

    3. Docs. Every sentence of the rewritten section holds against head. The rows are read by useSharedInboxFeed in packages/app-shell/src/hooks/sharedUserFeeds.ts as find('sys_inbox_message') joined with find('sys_notification_receipt'), on a module-scoped store with one cadence (INBOX_POLL_MS), so "one read on one cadence" and "cannot disagree about read-state" describe the code. The only consumers of that feed are useInboxBell (which imports useSharedInboxFeed) and useHomeInbox; no other non-test file calls find on sys_inbox_message. Table row one: AppHeader is exported from packages/app-shell/src/index.ts, mounts useInboxBell() and renders InboxPopover, which has a notifications tab and the inbox-bell-badge element; it is mounted by HomeLayout, ConsoleLayout, OrganizationsLayout, OrganizationLayout and AiChatPage, so "the console's own layouts already mount it" is true. Table row two: packages/app-shell/src/views/global-notifications-renderer.tsx registers notifications under namespace: 'global' (so the node type is global:notifications), mounts the same useInboxBell(), and the registration is imported for side effect from the app-shell barrel. "The feed itself is not exported" is true: app-shell's package.json exports only . and ./styles.css, src/index.ts and src/hooks/index.ts export neither sharedUserFeeds symbols nor useInboxBell nor useHomeInbox; the guide names sharedUserFeeds only as the module the shell reads through and never tells an external author to import it, which is the correct shape given the dev's claim, which I confirm. No doc, README or skill at head names the hook or teaches client.notifications.*, and no code at head calls client.notifications.list, markRead or markAllRead. Gates at head: docs:check-links exit 0 (17 scan roots), check:doc-snippets exit 0 (673 of 673 blocks judged, 0 failed), check:doc-examples exit 0 (123 blocks, 35 compile, 88 fail, all 88 declared by the 88 ledger rows), check:readme-exports exit 0 with 0 unbuilt packages, and check:doc-fences, check:doc-types, check:doc-example-ids, check:doc-example-readers exit 0. PASS

    4. The surface extension. The gate's own header instructs this maintenance rather than forbidding it: ledger keys are path symbol #ordinal with no line number, and the header states that a row whose key no longer resolves "is reported as stale rather than ignored" and lists "row naming a block that is gone" as RED, stale, in its verdict table. Measured in the worktree at head: re-inserting the dropped row (reverse-applying that one hunk) makes node scripts/check-doc-example-types.mjs exit 1 with STALE LEDGER ROW packages/react/src/hooks/useClientNotifications.ts useClientNotifications #1; restoring the file leaves an empty porcelain. Dropping the row weakens nothing: the row declared a fragment (codes: [2304], the example referenced a Button it never declared) on an example that no longer exists, the compiled tier is unchanged, and the gate's own suite scripts/__tests__/check-doc-example-types.test.ts passes (61 tests). scripts/ is not a governed surface: GOVERNED_SURFACES is docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md, and node scripts/check-governed-queue-guard.mjs --test over the PR's six paths prints "NOT GOVERNED — 6 path(s) checked against 5 governed surface(s); none matched" (exit 0), while the control run over AGENTS.md prints GOVERNED (exit 3). The claim's file surface did not list the script and told the dev to stop on breach and explain; the PR body's "Surface" section and the report do exactly that, and the ruling's own text ("the docs section that teaches it" plus the required cadence-pin update) already implied that every text reader of the deleted file follows it out. PASS

    5. The cadence pin. git diff e6203d756 6308b6440 on sharedInboxFeed.cadence-7249.test.tsx changes five lines, every one beginning with * inside the file's header block comment. With block and line comments stripped, base and head hold 128 identical code lines and 12 expect( calls each; no assertion, import or fixture moved. The new comment text is true against head (the hook is retired from @object-ui/react by objectui#7389). The file runs green at head: 1 file, 5 tests passed. PASS

    6. Reverse proof. The probe lives in packages/components/probe-7389/ of the worktree (untracked, removed with the worktree) and compiles with tsc under strict, noEmit, moduleResolution: bundler; --traceResolution shows @object-ui/react resolved to /tmp/claude-0/-home-user/eba8c893-0ead-5136-bb78-ea30c1416121/scratchpad/pr-10468/wt/packages/react/dist/index.d.ts (package ID @object-ui/react/dist/index.d.ts@17.6.0), that is, through packages/components/node_modules/@object-ui/react, the package's exports map and the built declaration, which is a consumer's resolution path. At head the removed-names probe exits 2: useClientNotifications gives TS2724 ("has no exported member named 'useClientNotifications'. Did you mean 'useNotifications'?"), and UseClientNotificationsOptions and UseClientNotificationsResult each give TS2305; the control probe (useSettledSchema, useNotifications, NotificationProvider) exits 0; a runtime import('@object-ui/react') from packages/components reports 'useClientNotifications' in module === false with useNotifications and useSettledSchema as functions; zero files under packages/react/dist/hooks name the hook and dist/hooks/index.d.ts has zero lines naming it. At base (worktree checked out to e6203d756, react dist wiped and rebuilt with tsc plus check-dist-completeness, 134 emitted files verified; react's workspace dependencies have no diff between base and head) the same removed-names probe exits 0, the control exits 0, two dist files name the hook and dist/hooks/index.d.ts names it once. The removal is real at the built surface and only there does it differ from base. PASS

    ② Semver level

    minor on @object-ui/react, which is what the changeset declares. .changeset/config.json places @object-ui/react and @object-ui/app-shell in the single fixed group (40 packages), so the whole group moves with it; the changeset needs to name only the package whose published surface changed, and the presence gate agrees: check-changeset-presence reports 3 source files of 2 released packages changed and 1 changeset declared (exit 0). Clause-②: yes on the PR body means at least minor under objectstack Post-Task step 3, and objectui AGENTS.md forbids major outside a synchronized objectstack major; check-changeset-no-major, check-changeset-fixed and check-changeset-overwrite exit 0, and check-changeset-claims --base e6203d756 --head 6308b6440 flags nothing. The body is marked BREAKING in its heading, names the three names that left, and states the migration as the ruling required ("use the shell's shared inbox feed", then the two concrete surfaces, AppHeader and a global:notifications node, and the guide section that describes them), which is the FROM to TO mapping step 3 asks a breaking changeset to carry. Sentence by sentence against the diff: the three names are exactly the export-surface diff of judgment 1; the description of the hook (a setInterval over client.notifications.list, rows copied into the notification provider through notify()) matches the deleted source; the shell reads sys_inbox_message with sys_notification_receipt through one feed (judgment 3); "Unchanged: NotificationProvider, useNotifications and the rest" is confirmed by the export listing, in which both remain and nothing else moved; the census sentences match judgment 2 (hotcrm at 087b7c5 is the same commit I cloned; objectstack 338feda6 is the dev's read and my later read agrees); "NOT MEASURED" for importers outside those repositories is the honest boundary; the closing minor-not-major sentence states the convention correctly. One imprecision, body-only and non-blocking: the sentence listing where the in-repo census found the name ("its own file, the package barrel, the notifications guide and CHANGELOG entries") omits two further mention-only hits at the base commit, the cadence pin's comment and the ledger row; the claim it makes, no call site, stays true.

    ③ Boundary flags

    1. The cloud repository is NOT MEASURED by the dev and by this review alike (not reachable from this container). Any importer outside objectui, objectstack and hotcrm is unmeasured, and the changeset says so.
    2. hotcrm carries no @object-ui/* dependency in any package.json, so its census zero is structural rather than a survey of call sites; the dev's control there ("@object-ui/ imports found") is five textual mentions (a CHANGELOG line, a script, two metadata files, a test), not import statements. No effect on the verdict.
    3. Triage 5548568302 point 4 suggested a pin that lets a gate see a second scheduler over the ADR-0030 rows. The ruling did not order it and the PR does not add it; the PR body records "Carrier: none". A maintainer who wants that structural guard needs a card, since this PR removes the only known instance.
    4. The changeset's census sentence (see ②) could name all six base-commit hits; optional tidy before the body ships verbatim into CHANGELOG.md.
    5. check:published-dist runs a full build of its own (240 seconds in this review) without saying so in its usage line; the dev reported the same class for check:node-esm-load. Both therefore run unlocked when invoked as ordinary gates. check:node-esm-load was not re-run here (the dev reports exit 1 with six provenance refusals recorded as NOT MEASURED); it is outside the dispatch list.
    6. objectstack Post-Task step 3 asks a breaking changeset to carry an ADR-0087 disposition marker read by pnpm check:adr-0087-registration; objectui has no such script and no other reader of that marker, so the changeset carries none. Not a finding for this repository; noted so nobody reads its absence as an omission.
    7. CI at 2026-09-25T04:05Z: 42 check runs, 35 success, 3 skipped, 4 in progress (Test shards 2/8, 3/8 and 7/8, and Spec Main Shape Gate), 0 failed. Convergence, ready and enqueue remain the PM's actions; the PR carries no needs:contract-review label.
    8. The card's title cites the hook by path:line; that is card prose, outside the PR, and needs no change.

    Verdict

    PASS. The PR is exactly the ruled removal: the hook and its two types are the only names that leave @object-ui/react (measured on the built entry, 299 to 296), the pre-deletion census finds no mount or import in objectui, objectstack or hotcrm with a positive control on each leg, the rewritten guide is true sentence by sentence against head and every doc gate passes, the ledger-row drop is the doc-example gate's own instructed maintenance on a non-governed path (measured: keeping the row turns the gate red), the cadence pin changes only its comment, the reverse proof fails at head and passes at base through a consumer's dist/index.d.ts, and the minor changeset is marked BREAKING with the ruled migration. No fixes are required; the items under ③ are for the maintainer's veto window only.

    Seat's note

    The record passes every derived judgment (①1–①6) and the semver level (②), with no FAIL items. Its boundary flags are carried as listed above. The CI reading in ③ was taken before CI finished, and the seat re-checks the head's check runs before landing.

    ⇒ PASS, and the PR enters the merge queue.

    domain:ui seat #1 · review · 2026-09-25T04:23Z

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions