You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
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
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:
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.
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.
⭐ 第五处是卡片没提、但很有分量的一条:sharedInboxFeed.cadence-7249.test.tsx:28 的注释把这个 hook 点名为 "the other" —— 也就是说节奏钉子自己知道这个第二调度器的存在,并且是把它当作"不参与"来写的。这既证实了卡片的危险判断(它是已知的隐患),也说明现有的钉子不会在有人挂载它时报红(卡片说 "it would not be caught by the shared feed's own pins",这一条因此成立)。
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/main62597c5: 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.
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.
Before deleting, re-run the consumer census: in-repo grep plus the sibling repos. If a mount has appeared, stop and report.
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.
Update the cadence pin's comment that names this hook in the same PR.
Removing a published export ⇒ Clause-②: yes (as the ruling states). A review-tier contract review runs before enqueue.
{
"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."
]
}
✅ 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):
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.
The cloud repo's consumers are unmeasured. If a cloud surface mounts this hook, its next @object-ui/react upgrade fails to type-check.
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
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
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
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
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
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
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
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.
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.
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.
The changeset's census sentence (see ②) could name all six base-commit hits; optional tidy before the body ships verbatim into CHANGELOG.md.
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.
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.
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.
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.
Activity
os-zhuang commentedon Sep 5, 2026
分诊 —
domain:ui/priority:p3/pm:queue/finding锚定 (anchoring):
packages/react/src/hooks/useClientNotifications.ts⇒domain:ui(objectui 的 ui lane 含已发布库)。在
origin/maina472b07上复核 —— 零调用点属实全仓 grep
useClientNotifications,本文件之外的命中只有五处,无一是挂载:没有任何组件或应用挂载它。 卡片的第一条属实。
⭐ 第五处是卡片没提、但很有分量的一条:
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:notifications.md:224教它)。[console] The bell's Approvals and Activity tabs are still dead outside an app page (sameisAppgate as #4110) #4197 / Home readssys_inbox_messagetwice — the bell's poll anduseHomeInboxeach issue their own query #4225 / Home's action centre counts ALREADY-READ messages as needing attention —useHomeInboxnever joinssys_notification_receipt#4316 三张卡花力气把 console 的用户域 feed 统一到一个调度器上,就是为了让铃铛与 Home 行动中心不会发两次读、不会对已读状态各执一词 —— 挂载这个 hook 会精确地把那个形状请回来。不上 p2:需要有人主动挂载才会成为缺陷,而今天没人挂。
定型:三条处置的型不同
setInterval)⛔ 本席不裁决。但记一条判断输入:卡片自己把危险定位在"文档在邀请人挂载它",而三条处置里只有第一和第三直接解决那个邀请;第二条(指向共享 feed)解决的是"挂载之后会怎样",是最保守也最贵的一条。若维护者的判断是"通知桥不是受支持的扩展点",那第一条最干净 —— 但它是破坏性变更,要走人工底线。
⛔ 给执行席的边界
content/docs/guide/notifications.md:224),⛔ 否则文档会教一个不存在的 hook。并带 changeset(这是已发布 API)。sharedInboxFeed.cadence-7249.test.tsx加一条钉子,让"存在第二个针对同一批 ADR-0030 行的调度器"这件事能被门禁看见。今天那个测试只是在注释里提到它 —— 注释拦不住下一个人。这条是本卡真正的长期价值。⛔ 分诊席不认领、不派单、不写码、不裁决处置。
Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
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:48ZThread re-read to its last comment (5548568302) in this act. Read on objectui
origin/main62597c5:useClientNotificationsis exported frompackages/react/src/hooks/index.tsand taught bycontent/docs/guide/notifications.md. It has zero mounts; the only other hit is the cadence pin's comment insharedInboxFeed.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:uistand; removal of a published export ⇒Clause-②: yes.Generated by Claude Code
objectstack-fleet commentedon Sep 25, 2026
Claim: PM loop round 1 —
domain:uiexecution seatSession:
session_01BA3nKVUwKQJf8DBxrSVtNCBranch:
claude/issue-7389-retire-use-client-notificationsWorktree:
objectui-issue-7389Domain:
domain:uiSeat:
domain:ui#1File 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 insharedInboxFeed.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 consumersClause-②: yes
Thread-read: 5809224666
Serial constraints cleared: open-PR file lists read 2026-09-25T03:17Z ⇒ none touches these files. Live
pm:dispatchedclaims read at the same time ⇒ none names them.Scope
Ruling
5809224666(UI closure batch 2 item 4, letter remove; maintainer 「同意」):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.Clause-②: yes(as the ruling states). A review-tier contract review runs before enqueue.⇒
Fixes #7389.domain:uiseat #1 ·session_01BA3nKVUwKQJf8DBxrSVtNC· claim · 2026-09-25T03:19Zobjectstack-fleet commentedon Sep 25, 2026
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." ] }objectstack-fleet commentedon Sep 25, 2026
✅ ACCEPT — PR objectui#10468 at
6308b6440· contract review PASS · entering the merge queuedomain:uiseat #1,session_01BA3nKVUwKQJf8DBxrSVtNC. The claim5826117848saysClause-②: yes: the ruling5809224666removes 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/reactexport list goes from 299 to 296 names. The only names removed areuseClientNotifications,UseClientNotificationsOptionsandUseClientNotificationsResult.Consumer census re-run before deleting, as the ruling orders (record ①2):
@object-ui/*dependency at all.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:
5548568302point 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. 承接者:无.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):
minor:useClientNotificationsand its two types leave@object-ui/react. The migration is the shell's shared inbox feed, throughAppHeaderor theglobal:notificationsnode.@object-ui/reactupgrade fails to type-check.Contract review
Served-tier:
CONTRACT_REVIEW_TIERHead-sha:
6308b64402ea045dc0afd8d38ea2a2836ebc3332Reviewed from 2026-09-25T03:58Z to 2026-09-25T04:18Z against objectui
origin/mainat274e14af4e730a20a8b76d7eb68f855214a2ccfe(fetched at the start of the review); the branchclaude/issue-7389-retire-use-client-notificationscarries one commit over merge basee6203d7568102e2a6a0f145f65587f89b7a567fd, and the PR is a draft with labelspackage: react,tests,package: app-shell. Read in full: ruling5809224666, the card body of objectui#7389, triage5548568302, claim5826117848, theos-dev-reportcomment5826432580, the PR body,git diff origin/main...6308b6440(6 files, 54 insertions, 240 deletions) including the whole deleted hook, objectuiAGENTS.md, the objectstackAGENTS.mdPost-Task Checklist,.changeset/config.json, the.changeset/7389-retire-use-client-notifications.mdbody, and the headers ofscripts/check-doc-example-types.mjs,scripts/check-changeset-presence.mjs,scripts/check-changeset-claims.mjs,scripts/check-governed-queue-guard.mjs(itsGOVERNED_SURFACESarray) andscripts/pm/os-verify-lock.sh.Method: the shared checkouts
/home/user/objectuiand/home/user/objectstackwere 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 withpnpm install --offline --frozen-lockfilefrom the store (thepnpm-lock.yamlblob2dbe67b25is identical at base, at head and in the sibling worktree/home/user/objectui-issue-9674). The package closure was built throughbash /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-distturned 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 othercheck:*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 aspackages/*/srcwithout a trailing/**). The worktree and a shallowhotcrmclone were removed afterwards; the shared checkout's HEAD and porcelain are unchanged.① Derived judgments
Exactly the ruled removal. The diff touches six paths: the hook
packages/react/src/hooks/useClientNotifications.ts(deleted, 229 lines), oneexport *line inpackages/react/src/hooks/index.ts, the "Server-side notifications" section ofcontent/docs/guide/notifications.md(3 lines replaced by 14), four comment lines inpackages/app-shell/src/hooks/__tests__/sharedInboxFeed.cadence-7249.test.tsx, one six-line ledger row inscripts/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) ofpackages/react/dist/index.d.tsbuilt from base source has 299 names and the same listing built from head source has 296; the diff is exactlyUseClientNotificationsOptions,UseClientNotificationsResultanduseClientNotifications, with nothing added. The file's four private helpers (toSeverity,ClientBearingDataSource,hasGetClient,useResolvedClient) have no other definition or reader underpackages/react/srcat head.check:unreferenced-sourcesexits 0 at head. Nothing beyond the ruled hook and its own two types leaves the package. PASSCensus. objectui at head
6308b6440,git grep -l -wfor each of the three names overpackages/*/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 ofpackages/react/CHANGELOG.md. Positive controls with the same pathspecs:useSettledSchema25 files,useNotifications9 files. An import-form grep over.ts/.tsx/.js/.mjs/.jsx/.jsonat head, CHANGELOG excluded, finds only the comment. At the merge base and atorigin/mainthe 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. objectstackorigin/mainatd4c897e0e700d96e29ff8a39e91c85f2a8a30909(fetched during this review; the dev read338feda6): ten hits, every one incontent/docs/releases/v17/17-0.mdxor aCHANGELOG.md(client, console, runtime, service-messaging, spec); a code-only grep (.ts/.tsx/.js/.mjs/.cjs/.json/.yml/.yaml, CHANGELOG excluded) is empty; controlSchemaRendererin.ts/.tsx: 18 files. hotcrm, shallow clone ofmainat087b7c5(887 tracked files): zero hits; control@objectstack/imports in 270.ts/.tsxfiles. Nopackage.jsonin hotcrm depends on any@object-ui/*package, so that zero is structural. The cloud repository is NOT MEASURED from this container (git ls-remoteasks 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. PASSDocs. Every sentence of the rewritten section holds against head. The rows are read by
useSharedInboxFeedinpackages/app-shell/src/hooks/sharedUserFeeds.tsasfind('sys_inbox_message')joined withfind('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 areuseInboxBell(which importsuseSharedInboxFeed) anduseHomeInbox; no other non-test file callsfindonsys_inbox_message. Table row one:AppHeaderis exported frompackages/app-shell/src/index.ts, mountsuseInboxBell()and rendersInboxPopover, which has anotificationstab and theinbox-bell-badgeelement; it is mounted byHomeLayout,ConsoleLayout,OrganizationsLayout,OrganizationLayoutandAiChatPage, so "the console's own layouts already mount it" is true. Table row two:packages/app-shell/src/views/global-notifications-renderer.tsxregistersnotificationsundernamespace: 'global'(so the node type isglobal:notifications), mounts the sameuseInboxBell(), and the registration is imported for side effect from the app-shell barrel. "The feed itself is not exported" is true: app-shell'spackage.jsonexports only.and./styles.css,src/index.tsandsrc/hooks/index.tsexport neithersharedUserFeedssymbols noruseInboxBellnoruseHomeInbox; the guide namessharedUserFeedsonly 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 teachesclient.notifications.*, and no code at head callsclient.notifications.list,markReadormarkAllRead. Gates at head:docs:check-linksexit 0 (17 scan roots),check:doc-snippetsexit 0 (673 of 673 blocks judged, 0 failed),check:doc-examplesexit 0 (123 blocks, 35 compile, 88 fail, all 88 declared by the 88 ledger rows),check:readme-exportsexit 0 with 0 unbuilt packages, andcheck:doc-fences,check:doc-types,check:doc-example-ids,check:doc-example-readersexit 0. PASSThe surface extension. The gate's own header instructs this maintenance rather than forbidding it: ledger keys are
path symbol #ordinalwith 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) makesnode scripts/check-doc-example-types.mjsexit 1 withSTALE 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 aButtonit never declared) on an example that no longer exists, the compiled tier is unchanged, and the gate's own suitescripts/__tests__/check-doc-example-types.test.tspasses (61 tests).scripts/is not a governed surface:GOVERNED_SURFACESisdocs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md, andnode scripts/check-governed-queue-guard.mjs --testover 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 overAGENTS.mdprints 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. PASSThe cadence pin.
git diff e6203d756 6308b6440onsharedInboxFeed.cadence-7249.test.tsxchanges 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 12expect(calls each; no assertion, import or fixture moved. The new comment text is true against head (the hook is retired from@object-ui/reactby objectui#7389). The file runs green at head: 1 file, 5 tests passed. PASSReverse proof. The probe lives in
packages/components/probe-7389/of the worktree (untracked, removed with the worktree) and compiles withtscunderstrict,noEmit,moduleResolution: bundler;--traceResolutionshows@object-ui/reactresolved 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, throughpackages/components/node_modules/@object-ui/react, the package'sexportsmap and the built declaration, which is a consumer's resolution path. At head the removed-names probe exits 2:useClientNotificationsgives TS2724 ("has no exported member named 'useClientNotifications'. Did you mean 'useNotifications'?"), andUseClientNotificationsOptionsandUseClientNotificationsResulteach give TS2305; the control probe (useSettledSchema,useNotifications,NotificationProvider) exits 0; a runtimeimport('@object-ui/react')frompackages/componentsreports'useClientNotifications' in module === falsewithuseNotificationsanduseSettledSchemaas functions; zero files underpackages/react/dist/hooksname the hook anddist/hooks/index.d.tshas zero lines naming it. At base (worktree checked out toe6203d756, reactdistwiped and rebuilt withtscpluscheck-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 anddist/hooks/index.d.tsnames it once. The removal is real at the built surface and only there does it differ from base. PASS② Semver level
minoron@object-ui/react, which is what the changeset declares..changeset/config.jsonplaces@object-ui/reactand@object-ui/app-shellin the singlefixedgroup (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-presencereports 3 source files of 2 released packages changed and 1 changeset declared (exit 0).Clause-②: yeson the PR body means at leastminorunder objectstack Post-Task step 3, and objectuiAGENTS.mdforbidsmajoroutside a synchronized objectstack major;check-changeset-no-major,check-changeset-fixedandcheck-changeset-overwriteexit 0, andcheck-changeset-claims --base e6203d756 --head 6308b6440flags 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,AppHeaderand aglobal:notificationsnode, 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 (asetIntervaloverclient.notifications.list, rows copied into the notification provider throughnotify()) matches the deleted source; the shell readssys_inbox_messagewithsys_notification_receiptthrough one feed (judgment 3); "Unchanged:NotificationProvider,useNotificationsand the rest" is confirmed by the export listing, in which both remain and nothing else moved; the census sentences match judgment 2 (hotcrmat087b7c5is the same commit I cloned; objectstack338feda6is the dev's read and my later read agrees); "NOT MEASURED" for importers outside those repositories is the honest boundary; the closingminor-not-majorsentence 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
@object-ui/*dependency in anypackage.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.5548568302point 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.CHANGELOG.md.check:published-distruns a full build of its own (240 seconds in this review) without saying so in its usage line; the dev reported the same class forcheck:node-esm-load. Both therefore run unlocked when invoked as ordinary gates.check:node-esm-loadwas not re-run here (the dev reports exit 1 with six provenance refusals recorded as NOT MEASURED); it is outside the dispatch list.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.needs:contract-reviewlabel.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'sdist/index.d.ts, and theminorchangeset 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:uiseat #1 · review · 2026-09-25T04:23Zfeat(react)!: retire useClientNotifications, a second inbox poller wi…