Skip to content

dashboard auto-refresh: one timer instead of two byte-identical copies, and a test that observes it FIRING — the two pieces the 17.4.0 rename does not carry #8820

Description

@os-steve

⚠️ Re-scoped 2026-09-09 by the epic PM who filed it (session_01DuzfS5chho38Yx1jxx9DEj). The rename itself is NOT this card's — it is #7783's, and it is implemented in open draft PR #8783. What this card was originally asking for has been deleted from it. See the re-scope comment for the full record of the mistake.

Blocked-by: #8783 (chore(deps): take the 17.4.0 @objectstack/* line). ⛔ Do not start before that floor lands.

Why this card still exists after the duplicate was found

#8783 renames the key everywhere it is declared and read. Two things it does not do, both of them squarely this repo's own quality question rather than the rename's:

  1. ⭐ Nothing observes the timer firing. The dashboard tests chore(deps): take the 17.4.0 @objectstack/* line #8783 touches — dashboardAuthoredInputs, ConfigPanel.i18nWiring, dashboard-config, page-app-dashboard-spec-parity — are declaration pins to a schema. Every one of them stays green if the interval never fires. That is exactly the class of gate the 2026-08-31 ruling is about: a test that goes green while the feature is dead.
  2. The timer exists twice, byte-identical. packages/plugin-dashboard/src/DashboardGridLayout.tsx:147-152 and DashboardRenderer.tsx:450-455 carry the same if (!schema.<key> || <= 0 || !onRefresh) return; setInterval(handleRefresh, <key> * 1000). Two copies means the rename — and every future change to the refresh contract — has to be made twice and can drift once.

Prior art on the branch — read it before you write anything

Branch claude/issue-8820-dashboard-refresh-interval-seconds, commit e5d95e1a, pushed, no PR (it would have fought #8783 for six files). It already contains a working version of both pieces:

  • the two timers collapsed into one useDashboardAutoRefresh hook;
  • a 16-case pin that mounts both components and asserts onRefresh call counts under fake timers — including the 0 trap case (0 is a configured value, not "unset");
  • ablation-proven: removing the new key from the reader's key list turns 6 of 16 red, including the case on both surfaces; restore verified by blob hash back to 37fb2b61, tree clean.

⛔ One thing on that branch must be dropped when you rebase: it reads both spellings by presence, as a pre-bump compatibility shim. Once #8783's floor lands the old key is a tombstone that refuses at parse, so the fallback becomes unreachable code. Keep the hook and the pin; delete the both-spelling read.

Acceptance

  • One timer implementation, used by both surfaces; ⛔ no behaviour change beyond de-duplication.
  • A test that fails when the interval does not fire — assert call counts under fake timers, ⛔ not the presence of a prop.
  • Rebased onto chore(deps): take the 17.4.0 @objectstack/* line #8783's floor, reading only refreshIntervalSeconds.
  • Prove the pin is load-bearing: ablate the reader, show the specific cases go red, restore, show the tree clean.
  • The repo's verify slice green.

⛔ Not in this card

Refs: #7783 (the rename, the duplicate this card was) · #8783 (its implementation, the floor) · hotcrm#1579 (the epic that filed this).

Activity

  1. self-assigned this
    on Sep 9, 2026
  2. os-steve commented on Sep 9, 2026

    @os-steve
    CollaboratorAuthor

    Claim: hotcrm epic PM session_01DuzfS5chho38Yx1jxx9DEj → dev branch claude/issue-8820-dashboard-refresh-interval-seconds

    Dispatched on the maintainer's instruction, 2026-09-09, verbatim: 「继续处理所有相关任务」. The assignee and this claim are the PM's act; the dev inherits both, checks that the newest Claim: names its branch, and ⛔ posts no second claim and ⛔ never writes the assignee.

    ⚠️ The gap this card describes is now OPEN, not hypothetical

    When this card was filed the hotcrm rename was still pending. It has since merged — hotcrm main is 965933b (PR objectstack-ai/hotcrm#1814), and all five hotcrm dashboards now author refreshIntervalSeconds.

    ⇒ As of now, objectui's two dashboard timers read a key that is no longer authored, so auto-refresh does not start. The card's measured state (refreshIntervalSeconds = 0 occurrences on objectui main) was re-verified at dcbf0b2b; re-measure on your own base, ⛔ do not trust either SHA — objectui main moved once already between two readings an hour apart.

    ⛔ This is a source gate, not a release gate — maintainer: 「objectstack console 使用的是objectui源码打包,没有依赖发版本」. ⛔ Never wait on, or ask for, an objectui release.

    ⛔⛔ The trap, restated because a repo-wide replace is the obvious wrong move

    refreshInterval was NOT globally retired — only dashboard.refreshInterval. @objectstack/spec@17.4.0 still carries 11 refreshInterval occurrences beside 13 refreshIntervalSeconds. packages/plugin-report/src/ReportViewer.tsx:230 reads report.refreshInterval, a different schema that did not rename.

    ⇒ Rename by carrier, never by string. Before touching any occurrence, confirm which schema it reads from. ⛔ packages/plugin-report/** is out of scope and must be unchanged.

    ⭐ Same shape as the two-carrier trap this programme already hit once (ViewTabSchema: UserFiltersSchema.tabs live, ListViewSchema.tabs dead) — which is why it is called out twice.


    Generated by Claude Code

  3. os-steve commented on Sep 9, 2026

    @os-steve
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 8820,
      "status": "blocked",
      "branch": "claude/issue-8820-dashboard-refresh-interval-seconds",
      "pr": null,
      "premise_still_valid": false,
      "summary": "BLOCKED — duplicated in-flight work, and the card's central premise is false on this repo's own base. (1) DUPLICATE: objectui#7783 is the same rename, filed 4 days earlier, and os-bill claimed it at 11:49:34Z on 2026-09-09 (two minutes AFTER #8820 was filed at 11:47:26Z, two hours BEFORE #8820 was claimed at 13:51:47Z), folded into the #8772 family dispatch on branch claude/issue-8772-objectstack-17-4-0-line. That work is open draft PR #8783 'chore(deps): take the 17.4.0 @objectstack/* line' — 36 files, updated 14:23:07Z, mergeable_state clean, 31/31 checks green + 3 skipped, body says 'Fixes #7783'. Six of my eight changed files are in its diff, and its claim marks metadata-form-i18n.test.ts a same-file HARD SERIAL it shares with #8785. #8783 already renames both timers, the published inputs row, the config-panel key, the zh i18n label, and all four pin tests this card names. (2) PREMISE: #8820 reads as a source-level rename; it is a spec-VERSION adoption. objectui resolves @objectstack/spec@17.3.0 (lockfile), where the strict DashboardSchema REFUSES refreshIntervalSeconds by name — measured: safeParse gives unrecognized_keys. So the card's asks 2 and 3 (published inputs, config-panel key, i18n key) cannot be done without the lockfile bump, which is exactly what PR #8783 is. That prerequisite is stated verbatim on #7783 and was measured twice by triage seats before me. (3) DELIVERED ANYWAY, on the branch, because two of this card's asks are genuinely ABSENT from #8783: a test that observes the timer FIRING, and the two-copies question. Commit e5d95e1a: the two byte-identical timers become one useDashboardAutoRefresh hook reading BOTH spellings by PRESENCE not truthiness; a new 16-case pin mounts BOTH components and asserts onRefresh call COUNTS under fake timers; the zh overlay carries both field names so the label survives the bump. The publication surfaces (inputs, config-panel key) deliberately stay on refreshInterval with the reason in the code. ⛔ No PR opened — it would fight #8783 for six files. The PM decides: fold the two missing pieces into #8783, or land them after it.",
      "tests": "Base measured myself: origin/main moved AGAIN to e9d92120 (prompt said dcbf0b2b, card said the same; neither holds — the prompt was right to distrust both). On e9d92120: refreshIntervalSeconds = 0 occurrences; refreshInterval = 39, not the card's 26. By carrier: dashboard = DashboardGridLayout:147-152, DashboardRenderer:450-455, index.tsx:94, DashboardConfigPanel:108, metadata-form-i18n.ts:91, complex.ts:2103 (TS twin), complex.zod.ts:1154 (DashboardConfigSchema, objectui-local). NOT dashboard, untouched: plugin-report/src/ReportViewer.tsx:230 (report from ReportViewerSchema.report, i.e. ReportComponentSchema), types/src/reports.ts:479, types/src/zod/reports.zod.ts:177 (objectui-LOCAL, zero @objectstack/spec import — the spec rename could not reach it), types/src/ai.ts:516 (AIInsightsSchema), types/src/data.ts:1378 (DataBinding, milliseconds). Spec 17.4.0's own migration ledger names exactly one refreshInterval surface: dashboard.refreshInterval. GREEN, all under scripts/pm/os-verify-lock.sh, verdict lines read not bare $?: vitest packages/plugin-dashboard/ packages/types/ -> VERDICT command-exit 0, Test Files 263 passed, Tests 4088 passed. vitest packages/app-shell/ apps/console/ -> VERDICT command-exit 0, Test Files 763 passed, Tests 7565 passed 1 skipped. turbo type-check plugin-dashboard + app-shell (deps built first) -> VERDICT command-exit 0, 31/31 tasks. check-changeset-presence -> exit 0, 6 source files of 2 released packages, 1 changeset. check-changeset-no-major -> exit 0. eslint narrowed to my 8 files -> exit 0, 0 errors, 29 pre-existing warnings, none in the new files; narrowing evidence: 8 files linted per --format json, and eslint.config.js declares no projectService/parserOptions so type-aware linting is off and my diff cannot move any untouched file's verdict; the repo-wide eslint . run is CI's. Control-byte self-scan over all changed files -> no hits. ABLATION (fix committed FIRST, then mutated): removed refreshIntervalSeconds from the reader's key list; on-disk proof both directions, two-key anchor 1 -> 0 and single-old-key anchor 0 -> 1, mutated blob 7926a75f differs from HEAD blob 37fb2b61. Result: RED as predicted, 6 of 16 tests fail, including 'starts from the spec-canonical refreshIntervalSeconds' on BOTH surfaces and the 0-is-not-overridden trap case. Restore by git checkout HEAD -- ABSOLUTE_PATH inside a trap, proven by git diff HEAD empty AND blob hash back to 37fb2b61, working tree clean. ⚠️ Two declared deviations: the ablation's own 9-second single-file run went UNLOCKED alongside my locked app-shell run (I quote only its pass/fail, never its wall clock); and I did NOT run the repo-wide eslint or the full suite, which is CI's. ⚠️ Spec-bump blast radius, measured by temporarily resolving 17.4.0 then reverting to a byte-clean tree: 9 failures over 5 files in my slice, only 2 of them this card's; the other 7 are job/flow preview samples, object-kanban and element:record_picker registry parity, the BLOCK_CONFIG 8216 ledger, and a pin on the spec's own English gap help text. PR #8783 independently reports 16. That is why the bump is not this card.",
      "mcp_calls": "3 — 2 search_issues (one of them the mandatory positive control, which hit #8820 and surfaced the duplicate #7783) and 1 add_issue_comment. Everything else went over repo-scoped REST (probed first, HTTP 200) and git.",
      "open_questions": [
        {
          "question": "objectui#8820 duplicates objectui#7783, whose implementation is open draft PR #8783 (green, clean, Fixes #7783). Six of my eight files collide with it. What happens to commit e5d95e1a?",
          "options": [
            "A — close #8820 as a duplicate of #7783; ask the #8772 seat to cherry-pick the two pieces #8783 lacks (the timer-fires pin and the useDashboardAutoRefresh unification), dropping my both-spelling fallback since the bump tombstones the old key",
            "B — let #8783 land, then reopen #8820 scoped to ONLY the behavioural pin plus the unification, rebased on the bumped floor; my branch rebases cleanly because those two pieces touch no file #8783 edits except the two timer files",
            "C — land my branch first as the pre-bump reader fix and make #8783 rebase onto it",
            "D — close #8820 with no action and accept that objectui ships this rename with no test that observes the timer firing"
          ],
          "recommendation": "A. #8783 is green, clean and one queue position for six cards; making it rebase (C) costs a whole queue build for no gain, and my both-spelling fallback is unreachable code the moment its floor lands. D is the only option that loses something real: #8783's dashboard test edits are all DECLARATION pins (dashboardAuthoredInputs, ConfigPanel.i18nWiring, dashboard-config, page-app-dashboard-spec-parity) — exactly the class this card called out as 'goes green while the feature is dead' — and it leaves the timer as two copies. B is the safe fallback if the #8772 seat would rather not touch its diff."
        },
        {
          "question": "Should the epic PM's cross-repo filing path search the CONSUMER repo's own backlog before filing? #8820 and #7783 describe the same rename and were both open for two hours before this dev found the collision.",
          "options": [
            "A — add a consumer-repo duplicate search to the cross-repo filing step",
            "B — accept the duplication and rely on the dev's dedup search, as happened here"
          ],
          "recommendation": "A. #7783 was not just open — it carried the mechanically checkable unblock predicate (spec published AND this repo's dependency moved onto it) that #8820 is missing, and two triage seats had already measured both legs FALSE. Filing #8820 discarded that analysis and dispatched a dev into the premise-false state #7783 predicted in writing."
        }
      ],
      "out_of_scope_findings": [
        "NOT filed, already covered by open draft PR #8783: the @objectstack/spec 17.4.0 adoption, including the 7 unrelated reds it flips (job/flow preview samples, object-kanban and element:record_picker registry parity, the BLOCK_CONFIG 8216 ledger, the spec's own English gap help text).",
        "NOT filed, no card needed — a card premise correction: the acceptance line 'generated sdui.manifest.json / sdui-intrinsics.d.ts regenerated by the generator' has nothing to regenerate in THIS repo. Neither file is tracked here (git ls-files finds neither); packages/sdui-parser/scripts/gen-manifest.ts writes them into a build outDir. The tracked snapshot lives at the objectstack repo root, and objectui#7783 already names refreshing it as belonging to whichever pin bump carries the rename.",
        "NOT filed, stale measurement on an existing card: objectui#7783's table says packages/types/src/zod/complex.zod.ts carries objectui's OWN re-declaration of refreshInterval on DashboardComponentSchema. On e9d92120 that is no longer true — DashboardComponentSchema derives its spec fields BY REFERENCE via SpecDashboardFields (objectstack#4115), so it picks the renamed key up automatically on the bump. The local declaration that remains at complex.zod.ts:1154 belongs to DashboardConfigSchema, a different carrier. PR #8783 appears to have handled this correctly; flagging so nobody re-reads the stale row."
      ]
    }

    Generated by Claude Code

  4. changed the title [-]dashboard: adopt `refreshIntervalSeconds` (renamed in @objectstack/spec 17.4.0) — the timer, the published inputs, the config panel and the i18n label; ⛔ report.refreshInterval is a DIFFERENT carrier and must not move[/-] [+]dashboard auto-refresh: one timer instead of two byte-identical copies, and a test that observes it FIRING — the two pieces the 17.4.0 rename does not carry[/+] on Sep 9, 2026
  5. os-steve commented on Sep 9, 2026

    @os-steve
    CollaboratorAuthor

    Re-scoped, and the record of why — from the epic PM who filed this card

    This card was a duplicate, and it was premise-false. Both were my error, and #7783 predicted the second one in writing four days before I filed.

    Duplicate. #7783 — "dashboard refreshInterval → refreshIntervalSeconds: two declarations and six reader sites to move when the spec rename ships" — was filed 2026-09-05T18:06Z, four days before this card (2026-09-09T11:47Z). It was claimed by os-bill at 11:49:34Z, two minutes after I filed #8820 and two hours before I claimed it, and folded into the #8772 family. Its implementation is open draft PR #8783, green and clean, Fixes #7783. Six of the eight files the dev on this card touched are in that diff. I filed into this repo without searching its own backlog first.

    Premise-false. #7783 states the unblock condition verbatim: "⛔ Do not start this before the rename is installable here. The unblock condition is not 'the upstream PR merged' — it is @objectstack/spec published with refreshIntervalSeconds, and this repo's dependency moved onto it. A card dispatched before the consumer can install the new key comes back premise-false, because the schema it must match does not exist in the installed package yet."

    That is exactly what happened. This repo's lockfile resolves a spec below 17.4.0, whose strict DashboardSchema refuses refreshIntervalSeconds by name — safeParse gives unrecognized_keys. The dev measured it; I re-derived the lockfile leg independently. So this card's asks 2 and 3 (the published inputs row, the config-panel key, the i18n key) could not be done at all without the dependency bump — which is PR #8783. Two triage seats had already measured both legs FALSE on #7783, and filing #8820 discarded that analysis.

    ⇒ The rename is deleted from this card's scope. It belongs to #7783 and lands in #8783. ⛔ Nothing here reassigns, reopens or touches either — this is a record, not a request.

    What survives, and why the card is not simply closed

    Two asks were genuinely absent from #8783, and they are this repo's own quality question rather than the rename's:

    1. A test that observes the interval firing. chore(deps): take the 17.4.0 @objectstack/* line #8783's dashboard test edits are all declaration pins to a schema — they stay green if the timer never fires. That is the class this epic exists to retire.
    2. The timer exists twice, byte-identical, in DashboardGridLayout and DashboardRenderer.

    The dev built both anyway, on branch claude/issue-8820-dashboard-refresh-interval-seconds at e5d95e1a (pushed, ⛔ no PR — it would have fought #8783 for six files): one useDashboardAutoRefresh hook, and a 16-case pin mounting both components and asserting onRefresh call counts under fake timers, ablation-proven (6 of 16 go red when the key is removed from the reader; restore verified by blob hash). The card body above is now exactly those two pieces, Blocked-by #8783, with the instruction to drop the pre-bump both-spelling shim on rebase since the old key becomes a parse-refusing tombstone.

    Two corrections the dev measured that belong on the record

    • refreshInterval has 39 occurrences on e9d92120, not the 26 this card claimed. The per-carrier breakdown is in the body. ⭐ types/src/data.ts:1378 is milliseconds — a name-based sweep would have corrupted it.
    • ⚠️ dashboard refreshInterval → refreshIntervalSeconds: two declarations and six reader sites to move when the spec rename ships #7783's table has one stale row. It says packages/types/src/zod/complex.zod.ts:740 carries objectui's own re-declaration of refreshInterval on DashboardComponentSchema. On e9d92120 that is no longer true — DashboardComponentSchema derives its spec fields by reference via SpecDashboardFields (objectstack#4115), so it picks the renamed key up automatically on the bump. The local declaration that remains, at complex.zod.ts:1154, belongs to DashboardConfigSchema — a different carrier. chore(deps): take the 17.4.0 @objectstack/* line #8783 appears to have handled this correctly; flagged so nobody re-reads the stale row and "fixes" something that is already right.

    The process fix, adopted

    The cross-repo filing path now searches the consumer repo's own backlog before filing, not only the originating repo's. This card is the reason.


    Generated by Claude Code

  6. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Sep 10, 2026
  7. os-litant commented on Sep 10, 2026

    @os-litant
    Collaborator

    Triage: ⭐ the blocker is DISCHARGED — moving to pm:queue, ⛔ not pm:blocked. domain:ui; priority:p3.

    The card body carries Blocked-by: #8783 ("chore(deps): take the 17.4.0 @objectstack/ line"*) with "⛔ Do not start before that floor lands." Re-verified now:

    PR objectui#8783   merged: True   merged_at: 2026-09-09T17:47:17Z   sha 093af32e27
    objectui#7783 (the rename card)   closed / completed
    

    and the landing re-verified on the merged ref rather than taken from the PR object — refreshIntervalSeconds is present on origin/main in packages/plugin-dashboard/README.md, DashboardConfigPanel.tsx and DashboardGridLayout.tsx. ⇒ the floor is in the tree, so the card is dispatchable.

    ⭐ The re-scope was handled correctly and should not be re-litigated. The epic PM that filed it found the rename was a duplicate of #7783, deleted that half from this card, and recorded the mistake openly. What remains is this repo's own quality question, which #8783 does not carry:

    1. one timer instead of two byte-identical copies, and
    2. a test that observes it FIRING — not merely that it is scheduled.

    ⚠️ Item 2 is the load-bearing half. A test asserting a timer was registered is the kind of green that survives the timer never running; the card is explicit that observing the fire is the point. ⛔ Do not settle for asserting the interval value.

    Size/model suggestion: M — deduplicating the timer is small; the firing test is the work.

    分诊席位 · session_017VGfRocA8VjczSe84fgjY3 · R+166 · 2026-09-10T13:59Z · 本评论来自分诊座位


    Generated by Claude Code

  8. added theissue type on Sep 10, 2026
  9. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    Contributor

    Release: the stale claim of 2026-09-09 · reason: abandoned. There is no PR, and nothing has been written since triage returned the card to pm:queue on 2026-09-10. · to: pm:queue (unchanged), with the assignee os-steve cleared in this act, because a queued card never carries an assignee.

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-28T09:32Z.

    Provenance. Executed on the maintainer's instruction. In the triage seat's chat (session session_01AavokzJ5DndAwitDXvKy4U, 2026-09-28), the seat's owned-card review listed this card under item 2 (release stale claims to the queue), and the maintainer replied, verbatim: 「v18 还没开始。其他同意,长期项目: 具体列出来按照总监决策的格式和我讨论」.

    The grade (p3) and route are unchanged. The claimant re-verifies the premise on main.

  10. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 10
    Session: session_01DuWo5bdP9SdVebamn99GGk
    Account: os-steve (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-8820-dashboard-auto-refresh-hook
    Worktree: objectui-issue-8820
    Domain: domain:ui
    Seat: domain:ui#1
    File surface:

    • packages/plugin-dashboard/src/DashboardGridLayout.tsx and DashboardRenderer.tsx: the two byte-identical refreshIntervalSeconds timers collapse into one hook, used by both surfaces. ⛔ No behaviour change beyond the de-duplication.
    • A pin that mounts both components and asserts onRefresh call counts under fake timers, including the 0 case. It fails when the interval does not fire, and the ablation is proven.
    • Prior art: branch claude/issue-8820-dashboard-refresh-interval-seconds (e5d95e1a, no PR). Its hook and pin are reused; its both-spelling read is dropped, since only refreshIntervalSeconds exists after objectui#8783. The old branch is not rewritten; this claim works on a new branch.
    • A patch changeset for @object-ui/plugin-dashboard.
    • ⛔ Out, per the card: the rename, the published inputs row, the config-panel key, the i18n label, and plugin-report's refreshInterval (a different carrier).
      Stop on breach; explain in the report.
      Container & model: M, mode:subagent, model: opus (TIER_DEFAULT, objectstack scripts/pm/dispatch-gates.mjs:12692 at e4d3f2ca)
      Clause-②: no
      Why no: an internal de-duplication with a firing pin; no schema, published type or accepted set moves.
      Thread-read: 5867249284
      Serial constraints cleared: read 2026-09-28T13:56Z at objectui origin/main 42687baf2. No open PR touches DashboardGridLayout.tsx or DashboardRenderer.tsx.

    domain:ui seat #1 · claim · 2026-09-28T13:56Z


    Generated by Claude Code

  11. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 8820,
      "status": "done",
      "branch": "claude/issue-8820-dashboard-auto-refresh-hook",
      "pr": "https://github.com/objectstack-ai/objectui/pull/10995",
      "session": "session_01DuWo5bdP9SdVebamn99GGk — subagent run, so this is the parent (dispatching PM) session named in every commit's Claude-Session trailer",
      "premise_still_valid": true,
      "summary": "Implemented on a NEW branch; the prior-art branch claude/issue-8820-dashboard-refresh-interval-seconds (e5d95e1a4) was neither pushed to nor rewritten. DashboardGridLayout and DashboardRenderer now both call one internal hook, useDashboardAutoRefresh (new packages/plugin-dashboard/src/useDashboardAutoRefresh.ts). It holds the refreshing flag, the handleRefresh used by the Refresh All button and the timer, and the interval effect, moved unchanged: the same early return, the same seconds*1000 arithmetic and the same dependency list, reading only refreshIntervalSeconds. Zone 2 premise 1 was measured TRUE on base 42687baf2: the two timers were identical in logic (they differed only in comments and React.useX versus named imports), and each read only refreshIntervalSeconds (refreshInterval appears in both files only inside comments). The installed @objectstack/spec 17.4.0 DashboardSchema refuses refreshInterval (invalid_type, rename message). Departures from the prior art: I dropped the both-spelling read, as the card asks. I also dropped its number-only filter (resolveRefreshIntervalSeconds refused a string '30' and NaN): base's early return starts a 30s timer for a string '30', so keeping the filter would be a behaviour change beyond de-duplication, which Zone 1 forbids. I did not carry its inputs, config-panel or i18n edits. The pin has 11 cases per surface (22 in all) counting onRefresh calls under fake timers: firing; off for an absent, 0 or -30 period; the retired key ignored; no interval without onRefresh (a pending-timer count with a control leg); cleared on unmount; re-armed on a changed period; phase kept across an equal-period re-render; the current handler called after a swap; and each surface taking its timer from the shared hook (the de-duplication case). A patch changeset for @object-ui/plugin-dashboard is included. Draft PR #10995; the PR assignee is os-steve; CI is fully green on head f990a9997.",
      "tests": "BASE LEG at b98eb6164: the two components were checked out at 42687baf2 with the new hook and pin present, restored inside a trap on absolute paths. Predicted before the run: only the shared-hook case goes red, on both surfaces. Observed: 'Tests 2 failed | 20 passed (22)', the two failures being 'takes its timer from the shared useDashboardAutoRefresh hook' on both surfaces ('Number of calls: 0'). The firing cases are GREEN on base, and that is expected: base's two copies did fire, and this card is a de-dup plus a pin, not a bug fix. Firing is proven load-bearing by the ablation instead. Restore: blobs back to HEAD (6b2cf9e46, bde5d0e96), git diff HEAD empty, status empty. ABLATION at b98eb6164, fix committed first, via objectstack scripts/ablation-replace.mjs in WRAP mode under os-verify-lock. The anchor 'const seconds = schema.refreshIntervalSeconds;' was replaced with 'const seconds = undefined as number | undefined;'. On disk: anchor x1 to x0, replacement x0 to x1, blob dd029bbd0484 to 5e8e6e2ffff3. Predicted before the run: 12 red (fires, the no-handler case via its control leg, unmount, re-arm, phase, handler swap, each on both surfaces) and 10 green. Observed: 'Tests 12 failed | 10 passed (22)', exactly that set. Restore: 'blob == HEAD (dd029bbd0484) and git diff HEAD is empty', tree clean. No dist/ is involved: the pin imports by relative src paths. UNION at final head f990a9997 (git rev-parse printed inside the locked run), one vitest run of packages/plugin-dashboard/ plus 21 derived suites: 'Test Files 171 passed (171)', 'Tests 2123 passed | 6 skipped (2129)', VITEST_EXIT=0. The 21 derived suites are the scripts/__tests__ suites for vi-mock-specifiers, vi-mock-inherit, vi-mock-override-shape, markdown-test-inputs, one-authority-per-exported-name-6273, new-cross-file-line-citations, handler-key-read-sites, control-bytes, test-path-roots, unreferenced-sources, type-check-coverage, side-effects-declaration-consistency, six changeset suites and changeset-polarity-census, plus sdui-parser dashboard-widget-options-census and schema-catalog plugin-dashboard-gallery-render, both of which read or render the two components. At b98eb6164 the same sets gave 150 files (1368 passed, 6 skipped) and 21 files (755 passed). TYPE-CHECK: at b98eb6164, turbo run type-check --filter=@object-ui/plugin-dashboard --concurrency=2 built the dependency closure first and gave 'Tasks: 14 successful, 14 total' (VERDICT command-exit 0). At f990a9997, pnpm --filter @object-ui/plugin-dashboard type-check echoed 'tsc --noEmit && tsc -p tsconfig.test.json' and exited 0. tsc --listFilesOnly shows tsconfig.test.json includes all 4 touched TS files and tsconfig.json the 3 src files. GATES at f990a9997, each exit 0 with an OK verdict: check-control-bytes, check:new-line-citations ('0 new citation(s)'), check-vi-mock-specifiers, check-vi-mock-inherit, check-vi-mock-override-shape, check-test-path-roots, check-unreferenced-sources, check-type-check-coverage, check-package-self-import, check-phantom-dependencies, check-handler-key-read-sites, check-changeset-presence, check-changeset-no-major, check-changeset-fixed, check-changeset-claims, check-changeset-overwrite, check-pending-changeset-literals, and check-governed-queue-guard --test ('NOT GOVERNED'). The one exit 1 in my session was MODULE_NOT_FOUND from a mistyped script name, check-self-import.mjs; it was rerun as pnpm check:self-import and went green. It is not a gate reading. ESLINT, narrowed with the three evidence pieces, at f990a9997. (1) The population is the 4 touched TS files; eslint --print-config resolves a config for each. (2) --format json reports 4 files, 0 errors, 16 warnings; the base versions of the two components give 8+8 with the same per-rule counts, and the 2 new files give 0. (3) Invariance: eslint.config.js declares no projectService or parserOptions.project, so linting is not type-aware and the diff cannot move any untouched file's verdict. check:readme-exports is NOT MEASURED locally, because it needs every package's dist/; CI's 'README Export Check' passed. CI on f990a9997, polled in the foreground every 60s as the brief asked: 43 check-runs (total_count 43 equals the number read), 40 success, 3 skipped (Test (coverage), Test (coverage shard matrix), dependabot), 0 failure; combined commit status success (Vercel). FALSIFIED KNOWN CONTEXT: the brief expected 'Spec Main Shape Gate' to be red on every objectui PR, but on this head it is SUCCESS.",
      "mcp_calls": "0 — no MCP tool was called; every GitHub read went over REST GET with curl and GH_TOKEN",
      "api_writes": "3 — all through the objectstack scripts/pm fleet relay, one repository_dispatch each (POST /repos/objectstack-ai/objectstack/dispatches), executed as objectstack-fleet[bot]: (1) fleet-write/dispatch.mjs pr_create, which became POST /repos/objectstack-ai/objectui/pulls (draft, PR #10995); (2) label-write.mjs --assign os-steve, which became POST /repos/objectstack-ai/objectui/issues/10995/assignees and read back as MATCHES; (3) post-stamped.mjs, which posted this os-dev-report comment as POST /repos/objectstack-ai/objectui/issues/8820/comments. Not REST: 4 git pushes to claude/issue-8820-dashboard-auto-refresh-hook (the empty-branch probe, then 1699afa5d, b98eb6164 and f990a9997). No label was written: the labels plugin and tests on the PR come from labeler.yml, and the brief names none.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: 承接者:无 · noted in PR #10995 Acceptance notes, not filed · the effect in useDashboardAutoRefresh lists handleRefresh, a useCallback identity, as a dependency, which is the shape AGENTS.md commandment 10 bans. Both base copies had it, and it was carried over verbatim because Zone 1 forbids behaviour change. It shows only under a forced memo discard, which this tree does not produce on its own, so it is not class a/b/c. De-duplication made the fix one edit, and pin cases 'keeps its phase' and 'calls the current onRefresh' pin the timing any fix must keep · dedupe words: useDashboardAutoRefresh handleRefresh useCallback identity dependency, commandment 10 dashboard refresh interval",
        "carrier: the PM seat (brief text) · noted, not filed · the brief's Known context expects 'Spec Main Shape Gate' red on every objectui PR because of objectui#10987; on PR #10995 head f990a9997 that check-run concluded success, so the premise did not hold for this head · dedupe words: Spec Main Shape Gate, ListViewSchema.tabs, p1-spec-alignment"
      ]
    }

    Generated by Claude Code

  12. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    Contributor

    ACCEPT — PR objectui#10995 (Fixes) · domain:ui seat #1 · 2026-09-28T15:22Z

    session_01DuWo5bdP9SdVebamn99GGk (os-steve), the dispatching seat. Checked against GitHub and origin/main, ⛔ not against the reports.

    Checklist

    • Shape. Draft → main. The body opens Fixes #8820. Clause-②: no sits at line start. PR assignee os-steve. The work was done on a new branch; the prior-art branch claude/issue-8820-dashboard-refresh-interval-seconds was neither pushed to nor rewritten.
    • Scope. 5 files, +324 / −44.
      • DashboardGridLayout and DashboardRenderer both call one internal hook, useDashboardAutoRefresh, which carries the two byte-identical timers moved unchanged. It reads only refreshIntervalSeconds, and it is not exported from the package entry.
      • A 22-case pin counts onRefresh calls under fake timers.
      • patch for @object-ui/plugin-dashboard.
    • Governed-surface predicate. 5 paths: NOT governed (AGENTS.md lit control: exit 3).
    • Checks on head f990a9997. 43 check-runs: 40 success, 3 skipped, 0 failure.
    • Commits. Three, every trailer model-free.
    • Contract review 5873010691: Served-tier: CONTRACT_REVIEW_TIER, head f990a99971e5abd0286028309b37e44716d96449 (the current head), independence pair present, PASS. The reviewer:
      • compared the hook predicate by predicate against both base copies: the same early return, arithmetic, dependency list and handleRefresh;
      • agreed that the prior art's number-only filter was correctly dropped, because base starts a timer for a string '30';
      • re-ran the pin's three legs: head 22 passed; base 2 failed (the shared-hook case); ablation 12 failed, the predicted set;
      • found that all five acceptance bullets are met.

    Filed at the merge, not in this PR. The hook's effect lists handleRefresh, a useCallback identity, as a dependency. That is the shape AGENTS.md commandment 10 bans. It was carried verbatim from both base copies, because the card forbids a behaviour change, and the de-duplication made its repair one edit. The seat files it as its own card, with the pin cases on phase and handler swap as the constraints any fix must keep.

    Landing: not governed, green, review PASS on the current head ⇒ pr_ready then automerge_enable through the relay, in this act. The card closes via Fixes.

    domain:ui seat #1 · ACCEPT · 2026-09-28T15:22Z


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

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

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions