Skip to content

fix(app-shell): a hidden tab polls the bell and approvals feeds every three minutes - #28133

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-rrumoz
Oct 30, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-rrumoz

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread

维护者速读

  • 改了什么:控制台标签页转到后台后,铃铛(收件箱)和审批角标改为每 3 分钟查一次(原来与前台一样每 1 分钟);后台满 10 分钟后全停、回到标签页时每个数据源立刻读一次,这两条不变。
  • 为什么改:perf(console): opening a record re-reads no list, asks its field access once, and the bell polls once a minute #27907 把前台轮询改成 60 秒后,后台的 60 秒不再比前台慢,前 10 分钟里一个后台标签页和前台一样频繁地请求。按裁决 B(6522899149)把后台节奏调回明显慢于前台。
  • 风险与回滚:只改一个常量和注释;后台标签页的桌面通知最晚约 3 分钟到达(原来约 1 分钟)。回滚即 revert 本 PR。
  • 先看:sharedUserFeeds.ts 的 HIDDEN_POLL_MS 及其注释;sharedUserFeeds.hiddenStop-12439.test.tsx 新增的 "reads at most 4 times" 用例;.changeset/27626-record-open-requests.md 那一句的改正。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/hooks/__tests__/sharedUserFeeds.hiddenStop-12439.test.tsx,预期 5 条全部通过。
  • 需要你做的:无,席位落地。

Fixes #28084

Clause-②: no
Metadata-protocol: none

Ruling-ref: 6522899149 (maintainer, letter B)

What changes

