Repository navigation
Description
eval#1 · os-bob · session_012dn2AcTB69pEwZ9ugr1g1w
Filing gate: ② a decision only the maintainer can make.
维护者速读
- 现象:通过 MCP 修改元数据(落成草稿)后,已经打开的 Studio 页面上「变更」角标和草稿标记都不变,看起来像"没改成";必须手动刷新浏览器才能看到。反过来,在 Studio 发布后回到 MCP 也无从得知。只有打开「变更」抽屉那一刻会重读列表。
- 要你裁:Studio 是否要对"本标签页之外"的元数据变更自动刷新,以及走哪种形状(前端轮询/聚焦重取,还是服务端推送)。
- 推荐:B → C 分期 — 先在
usePendingDrafts加"窗口重新获得焦点时重取 + 页面可见时低频轮询",一个 PR 解决今天的困惑;再把草稿变更接入已有的 realtime 通道,作为终态。
One-sentence problem
When an agent changes the app over MCP, the Studio page the user already has open keeps showing the old state — no pending-change badge, no draft marks — until the user thinks to reload the browser, so a change that worked looks like a change that failed.
Evidence (reach)
Session on the os-dev-test environment (framework c110853e): metadata drafts staged by MCP authoring tools (apply_edit, update_metadata, create_seed) did not appear in an open Studio tab's Changes badge until a manual browser reload; the user reported it as "Studio 页面上看不到是否已变更".
Code reading (all re-checkable):
ui/packages/app-shell/src/preview/usePendingDrafts.ts— the single source of the pending-drafts count (GET /api/v1/meta/_drafts). It refetches on mount, scope change and the in-tabsubscribeMetadataRefreshpulse (:248) only; no polling, no focus/visibility refetch. Re-check:grep -n "setInterval\|visibilitychange\|subscribeMetadataRefresh" ui/packages/app-shell/src/preview/usePendingDrafts.ts.ui/packages/app-shell/src/assistant/assistantBus.ts:156-169— the refresh bus is an in-memory per-tab listener set; it is pulsed by Studio's own saves/publishes and by the in-console AI chat (console/ai/AiChatPage.tsx~:2425), which is why Studio refreshes for its own copilot but never for an external MCP client.ui/packages/app-shell/src/preview/DraftChangesPanel.tsx~:1629 — the Changes sheet re-reads its list each time it opens; the badge and per-item draft marks do not.- Server: a draft save emits
emitMetadataMutation({ state: 'draft' })in-process only; the cluster fan-out returns early for drafts (packages/metadata-protocol/src/protocol-mutation-fan-out.ts:115,protocol-class-head.ts:375), andMetadataManager.publishRealtimeMetadataEventfires only fromregister()/delete, never for drafts. Re-check:grep -n "state === 'draft') return" packages/metadata-protocol/src/*.ts. - The only browser push for metadata is the dev-only HMR SSE (
GET /api/v1/dev/metadata-events,NODE_ENV=development; clientui/apps/console/src/components/MetadataHmrReloader.tsx,import.meta.env.DEV, fulllocation.reload()). - The QA checklist already records the gap as expected:
docs/qa/platform-checklist/areas/studio-authoring.jsonitemstudio-authoring.draft-publish-lifecycle— "A publish made outside Studio (the REST publish …) shows in Studio only after a reload". Re-check:grep -n "only after a reload" docs/qa/platform-checklist/areas/studio-authoring.json.
Governing text
docs/NORTH-STAR.mdline 7 — "Two kinds of user: today, business staff through the UI …; tomorrow, agents through MCP. Every step of the road must hold for both kinds of user." An agent's change that the UI user cannot see breaks the hand-off between the two.docs/adr/0008-metadata-repository-and-change-log.md(~:140-170) — describes a change log withseqandwatch()/subscribe(since=seq)with an SSE/WS broadcast; only the dev HMR stream was built.docs/adr/0037-live-canvas-draft-preview.mdPhase 2 (~:68-73) — live refresh viaassistantBusfor the in-console chat only; external clients are out of its scope.- Protocol change? No spec change for A/B; C adds draft events to the realtime channel (a server contract), which needs an ADR-0008 amendment.
Options
| Option | What it does | What the customer sees | Effort |
|---|---|---|---|
| A | Keep today's behaviour; document "reload to see changes made outside Studio" | Agent says "改好了", Studio shows nothing until reload | none (doc line) |
| B | Client-side: usePendingDrafts (and the package metadata refresh) also refetch on window focus / visibilitychange, plus a low-frequency poll (e.g. 15–30 s) while the tab is visible |
Switching back to the Studio tab shows the agent's changes within a moment; an idle visible tab catches up within the poll interval | ui seat · 1 PR · no measured precedent; small (one hook + its consumers' pulse) |
| C | Server push: forward draft (and publish) metadata mutations to the existing service-realtime channel (metadata.<type>.drafted/published), and have the console subscribe (RealtimeAPI.subscribeMetadata exists in the client SDK, unused) and pulse emitMetadataRefresh |
Changes appear live, in every open tab, including from MCP/REST/other users | server + ui seats · 2–3 PRs · ADR-0008 amendment · no measured precedent |
Business reading: A = "refresh the page" stays a user chore; B = the page catches up when you look at it; C = the page is live, like a shared document.
Recommendation
C as the end state, delivered B first. Two years out Studio and an agent edit the same app concurrently, and every mainstream collaborative builder shows other writers' changes live (Figma, Notion, Retool multiplayer; Salesforce setup at least warns on stale metadata). ADR-0008 already describes the change-log subscription this needs; only the dev stream exists. B is the timing step, not a different letter: it fixes today's confusion in one PR and stays useful as the fallback when a push connection drops. Not A: it leaves the agent/UI hand-off broken, which is exactly the North Star's "both kinds of user" road.
Confidence gap: the realtime service's availability and fan-out cost per tenant for draft-level events are unmeasured; C should be scoped to the package the tab is showing.
Self-check: only ① picks C (B as its first phase); ②③④ do not flip it — ② and ④ only set the B-first timing.
After the ruling
Phase 1 (B): usePendingDrafts refetches on focus/visibility and polls while visible; Studio's item draft marks and the package metadata resolver listen to the same pulse; drop the "only after a reload" clause from studio-authoring.draft-publish-lifecycle. e2e: stage a draft via REST/MCP, switch back to an open Studio tab, the badge updates without reload.
Phase 2 (C): draft/publish mutations published on service-realtime; the console subscribes once per tab and pulses emitMetadataRefresh; ADR-0008 amended.
os-decision-facets
① 项目长远合理性: C 落地 ADR-0008 已描述的变更订阅,让 UI 与 agent 共编同一应用时状态一致;B 是其分期第一步与断线兜底,A 让两类用户之间的交接长期断开。
② 实际业务拉动: 今天每个用 MCP 搭应用、同时开着 Studio 的用户都会撞上(本次会话实测),且 QA 清单已把"外部发布需刷新"记成预期行为。
③ 防 AI 犯错: 看不到变更时,用户会以为 AI 没改成而让它重做,导致重复/冲突修改;B/C 让结果可见,错误更容易被及时发现。
④ 创业阶段不扩散: B 只改一个 hook、无新契约;C 新增 realtime 事件需维护,故按 B → C 分期。
Prior rulings read: "Studio drafts reload / Studio changes refresh" → 12 hits (#32440, #25943, #29672 — none rule on cross-tab/external refresh); ADR-0008, ADR-0037; thread: none
Reader who acts: the ui/ app-shell seat (phase 1) and the metadata/realtime server seat (phase 2), after the ruling.
Dedupe: repo:objectstack-ai/objectstack is:issue Studio changes refresh → 6 hits, Studio drafts reload → 6 hits, none on external-change refresh.
Generated by Claude Code
Activity
objectstack-fleet commentedon Nov 12, 2026
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
Path: none
分诊首触 + 决策分析(fable 档独立评估)· 2026-11-12T16:53Z
维护者速读
hiddenTabPollStop.ts)。是否已有裁决: 未找到(ADR-0008 描述了 subscribe,但只建了开发用的流)。
前提核实:
ui/packages/app-shell/src/preview/usePendingDrafts.ts:248只订阅本标签页内的刷新。studio-authoring.json:511把「外部改动需刷新才可见」记成了预期行为。hiddenTabPollStop.ts四轴:
hiddenTabPollStop(轮询会让缩容到零的后端一直醒着);C 新增事件契约,暂缓。推荐 B。 置信缺口:轮询间隔对缩容计费的影响没有量。自检: 只看①选 C(B 是第一步);②③④翻转为只做 B。人工底线项:仅 C。
os-decision-facets
Prior rulings read: ADR-0008 (subscribe, dev stream only); thread: none
Generated by Claude Code
objectstack-fleet commentedon Nov 13, 2026
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
Brief-claimed-by: session_01Q4EjjMnyLqJdzuktEmRPXH · batch #1 · 2026-11-13T04:20Z
Decision batch #1 (the MCP agent-capability family, under epic #32897), taken on the maintainer's 「5张一批按总监决裁格式给我」.
objectstack-fleet commentedon Nov 13, 2026
triage#1 · hotlong · session_01Q4EjjMnyLqJdzuktEmRPXH
Ruling recorded · #32592 · C in two phases; phase 1 = B now · 2026-11-13T04:40Z
Maintainer, verbatim (triage#1 session): 「以上原则同意」, answering decision batch #1 (
32641 B / 32591 A / 32592 C / 32686 A / 32747 A). The batch was analysed independently at the fable tier, with prior rulings checked first; the full analysis was presented in the session.domain:ui, one PR): refetch when the Studio tab becomes visible again, plus a low-frequency poll while visible, under the existinghiddenTabPollStoprule. The item draft marks and the package metadata resolver listen to the same pulse. Remove the platform-checklist line that expects external changes only after a reload (studio-authoring.json). Acceptance: stage a draft via REST or MCP, switch back to an open Studio, and the badge updates without a reload.pm:blockedcard fordomain:spec): draft and publish events on the live change channel (ADR-0008). It does not start until three things are done: the scale-to-zero cost of persistent connections is measured, a rule says who may receive another user's draft events, and ADR-0008 is amended.needs-user-decision→pm:queue.Generated by Claude Code
objectstack-fleet commentedon Nov 13, 2026
domain:ui#4 · os-tesla · session_01L9qibvZYR36KAJUv44oXZw
Claim: PM loop round 6 · 2026-11-13T22:31Z
Session:
session_01L9qibvZYR36KAJUv44oXZw(seat); card threadsession_01L9qibwsmJho8tS1SLKTiPais the devAccount:
os-teslaBranch:
claude/project-thread-wv46ii(the card thread's designated branch; the platform lets a thread push only that branch, so notclaude/issue-32592-*)Worktree:
objectstack-issue-32592Domain:
domain:uiSeat:
domain:ui#4File surface:
ui/packages/app-shell/src/preview/usePendingDrafts.ts,preview/draftStatus.ts,views/studio-design/StudioDesignSurface.tsx(the draft-marks pulse only), the package metadata resolver behind Studio's item draft marks,hooks/hiddenTabPollStop.ts(reuse), their tests, an e2e spec if added;docs/qa/platform-checklist/areas/studio-authoring.json(one clause + revision entry); root.changeset/. ⛔ No server change, nopackages/specchange (stop on breach; explain in the report)Container & model: M,
mode:thread,model: default tier,effort: high(a polling seam under the hidden-tab rule; fleet token rule 2)Clause-②: no
Metadata-protocol: none
Responsibility:
usePendingDraftsrefetches only on the in-tab pulse, so a change staged outside the tab shows only after a reload (objectstackui/Studio) | maintainer ruling 6844196863: C in two phases, phase 1 = B now (phase 2 is #33023) | every user with Studio open while an agent edits over MCP or RESTThread-read: 6844196863
Premise: holds at
origin/main84c66d36:usePendingDrafts.ts:248subscribes only tosubscribeMetadataRefresh; no poll or visibility refetch;studio-authoring.json:511records 'shows in Studio only after a reload'Serial constraints cleared: none of the 24 open PRs touches this surface (seat read 2026-11-13 ~22:30Z). Sibling seat cards in flight: #33098, #33073, #33052, #32592, #32904, with disjoint surfaces; #33052 may edit another region of
StudioDesignSurface.tsx(allowed; the second to land mergesmainonce).[-][Decision] Should Studio show changes staged or published from outside the browser tab (MCP, REST, another tab) without a manual reload — and by polling or by a server push?[/-][+]studio: refetch drafts on focus and poll while the tab is visible, so changes made outside the tab show without a reload — ruled C, phase 1 (phase 2 is #33023)[/+]objectstack-fleet commentedon Nov 13, 2026
domain:ui#4 · os-tesla · session_01L9qibwsmJho8tS1SLKTiPa
os-dev-report
{ "issue": 32592, "status": "done", "branch": "claude/project-thread-wv46ii", "pr": "https://github.com/objectstack-ai/objectstack/pull/33165", "session": "session_01L9qibwsmJho8tS1SLKTiPa", "premise_still_valid": true, "summary": "Phase 1 (B) of ruling 6844196863. usePendingDrafts keeps a ref-counted set of the _drafts scopes its mounted hooks read and checks them on visibilitychange-to-visible, on window focus, and every PENDING_DRAFTS_POLL_MS = 30 s while visible (top of the ruled 15-30 s range: the return read is immediate, the timer only covers a side-by-side visible Studio). A check reads each scope once through the existing in-flight share; when the answer's fingerprint differs from the one this tab last read it sends the tab's own emitMetadataRefresh, so the badge, the item header line, the pillars' re-reads that carry the draft marks, the package app resolver and MetadataProvider converge on one pulse; an unchanged answer sends nothing. A hidden tab clears the timer and sends no request (stricter than hiddenTabPollStop, which is consulted at arm and fire). The checklist clause expecting an outside publish to show only after a reload is replaced, revision 13. No StudioDesignSurface.tsx, server or spec change.", "tests": "head 36f74ea0 (code identical to 922b9a62, where the suites ran). Under os-verify-lock from ui/: new pins + usePendingDrafts afterSave/inflightShare + itemDraftLine harness: Test Files 4 passed (4), Tests 31 passed (31). All 244 ui test files importing the changed surface: 66 visibility/timer/ledger-sensitive files Test Files 66 passed (66), Tests 399 passed (399); remaining 178 files Test Files 178 passed (178), Tests 1251 passed (1251). app-shell type-check (both programs) VERDICT command-exit 0. Ablation at 922b9a62 via scripts/ablation-replace.mjs: anchor document.addEventListener('visibilitychange', onWatchVisibility) replaced, anchor x1 to x0, blob afced872 to d8403f21; Test Files 2 failed (2), Tests 4 failed | 18 passed (22) (return, unchanged-answer, hidden-then-return, Studio item-line pins red; focus and poll pins green as expected); restore blob == HEAD afced872, git diff HEAD empty. No dist build involved (app-shell tests import source).", "gates": "dispatch-gates --commands derived 43; all run with stdin /dev/null, exit captured before any pipe; --ran: 43 derived accounted for, 43 run, 0 NOT-MEASURED (a DERIVED zero). check:platform-checklist first exit 1 (short anchor path), fixed, exit 0; check:type-check-debt first exit 124 (300 s wrapper timeout queued on the lock), re-run exit 0. Derivation byte-identical after the main merge; on 36f74ea0 re-ran exit 0: check:platform-checklist, check-sdui-manifest, check:nul-bytes, check:doc-authoring, check-empty-changeset, check-changeset-no-major, check:test-source-alias, check:cross-package-test-inputs. ui check:new-line-citations: 0 new. CI families: read from PR 33165 check-runs (in_progress at report time).", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "2 — mcp__github__issue_read (get, get_comments), reads only; 0 write tools", "api_writes": "2 — fleet-write relay POST /repos/objectstack-ai/objectstack/dispatches (pr_create, run 43526531817, PR 33165 with assignee os-tesla); post-stamped comment on issue 32592 (this report). Plus git push (not REST). One REST read of the claim comment.", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "carrier: none · e2e not added: a Playwright page cannot background itself as a tab does, so the browser run would fake document.hidden as the jsdom pins already do; noted in Acceptance notes", "carrier: none · dispatch order put the verification record before Acceptance notes, os-dev.md orders What changes/Tests/Acceptance notes/Verification; the body follows os-dev.md with Tests (the verification record) ahead of Acceptance notes" ] }