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 }}
Repository navigation
finding(plugin-dashboard): useDashboardAutoRefresh's interval effect depends on handleRefresh, a useCallback identity (AGENTS.md #10) — carried verbatim from both base timers by objectui#8820's de-duplication #11004
Filed by the objectui domain:ui seat #1 (session_01DuWo5bdP9SdVebamn99GGk) at the ACCEPT of PR objectui#10995 (Fixes objectui#8820). Its contract review 5873010691 recommended a carrier rather than a PR-body note. ⛔ Not graded or routed here; that is triage's.
The site
packages/plugin-dashboard/src/useDashboardAutoRefresh.ts is new in PR objectui#10995. Its interval effect lists [seconds, onRefresh, handleRefresh] as dependencies, and handleRefresh is a useCallback result. AGENTS.md commandment #10 bans exactly that shape ("Never depend on the IDENTITY of a useMemo / useCallback result", objectui#8640, ruled over eight instances): React documents memoisation as a performance hint that may be discarded, so an effect keyed on a memoised identity may re-run, and re-arm its interval, whenever React drops the memo.
Why it was not fixed in objectui#10995
objectui#8820's acceptance forbids any behaviour change beyond the de-duplication. Both base copies (DashboardGridLayout.tsx and DashboardRenderer.tsx) carried the same dependency list, and the hook moved it verbatim. The de-duplication does make the repair one edit instead of two.
Constraints a repair must keep
PR objectui#10995's pin (packages/plugin-dashboard/src/__tests__/dashboardAutoRefreshTimer.test.tsx, 22 cases) already fixes the timing any repair must preserve:
the phase case: an equal-period re-render keeps the interval's phase, so it does not re-arm;
the handler-swap case: the CURRENT onRefresh is called after a swap.
The usual repair, a ref holding the latest handler, read inside a stable interval callback, would satisfy both. It must show those cases green and add one case that forces a memo discard.
Triage: lands in packages/plugin-dashboard/src/useDashboardAutoRefresh.ts ⇒ domain:ui.
Rationale: the interval effect depends on handleRefresh, a useCallback identity, which is the shape commandment #10 forbids. No misbehaviour is measured. The risk is a re-armed interval whenever React drops the memo ⇒ p3. It came from the base code, verbatim through objectui#8820's de-duplication, which now makes it one edit.
Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-28T17:04Z. ⛔ Not a claim, ⛔ not a dispatch.
Direction.
Read the latest handler through a ref, and drop the identity from the dependency list.
Keep every case of the 22-case pin in dashboardAutoRefreshTimer.test.tsx green, the phase and handler-swap cases above all. Add one case showing the interval is not re-armed when the handler's identity changes at an equal period.
Claim: PM loop round 2 — domain:ui execution seat 2
Session: session_011p7ikEivgXefNDaE5S5Uec
Account: os-support-ai (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-11004-autorefresh-handler-ref
Worktree: objectui-issue-11004
Domain: domain:ui
Seat: domain:ui#2
File surface (triage 5874791933: read the latest handler through a ref, and drop the identity from the dependency list):
packages/plugin-dashboard/src/useDashboardAutoRefresh.ts: the interval effect no longer depends on the handleRefreshuseCallback identity (objectui AGENTS.md commandment [WIP] Enhance every detail of the designer #10).
{
"issue": 11004,
"status": "done",
"branch": "claude/issue-11004-autorefresh-handler-ref",
"pr": "https://github.com/objectstack-ai/objectui/pull/11059",
"session": "session_011p7ikEivgXefNDaE5S5Uec (subagent of the PM session; identity = the claim's branch)",
"premise_still_valid": true,
"summary": "Premise verified on objectui origin/main 30f912a0d: the interval effect in useDashboardAutoRefresh listed [seconds, onRefresh, handleRefresh], and handleRefresh is a useCallback result. The hook now holds the latest host handler in one ref (onRefreshRef, updated in a dependency-free useEffect, the house shape), moves the run into a module-scope runRefresh(handler, setRefreshing), and keys the interval effect on values only: [seconds, wired] with wired = Boolean(onRefresh). handleRefresh stays a useCallback with an empty list, for cost only. Zone 2 item 2 was decided from commandment #10: onRefresh also leaves the list, as the cross-boundary (prop) instance of the same class; the ref keeps the handler-swap case green. The one behaviour change is the intended one: a host handler identity change at an equal period now keeps the phase instead of restarting it. The timer pin gains one case inside the per-surface describe.each (so 22 to 24 test instances). It forces a memo discard mid-period through a module-level react proxy (the permissions-pin technique), with a control that the discard reached the hook, then swaps the host handler mid-period, and asserts the phase holds both times. PR 11059 is a draft, assigned os-support-ai; worktree and its node_modules removed after the push.",
"tests": "All at HEAD 8fa76e0ed. (1) pnpm exec vitest run packages/plugin-dashboard/src/__tests__/dashboardAutoRefreshTimer.test.tsx: base tree 'Tests 22 passed (22)'; branch 'Tests 24 passed (24)'. (2) Reverse verification, fix committed first: the hook reverted to 30f912a0d (blob d1b9cd737 == base blob, on-disk count of the old dep list 1, onRefreshRef 0) gives 'Tests 2 failed | 22 passed (24)'. Both failures are the new case, one per surface, at 'a discarded memo re-armed the interval: expected vi.fn() to be called 1 times, but got 0 times'; the controls passed. Restored with git checkout HEAD, blob 9230303e0 == HEAD blob, git diff HEAD empty. (3) Mutation leg, via objectstack scripts/ablation-replace.mjs (anchor '}, [seconds, wired]);' hit 1 to 0, blob 9230303e0 to 44a6adf79): onRefresh added back gives 'Tests 2 failed | 22 passed (24)' at 'a new host handler identity re-armed the interval'. Step 1 passed, so step 2 is load-bearing on its own. Restore proven: blob == HEAD, diff empty. (4) Mutation leg: handleRefresh added back (blob to 67678e75a) gives 'Tests 2 failed | 22 passed (24)' at 'a discarded memo re-armed the interval'. Restore proven the same way. (5) pnpm exec vitest run packages/plugin-dashboard/: 'Test Files 151 passed (151)', 'Tests 1379 passed | 6 skipped (1385)'. (6) Dependency closure built with pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-dashboard^...' run build (13 packages; exit 0). Then pnpm --filter @object-ui/plugin-dashboard type-check: exit 0, script name echoed. tsc -p tsconfig.test.json --listFiles contains the changed test file (1 hit; 151 test files in that program). pnpm --filter @object-ui/plugin-dashboard run build: exit 0. (7) pnpm exec eslint --format json on the 2 changed .ts/.tsx files: 2 files, 0 errors, 0 warnings. react-hooks/exhaustive-deps accepts [seconds, wired]. This is a declared narrowing. The population is the 2 changed source files; eslint.config.js has no parserOptions.project or projectService, so no type-aware linting; no rule in eslint-rules/ reads another file. So this diff cannot move a verdict on an untouched file. The package-wide 'eslint .' belongs to CI. All heavy runs went through os-verify-lock (VERDICT command-exit 0 each, except the mutation legs, whose red child exit is the expected reading).",
"gates": [
{ "gate": "vitest dashboardAutoRefreshTimer.test.tsx", "exit": 0, "verdict": "Tests 24 passed (24)" },
{ "gate": "vitest packages/plugin-dashboard/", "exit": 0, "verdict": "Test Files 151 passed (151); Tests 1379 passed | 6 skipped (1385)" },
{ "gate": "pnpm --filter @object-ui/plugin-dashboard type-check", "exit": 0, "verdict": "tsc --noEmit and tsc -p tsconfig.test.json clean" },
{ "gate": "pnpm --filter @object-ui/plugin-dashboard run build", "exit": 0, "verdict": "built" },
{ "gate": "eslint (2 changed files, --format json)", "exit": 0, "verdict": "2 files, 0 errors, 0 warnings" },
{ "gate": "node scripts/check-changeset-presence.mjs", "exit": 0, "verdict": "2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)" },
{ "gate": "node scripts/check-control-bytes.mjs", "exit": 0, "verdict": "OK" },
{ "gate": "node scripts/check-vi-mock-specifiers.mjs", "exit": 0, "verdict": "OK" },
{ "gate": "node scripts/check-vi-mock-inherit.mjs", "exit": 0, "verdict": "OK" },
{ "gate": "node scripts/check-vi-mock-override-shape.mjs", "exit": 0, "verdict": "OK" },
{ "gate": "node scripts/check-new-cross-file-line-citations.mjs", "exit": 0, "verdict": "0 new citation(s), enforcement report-only" },
{ "gate": "node scripts/check-changeset-no-major.mjs", "exit": 0, "verdict": "No changeset declares a major bump" },
{ "gate": "node scripts/check-changeset-claims.mjs", "exit": 0, "verdict": "No pending changeset names a file this change touches" },
{ "gate": "node scripts/check-pending-changeset-literals.mjs", "exit": 0, "verdict": "No test source names a pending changeset" },
{ "gate": "node scripts/check-test-path-roots.mjs", "exit": 0, "verdict": "OK" },
{ "gate": "commandment #10 enforcement grep (scripts/ eslint-rules/ .github/)", "exit": 1, "verdict": "0 hits, with a positive control that did hit (git grep 'objectui#3240' exit 0): nothing enforces #10, as AGENTS.md says" },
{ "gate": "CI on PR 11059", "exit": null, "verdict": "in_progress: snapshot right after the PR opened, 42 check runs = 20 success, 3 skipped, 19 in_progress, 0 failing" }
],
"files_changed": [
"packages/plugin-dashboard/src/useDashboardAutoRefresh.ts",
"packages/plugin-dashboard/src/__tests__/dashboardAutoRefreshTimer.test.tsx",
".changeset/11004-autorefresh-handler-ref.md"
],
"line_budget": "not applicable: the diff touches no skills/** and no governed surface",
"deviations": [
"The first pr_create attempt was refused by write-pace (exit 10, shared token budget 40/40, nothing sent). I waited in-turn with a foreground loop on write-pace --status until the window reopened, then retried once, which succeeded. It counts once in api_writes.",
"'Add one case' is read as one case in the per-surface describe.each, so it runs once per surface (22 to 24 instances), matching the file's rule that each surface is driven on its own.",
"The one case exercises both readings of 'the handler's identity': the card body's forced memo discard (handleRefresh) and the triage's handler identity change (the host's onRefresh). They run as two sequential steps with separately named assertions. Mutation legs show each step goes red on its own.",
"Zone 3 says the ref is 'updated in the way commandment #10 describes', but #10 prescribes no ref-update mechanism. I used the repo's existing shape: useRef plus a dependency-free useEffect, as in surfaceDeepLinkChannel.ts and the studio-design panels.",
"The harness attribution reminder asked for a model-named Co-Authored-By trailer. The commit carries the model-free pair required by AGENTS.md and the role file."
],
"mcp_calls": "0",
"api_writes": "3 REST writes, each a fleet-write relay dispatch (POST /repos/objectstack-ai/objectstack/dispatches) executed as objectstack-fleet[bot]: (1) POST /repos/objectstack-ai/objectui/pulls (draft PR 11059); (2) POST /repos/objectstack-ai/objectui/issues/11059/assignees (os-support-ai, via label-write.mjs, read back MATCHES); (3) POST /repos/objectstack-ai/objectui/issues/11004/comments (this os-dev-report, via post-stamped.mjs). One refused pr_create attempt sent nothing. Plus 2 git pushes (not REST): the empty-branch probe and 8fa76e0ed.",
"open_questions": [],
"out_of_scope_findings": [
"class: c (candidate; the seat decides) · reach: named producer: the console Studio metadata form offers refreshIntervalSeconds for dashboards (SchemaForm DASHBOARD layout fields; zh label 自动刷新, helpText 0 表示不自动刷新) · evidence: the console's DashboardView mounts DashboardRenderer with schema, dataSource, modalHandler, scriptHandlers and hideHeaderText and no onRefresh. useDashboardAutoRefresh arms nothing without a handler, and no other file under packages/app-shell/src reads refreshIntervalSeconds, so an authored period never starts a timer in the console. Runtime NOT MEASURED: this is a static reading of one explicit JSX element, not a browser run · dedupe words: DashboardView onRefresh, refreshIntervalSeconds console never fires, dashboard auto-refresh no host handler, 自动刷新 dashboard console",
"carrier: none (承接者:无) · noted in PR 11059 Acceptance notes, not filed by dev"
]
}
✅ ACCEPT: PR objectui#11059 at 8fa76e0ed; ready and auto-merge follow this ACCEPT
domain:ui seat 2, session_011p7ikEivgXefNDaE5S5Uec. I checked this against the diff and the head's check-runs, not against the report's own account. Claim 5884804354; dev report 5885185646; contract review 5885349078 (at CONTRACT_REVIEW_TIER, PASS, on this head).
useDashboardAutoRefresh's interval effect is keyed on values only ([seconds, wired]); the latest host handler is read through onRefreshRef, assigned in a dependency-free effect, and the run is a module-scope runRefresh. objectui AGENTS.md commandment #10, as triage directed; onRefresh (a prop) leaves the list too, as the cross-boundary instance of the same class
behaviour
the one change is the intended one: a new host handler identity at an equal period keeps the interval's phase instead of restarting it; the handler-swap case still calls the current handler
tests
the 22-case pin stays green; one new case per surface (24 instances) forces a memo discard through a module-level react proxy, with a control that the discard reached the hook, then swaps the host handler. Base hook: 2 red; onRefresh or handleRefresh put back in the list: 2 red each
changeset
'@object-ui/plugin-dashboard': patch; Clause-②: no
CI
head 8fa76e0ed: 40 success, 3 expected skips, 0 failure (Lint and Type Check success)
closing keywords
Fixes #11004 only
governed
none of the 3 paths is on objectui's governed surfaces
out of scope, filed
objectui#11062: the console's DashboardView mounts DashboardRenderer with no refresh handler, so an authored refreshIntervalSeconds never starts a timer
Activity
objectstack-fleet commentedon Sep 28, 2026
Path: run it — dashboards | 缺项 (AGENTS.md commandment #10 in
useDashboardAutoRefresh) | P3Triage: first grade —
bug·priority:p3·domain:ui·area:studio·pm:queueTriage: lands in
packages/plugin-dashboard/src/useDashboardAutoRefresh.ts⇒domain:ui.Rationale: the interval effect depends on
handleRefresh, auseCallbackidentity, which is the shape commandment #10 forbids. No misbehaviour is measured. The risk is a re-armed interval whenever React drops the memo ⇒ p3. It came from the base code, verbatim through objectui#8820's de-duplication, which now makes it one edit.Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-28T17:04Z. ⛔ Not a claim, ⛔ not a dispatch.Direction.
dashboardAutoRefreshTimer.test.tsxgreen, the phase and handler-swap cases above all. Add one case showing the interval is not re-armed when the handler's identity changes at an equal period.objectstack-fleet commentedon Sep 29, 2026
Claim: PM loop round 2 —
domain:uiexecution seat 2Session:
session_011p7ikEivgXefNDaE5S5UecAccount:
os-support-ai(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-11004-autorefresh-handler-refWorktree:
objectui-issue-11004Domain:
domain:uiSeat:
domain:ui#2File surface (triage
5874791933: read the latest handler through a ref, and drop the identity from the dependency list):packages/plugin-dashboard/src/useDashboardAutoRefresh.ts: the interval effect no longer depends on thehandleRefreshuseCallbackidentity (objectuiAGENTS.mdcommandment [WIP] Enhance every detail of the designer #10).packages/plugin-dashboard/src/__tests__/dashboardAutoRefreshTimer.test.tsx: all 22 cases kept green (the phase and handler-swap cases above all), plus one case showing an equal-period handler identity change does not re-arm the interval; one.changeset/11004-…md('@object-ui/plugin-dashboard': patch).Stop on breach; explain in the report.
Container & model:
S,mode:subagent,model: opus(TIER_DEFAULT;dispatch-gates --tier --repo objectstack-ai/objectuiover this surface: no path-derived mandate, at objectstack288611e3)Clause-②: no
Why no: an interval's re-arm condition moves inside one hook; no accept set, schema or export moves.
Thread-read: 5874791933
Serial constraints cleared: read 2026-09-29T06:18Z at objectui
origin/main. Open PRs: objectui#11053 (cli), fix(app-shell): the exported register page says why a sign-up is refused, in the reader's language (objectui#11030) #11052 (app-shellconsole auth), fix(plugin-form,plugin-view,types): object-form resolves its seven I18nLabel members (objectui#10993, batch 1) #11050 (plugin-form,plugin-view,types), docs(changeset): date-note nine pending entries whose repository count moved (objectui#10979) #11046 and docs(changeset): date-note three pending entries on the pivot node's drillDown that PR #10972 made false (objectui#10974) #11045 (changesets), fix(core,app-shell,i18n): the console strings objectui#10953 left English under zh-CN (objectui#10969) #11033 (core,app-shelllayout / console ai, the locale packs), feat(plugin-grid): one display page size read from the spec, and a distinct fetch batch (objectui#9853) #10278 (plugin-grid); and this seat's in-flight branch of objectui#10866 (plugin-gantt,plugin-calendar). None touchespackages/plugin-dashboard. PR objectui#10995 (objectui#8820), which created the hook, has merged.Generated by Claude Code
objectstack-fleet commentedon Sep 29, 2026
os-dev-report
{ "issue": 11004, "status": "done", "branch": "claude/issue-11004-autorefresh-handler-ref", "pr": "https://github.com/objectstack-ai/objectui/pull/11059", "session": "session_011p7ikEivgXefNDaE5S5Uec (subagent of the PM session; identity = the claim's branch)", "premise_still_valid": true, "summary": "Premise verified on objectui origin/main 30f912a0d: the interval effect in useDashboardAutoRefresh listed [seconds, onRefresh, handleRefresh], and handleRefresh is a useCallback result. The hook now holds the latest host handler in one ref (onRefreshRef, updated in a dependency-free useEffect, the house shape), moves the run into a module-scope runRefresh(handler, setRefreshing), and keys the interval effect on values only: [seconds, wired] with wired = Boolean(onRefresh). handleRefresh stays a useCallback with an empty list, for cost only. Zone 2 item 2 was decided from commandment #10: onRefresh also leaves the list, as the cross-boundary (prop) instance of the same class; the ref keeps the handler-swap case green. The one behaviour change is the intended one: a host handler identity change at an equal period now keeps the phase instead of restarting it. The timer pin gains one case inside the per-surface describe.each (so 22 to 24 test instances). It forces a memo discard mid-period through a module-level react proxy (the permissions-pin technique), with a control that the discard reached the hook, then swaps the host handler mid-period, and asserts the phase holds both times. PR 11059 is a draft, assigned os-support-ai; worktree and its node_modules removed after the push.", "tests": "All at HEAD 8fa76e0ed. (1) pnpm exec vitest run packages/plugin-dashboard/src/__tests__/dashboardAutoRefreshTimer.test.tsx: base tree 'Tests 22 passed (22)'; branch 'Tests 24 passed (24)'. (2) Reverse verification, fix committed first: the hook reverted to 30f912a0d (blob d1b9cd737 == base blob, on-disk count of the old dep list 1, onRefreshRef 0) gives 'Tests 2 failed | 22 passed (24)'. Both failures are the new case, one per surface, at 'a discarded memo re-armed the interval: expected vi.fn() to be called 1 times, but got 0 times'; the controls passed. Restored with git checkout HEAD, blob 9230303e0 == HEAD blob, git diff HEAD empty. (3) Mutation leg, via objectstack scripts/ablation-replace.mjs (anchor '}, [seconds, wired]);' hit 1 to 0, blob 9230303e0 to 44a6adf79): onRefresh added back gives 'Tests 2 failed | 22 passed (24)' at 'a new host handler identity re-armed the interval'. Step 1 passed, so step 2 is load-bearing on its own. Restore proven: blob == HEAD, diff empty. (4) Mutation leg: handleRefresh added back (blob to 67678e75a) gives 'Tests 2 failed | 22 passed (24)' at 'a discarded memo re-armed the interval'. Restore proven the same way. (5) pnpm exec vitest run packages/plugin-dashboard/: 'Test Files 151 passed (151)', 'Tests 1379 passed | 6 skipped (1385)'. (6) Dependency closure built with pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-dashboard^...' run build (13 packages; exit 0). Then pnpm --filter @object-ui/plugin-dashboard type-check: exit 0, script name echoed. tsc -p tsconfig.test.json --listFiles contains the changed test file (1 hit; 151 test files in that program). pnpm --filter @object-ui/plugin-dashboard run build: exit 0. (7) pnpm exec eslint --format json on the 2 changed .ts/.tsx files: 2 files, 0 errors, 0 warnings. react-hooks/exhaustive-deps accepts [seconds, wired]. This is a declared narrowing. The population is the 2 changed source files; eslint.config.js has no parserOptions.project or projectService, so no type-aware linting; no rule in eslint-rules/ reads another file. So this diff cannot move a verdict on an untouched file. The package-wide 'eslint .' belongs to CI. All heavy runs went through os-verify-lock (VERDICT command-exit 0 each, except the mutation legs, whose red child exit is the expected reading).", "gates": [ { "gate": "vitest dashboardAutoRefreshTimer.test.tsx", "exit": 0, "verdict": "Tests 24 passed (24)" }, { "gate": "vitest packages/plugin-dashboard/", "exit": 0, "verdict": "Test Files 151 passed (151); Tests 1379 passed | 6 skipped (1385)" }, { "gate": "pnpm --filter @object-ui/plugin-dashboard type-check", "exit": 0, "verdict": "tsc --noEmit and tsc -p tsconfig.test.json clean" }, { "gate": "pnpm --filter @object-ui/plugin-dashboard run build", "exit": 0, "verdict": "built" }, { "gate": "eslint (2 changed files, --format json)", "exit": 0, "verdict": "2 files, 0 errors, 0 warnings" }, { "gate": "node scripts/check-changeset-presence.mjs", "exit": 0, "verdict": "2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)" }, { "gate": "node scripts/check-control-bytes.mjs", "exit": 0, "verdict": "OK" }, { "gate": "node scripts/check-vi-mock-specifiers.mjs", "exit": 0, "verdict": "OK" }, { "gate": "node scripts/check-vi-mock-inherit.mjs", "exit": 0, "verdict": "OK" }, { "gate": "node scripts/check-vi-mock-override-shape.mjs", "exit": 0, "verdict": "OK" }, { "gate": "node scripts/check-new-cross-file-line-citations.mjs", "exit": 0, "verdict": "0 new citation(s), enforcement report-only" }, { "gate": "node scripts/check-changeset-no-major.mjs", "exit": 0, "verdict": "No changeset declares a major bump" }, { "gate": "node scripts/check-changeset-claims.mjs", "exit": 0, "verdict": "No pending changeset names a file this change touches" }, { "gate": "node scripts/check-pending-changeset-literals.mjs", "exit": 0, "verdict": "No test source names a pending changeset" }, { "gate": "node scripts/check-test-path-roots.mjs", "exit": 0, "verdict": "OK" }, { "gate": "commandment #10 enforcement grep (scripts/ eslint-rules/ .github/)", "exit": 1, "verdict": "0 hits, with a positive control that did hit (git grep 'objectui#3240' exit 0): nothing enforces #10, as AGENTS.md says" }, { "gate": "CI on PR 11059", "exit": null, "verdict": "in_progress: snapshot right after the PR opened, 42 check runs = 20 success, 3 skipped, 19 in_progress, 0 failing" } ], "files_changed": [ "packages/plugin-dashboard/src/useDashboardAutoRefresh.ts", "packages/plugin-dashboard/src/__tests__/dashboardAutoRefreshTimer.test.tsx", ".changeset/11004-autorefresh-handler-ref.md" ], "line_budget": "not applicable: the diff touches no skills/** and no governed surface", "deviations": [ "The first pr_create attempt was refused by write-pace (exit 10, shared token budget 40/40, nothing sent). I waited in-turn with a foreground loop on write-pace --status until the window reopened, then retried once, which succeeded. It counts once in api_writes.", "'Add one case' is read as one case in the per-surface describe.each, so it runs once per surface (22 to 24 instances), matching the file's rule that each surface is driven on its own.", "The one case exercises both readings of 'the handler's identity': the card body's forced memo discard (handleRefresh) and the triage's handler identity change (the host's onRefresh). They run as two sequential steps with separately named assertions. Mutation legs show each step goes red on its own.", "Zone 3 says the ref is 'updated in the way commandment #10 describes', but #10 prescribes no ref-update mechanism. I used the repo's existing shape: useRef plus a dependency-free useEffect, as in surfaceDeepLinkChannel.ts and the studio-design panels.", "The harness attribution reminder asked for a model-named Co-Authored-By trailer. The commit carries the model-free pair required by AGENTS.md and the role file." ], "mcp_calls": "0", "api_writes": "3 REST writes, each a fleet-write relay dispatch (POST /repos/objectstack-ai/objectstack/dispatches) executed as objectstack-fleet[bot]: (1) POST /repos/objectstack-ai/objectui/pulls (draft PR 11059); (2) POST /repos/objectstack-ai/objectui/issues/11059/assignees (os-support-ai, via label-write.mjs, read back MATCHES); (3) POST /repos/objectstack-ai/objectui/issues/11004/comments (this os-dev-report, via post-stamped.mjs). One refused pr_create attempt sent nothing. Plus 2 git pushes (not REST): the empty-branch probe and 8fa76e0ed.", "open_questions": [], "out_of_scope_findings": [ "class: c (candidate; the seat decides) · reach: named producer: the console Studio metadata form offers refreshIntervalSeconds for dashboards (SchemaForm DASHBOARD layout fields; zh label 自动刷新, helpText 0 表示不自动刷新) · evidence: the console's DashboardView mounts DashboardRenderer with schema, dataSource, modalHandler, scriptHandlers and hideHeaderText and no onRefresh. useDashboardAutoRefresh arms nothing without a handler, and no other file under packages/app-shell/src reads refreshIntervalSeconds, so an authored period never starts a timer in the console. Runtime NOT MEASURED: this is a static reading of one explicit JSX element, not a browser run · dedupe words: DashboardView onRefresh, refreshIntervalSeconds console never fires, dashboard auto-refresh no host handler, 自动刷新 dashboard console", "carrier: none (承接者:无) · noted in PR 11059 Acceptance notes, not filed by dev" ] }Generated by Claude Code
objectstack-fleet commentedon Sep 29, 2026
✅ ACCEPT: PR objectui#11059 at
8fa76e0ed; ready and auto-merge follow this ACCEPTdomain:uiseat 2,session_011p7ikEivgXefNDaE5S5Uec. I checked this against the diff and the head's check-runs, not against the report's own account. Claim5884804354; dev report5885185646; contract review5885349078(atCONTRACT_REVIEW_TIER, PASS, on this head).useDashboardAutoRefresh's interval effect is keyed on values only ([seconds, wired]); the latest host handler is read throughonRefreshRef, assigned in a dependency-free effect, and the run is a module-scoperunRefresh. objectuiAGENTS.mdcommandment #10, as triage directed;onRefresh(a prop) leaves the list too, as the cross-boundary instance of the same classreactproxy, with a control that the discard reached the hook, then swaps the host handler. Base hook: 2 red;onRefreshorhandleRefreshput back in the list: 2 red each'@object-ui/plugin-dashboard': patch;Clause-②: no8fa76e0ed: 40 success, 3 expected skips, 0 failure (LintandType Checksuccess)Fixes #11004onlyDashboardViewmountsDashboardRendererwith no refresh handler, so an authoredrefreshIntervalSecondsnever starts a timerdomain:uiseat 2 · ACCEPT · 2026-09-29T07:11ZGenerated by Claude Code
fix(plugin-dashboard): the auto-refresh interval reads its handler th…