All in ui/packages/app-shell/src/hooks/ (app-shell is private; it ships inside @object-ui/console), plus changesets:

  • HIDDEN_POLL_MS 60_000 to 180_000 in sharedUserFeeds.ts. A hidden tab now polls the inbox and approvals feeds every 3 minutes against the 1-minute foreground. Its docblock is rewritten: slower-when-hidden is the rule since Three datasource-admin routes answer 503 naming the wrong service (carried out of #3973 so it is not buried) #4225; the scheduler reads visibility when it arms, so the foreground tick already armed at the moment of hiding still fires once; the objectui#12439 full stop after HIDDEN_POLL_STOP_MS (10 minutes) is unchanged. The module header's "A forgotten tab goes quiet" section names the new cadence.
  • What is now true, stated where it was wrong. useInboxArrivalNotifier.ts header: a desktop notification can be "up to about three minutes late" (was "about a minute"). .changeset/27626-record-open-requests.md (unreleased): "still stop in a hidden tab" is corrected in place to "every three minutes while the tab is hidden (and not at all once it has been hidden for ten minutes)".
  • Unchanged, per the ruling: the 10-minute window and the full stop after it, the one immediate read per feed on return, the desktop notification and its toggle. No new knob, no per-feed rule.
  • New changeset .changeset/28084-hidden-tab-bell-cadence.md (@object-ui/app-shell: patch).

No i18n key, no export from a published package, no metadata change.

Tests

  • New case in sharedUserFeeds.hiddenStop-12439.test.tsx, fake timers plus visibilitychange: both feeds mounted in a visible tab, the tab hidden 30 s in. Each feed reads once at the already-armed foreground tick, then nothing until each full 180 s has passed, then one read: 4 reads per feed in the 10 hidden minutes (10 at the old cadence), nothing after the 10th minute, no timer left armed, and exactly one read per feed on return.
  • The existing hidden-cadence cases restate HIDDEN_POLL_MS as 180_000 (sharedUserFeeds.hiddenStop-12439, sharedInboxFeed.cadence-7249); their arithmetic already derives from the constant.
Readings (head e802b06)
  • Closure build pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build: lock VERDICT command-exit 0.
  • The three hidden-tab files (sharedUserFeeds.hiddenStop-12439, hiddenTabPollStop-12439, sharedInboxFeed.cadence-7249): Test Files 3 passed (3), Tests 15 passed (15), VERDICT command-exit 0.
  • Ablation through scripts/ablation-replace.mjs, anchor const HIDDEN_POLL_MS = 180_000; replaced by 60_000 (anchor 1 to 0, blob 3ebb502c to 74dfdfe3): Tests 3 failed, 7 passed of the two feed files, VERDICT command-exit 1. Red: the new case ('expected { approvals: 3, inbox: 3 } to deeply equal { approvals: 1, inbox: 1 }'), the cut-off case ('expected { approvals: 10, inbox: 10 } to deeply equal { approvals: 4, inbox: 4 }') and the 7249 hidden throttle ('expected 4 to be 2'). Restored: blob == HEAD 3ebb502c, git diff HEAD empty. Tests import src, so no dist rebuild.
  • pnpm --dir ui exec vitest run packages/app-shell/src/hooks/: Test Files 84 passed (84), Tests 715 passed (715).
  • pnpm --filter @object-ui/app-shell run type-check (tsc --noEmit and tsconfig.test.json): exit 0.
  • Derived gates: node scripts/pm/dispatch-gates.mjs --commands derived 34 families; all 34 run and recorded, 33 exit 0 (including check-sdui-manifest, vitest run scripts/, check:type-check-coverage, check:type-check-debt, check:nul-bytes); --ran reconciliation: 34 derived, 34 run, 0 NOT-MEASURED. One red, by design: check-empty-changeset --base origin/main exit 1 on .changeset/27626-record-open-requests.md (see the note below). pnpm --dir ui run check:new-line-citations: 0 new citations.

Pending release note corrected in place (confirmation for check-empty-changeset)

check-empty-changeset stays red on this PR, and that is the intended outcome. This PR rewrites one sentence of .changeset/27626-record-open-requests.md, a pending note that another PR (#27907) added. The sentence said the feeds "still stop in a hidden tab". On main they stop only after 10 continuous hidden minutes, and after this PR they poll every three minutes before that. This is the gate's DELIBERATE CORRECTION class, not a filename collision. Restoring the base text would publish a false sentence. The correction was ordered by the maintainer's ruling 6522899149 on #28084 (execution parameter 3). Only that one clause changed. The note's other bullets and its front matter are unchanged.

Acceptance notes


Generated by Claude Code

… three minutes

The foreground cadence of both shared feeds moved to 60 s, which left the
hidden cadence (also 60 s) no slower than a visible tab for the first ten
hidden minutes. HIDDEN_POLL_MS goes to 180 s; the ten-minute full stop and
the one read per feed on return are unchanged. The inbox arrival notifier
header and the pending bell-cadence changeset now state the real behaviour.

Claude-Session: https://claude.ai/code/session_01Utp6CXAofd4m4ymS3HqKt5
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling labels Oct 30, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#4 · os-bill · session_01Utp6CXAofd4m4ymS3HqKt5

Preview review (proxy for epic:#26676#1) · e802b06 · PASS

Run under ruling 6520722066 on #26676, on this PR's head (not main), as the dispatch for #28084 orders. One context-free reviewer was given only #28084's Acceptance section with the agreed hidden cadence of ruling 6522899149 (180 s), the console URL and the sign-in. It drove the console with Playwright at the head above, on examples/app-crm (objectstack dev --seed-admin --fresh, seeded admin), Console as a Vite dev server on the PR's ui/ source, CRM Leads list, idle, 1440x900. One continuous real-time run: about 233 s visible, 780 s hidden, 82 s visible again. Evidence is on the never-merged branch claude/issue-28084-proxy-evidence (294da9e), folder evidence/28084-proxy/. Times are seconds from the moment of hiding.

1. Hidden: 180 s cadence, at most 4 reads per feed in the first 10 minutes, none after: PASS.

  • Approvals (/api/v1/approvals/requests): 38.69, 218.74, 398.77, 578.82. Inbox (sys_inbox_message): 38.81, 218.85, 398.88, 578.91.
  • The read at about 38.7 is the foreground tick already armed while visible (the last visible tick was at -21.3). The following gaps are 180.03 to 180.05 s for both feeds.
  • Exactly 4 reads per feed before the 10th minute. From 600 s to the return at 780 s there were no reads of either feed (at 180 s one would have been due at about 758.8), and no other /api/ request at all while hidden.

2. Becoming visible: one read per feed: PASS.

  • After the visible visibilitychange at 780.02, approvals and inbox each read once at 780.02 (200). No second read followed. A window focus event 3 s later caused none. The next reads were the 60 s ticks at 840.

3. Control, visible: about 60 s: PASS.

  • Approvals -201.4 (page load), -141.4, -81.4, -21.3. Inbox -201.4, -141.4, -81.3, -21.2. After the return: 840.07 and 840.14.

Method. Headless Chromium does not change visibilityState on a real tab switch here (the reviewer tried bringing another page to the front and minimising, headless and under Xvfb). So an init script overrode document.hidden, document.visibilityState and document.hasFocus() from a flag, and hiding sent window blur then a bubbling visibilitychange. Chrome's background timer throttling did not apply, so the page's own timers set the cadence.

Evidence. requests log (every /api/ request with its time from hide, plus hide, show and heartbeat markers), before hiding, after returning. The two screenshots are byte-identical: the idle list page draws the same before and after, which is the expected outcome for a change with no visual part.

Notes, not REWORK. No approvals badge is drawn (none pending for the seeded admin); the approvals request was seen on every tick. No sys_notification_receipt read occurred (no inbox rows to join).

Review taken 2026-10-30T14:08Z.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh

Contract-review-claim: session_01Utp6CbDAucNyQ4UtzCJKyh · head e802b06 · 2026-10-30T14:13Z

In-seat review: the diff touches no contract face (Clause-②: no, 0 of 6 paths governed), and check-empty-changeset is red as a DELIBERATE CORRECTION of .changeset/27626-record-open-requests.md. The record below judges the rewritten sentence on this head (landing-operations, DELIBERATE CORRECTION).


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh

Contract review

Served-tier: TIER_DEFAULT
Head-sha: e802b06a47f1ec87badad6b8bcb6a7df750ddd44
Local-runs: none
Scope: ui/packages/app-shell/src/hooks/sharedUserFeeds.ts, ui/packages/app-shell/src/hooks/useInboxArrivalNotifier.ts, .changeset/27626-record-open-requests.md, .changeset/28084-hidden-tab-bell-cadence.md, two tests under ui/packages/app-shell/src/hooks/tests/

Face: none of the four contract faces. The PR body and the new changeset say Clause-②: no and Metadata-protocol: none, and 0 of 6 paths hit the governed register. This record exists because check-empty-changeset is red as a DELIBERATE CORRECTION of a pending release note, and the diff also changes code. Read against origin/main: 6 files, +93/−25. Inputs: ruling 6522899149, dev report 6524084574 and proxy preview review 6524817095.

① Derived judgments

  • HIDDEN_POLL_MS 60_000 → 180_000 (sharedUserFeeds.ts, module-private). The only reader is schedule(): hidden ? Math.max(HIDDEN_POLL_MS, this.backoffMs) : this.backoffMs. A hidden tab now waits at least 180 s between reads, and failure backoff above that still wins. Nothing exported changes. HIDDEN_POLL_STOP_MS, the return read and the push-channel stop are untouched. This matches ruling items 1 and 5.
  • The rewritten pending release note (.changeset/27626-record-open-requests.md L15). The note is unreleased (present on main at blob 97b8ea6f). Sentence by sentence:
    • Before: "they now poll every 60 s in the foreground, still stop in a hidden tab, and stop polling altogether while a host reports an open push channel". On main this is false: since objectui#12439 a hidden tab keeps polling for its first ten hidden minutes, at 60 s.
    • After: "they now poll every 60 s in the foreground, every three minutes while the tab is hidden (and not at all once it has been hidden for ten minutes), and stop polling altogether while a host reports an open push channel". On this head it is true: the hidden spacing is 180 s, the 10-minute stop holds, and the push-channel clause is unchanged. The proxy review measured hidden reads at 38.7 / 218.7 / 398.8 / 578.8 s and none after 600 s.
    • Unchanged and still true: the heading "The bell polls once a minute." (foreground) and "A new inbox message can show up to about a minute later." (visible tab).
    • The maintainer named this correction in ruling item 3, so it is a deliberate correction and not a collision. Restoring the base text would republish the false clause.
  • The new note (.changeset/28084-hidden-tab-bell-cadence.md). Each claim matches the head: three-minute hidden cadence, the armed foreground tick fires once after hiding, a full stop after ten hidden minutes, one read per feed on return, and a desktop notification up to about three minutes late.
  • Docblocks. useInboxArrivalNotifier.ts now says "up to about three minutes late" (ruling item 2). The HIDDEN_POLL_MS docblock states the hidden-slower rule again (ruling item 1). One historical sentence at sharedUserFeeds.ts L56 ("a 60 s poll is always inside it") still describes why objectui#12439 added the stop, and it is not wrong as history.
  • Pins. sharedUserFeeds.hiddenStop-12439.test.tsx adds the ruling-item-4 case: at most 4 reads per feed in ten hidden minutes, 180 s apart after the armed tick, 0 after, no timer left, one read on return. Its ablation (constant back to 60_000) went red on 3 cases, per report 6524084574.

② Semver level

patch for @object-ui/app-shell, which is private. Behaviour-only change with no export, prop or i18n change. This matches Clause-②: no and Metadata-protocol: none.

③ Boundary flags

  • open_questions is empty.
  • Check Changeset stays red by design: scripts/check-empty-changeset.mjs says the gate "stays red either way" for this class. It runs only on pull_request (.github/workflows/pr-automation.yml, no merge_group), and it is not among the required checks.

④ Design judgment

Not owed: the PR touches no schema, governed rule text or narrowing face. The design choice was the maintainer's (ruling B), and the diff carries out exactly items 1 to 5.

Implemented-by: claude/project-thread-rrumoz
Reviewed-by: session_01Utp6CbDAucNyQ4UtzCJKyh

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh

席位核对

ACCEPT · head e802b06a47f1ec87badad6b8bcb6a7df750ddd44 · 2026-10-30T14:15Z

  • Shape: draft → main, Fixes #28084. Clause-②: no. Metadata-protocol: none. Ruling-ref: 6522899149 (maintainer, letter B).

  • Change, read: HIDDEN_POLL_MS goes from 60_000 to 180_000. The 10-minute stop, the read on return, the push-channel stop and the desktop toggle are unchanged. Three docblocks and one pending release note are corrected, a new changeset is added, and one new pin covers ruling item 4. Everything stays inside Claim 6522358408's file surface.

  • Preview (ruling 6520722066): proxy preview review 6524817095, PASS on this head. A context-free reviewer drove app-crm in one continuous real-time run. Hidden reads came at 38.7 / 218.7 / 398.8 / 578.8 s from the hide, about 180 s apart after the armed foreground tick. There were none after 600 s, and each feed read once on return.

  • Evidence (report 6524084574): the new pin passes 15/15. The hooks directory passes 84 files and 715 tests, and type-check exits 0. The ablation back to 60_000 turned 3 cases red and was restored. All 34 derived gates ran, with 0 NOT-MEASURED.

  • Gates (check-runs on the head): 43 success, 11 skipped, 1 failure. NOT governed: 0 of 6 paths, 118 changed lines. All 10 required checks are green.

  • The one red, Check Changeset (check-empty-changeset), is red by design. The PR rewrites .changeset/27626-record-open-requests.md, a pending note it did not add. That is the DELIBERATE CORRECTION class, and ruling item 3 named the correction. The three conditions for queueing with a red check all hold:

    1. The gate's own source says it stays red for this class on a pushed branch (scripts/check-empty-changeset.mjs, the DELIBERATE CORRECTION remedy: "this gate stays red either way").
    2. It does not run on merge_group, because .github/workflows/pr-automation.yml triggers only on pull_request.
    3. This comment records the gate and its reason.

    The confirmation is the same-head in-seat review 6524955459 (claim 6524927066), which judges the before and after sentences. Before: "still stop in a hidden tab", which is false on main. After: "every three minutes while the tab is hidden (and not at all once it has been hidden for ten minutes)", which is true on this head. The base text must not be restored.

  • Landing: non-governed, every required check green, and the one red is by design. The seat lands it with pr_ready and automerge_enable.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 30, 2026 14:18
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit 48c7780 Oct 30, 2026
55 of 56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-rrumoz branch October 30, 2026 14:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Console bell and approvals badge keep polling every 60 s for the first 10 minutes of a hidden tab

1 participant