You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
studio(changes): the Changes panel shows no diff, marks a no-op draft as Update, and publish gives no confirmation #11807
Filing gate ② — a product decision only the maintainer can make.
Who acts on it: objectui triage → the Studio / app-shell owner. ⛔ Not a claim. Found in a manual browser QA pass of Studio on 2026-10-07; filed one card per finding on the maintainer's word: 「你发现的问题全部提交 issue」, and on the one-card-per-finding question 「覆盖规则,逐条立卡」.
Maintainer quick-read
The Changes panel lists only machine names with New/Update, and publishing gives no confirmation. Show what changed?
Seen
Changes: OBJECT · 1 repairs_repair_ticket Update — no field-level diff, label, or author.
A draft identical to the live version is listed as Update.
Publish all takes ~5 s and shows no success message; only the header text changes to "No drafts pending publish". The Form tab keeps the caption "Draft layout" after publish.
Options
A — per-item diff (added/removed/changed keys), hide no-op drafts, a "Published N items" toast.
B — toast + hide no-ops only.
C — status quo.
Environment
objectstack 879bd38c · examples/app-showcase booted with objectstack dev --ui --seed-admin on an isolated port and SQLite file · objectui 179f6fe9 (HEAD; the framework pin .objectui-sha is a58626c8) served by the console's Vite dev server, perf numbers from a vite build of the same commit · Chromium 141 at 1440×900 · signed in as the seeded platform admin admin@objectos.ai unless stated.
Path: write metadata — the author sees what publishing will change | 缺项 | P3
Triage: first grade, enhancement · priority:p3 · domain:ui · area:studio · pm:queue. Filed as ②, but routed as a Studio UX improvement: no contract or product question is open. Direction: the body's option A
Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-07T17:30Z. ⛔ Not a claim, ⛔ not a dispatch.
Triage: lands in packages/app-shell/src/preview/DraftChangesPanel.tsx and draftStatus.ts ⇒ domain:ui; rationale: a Studio or console surface in objectui. Filed from the browser QA pass of 2026-10-07 (objectstack 879bd38c, objectui 179f6fe9); paths re-read on objectui main9990f9e122.
Why not the decision box: a diff, hiding no-op drafts and a toast change nothing a server stores. Why p3: publishing works; the author cannot see what it will do.
Direction:
a per-item diff of added, removed and changed keys (reuse LayeredDiff.tsx where it fits)
a draft equal to the published item is not listed as Update
the new / update classification (kind, about :92–:150): a draft equal to its published item is not listed as Update;
the per-item drill-in (computeChangeDetail, about :216–:300): it shows added, removed and changed keys. LayeredDiff.tsx is reused by import where it fits, never edited.
packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx: only doPublish's success path (about :1271–:1365), for a toast that names how many items were published. The dev first measures whether the existing engine.studio.publishedAll toast (about :1352) already answers the card.
packages/app-shell/src/preview/usePublishAllDrafts.ts: only its success toast, if Home and the preview bar need the same count.
packages/app-shell/src/views/metadata-admin/i18n.ts: only new or reworded engine.studio.changes.* / engine.studio.publishedAll* rows (en and zh).
The tests beside these, and .changeset/11807-*.md.
⛔ Not on it:
LayeredDiff.tsx and any draft-existence probe: seat 1's objectui#11799 holds the /layers call and the ?state=draft existence probes. fetchItemBody's draft read (about :180–:190) reads a body for the diff and is left as it is.
StudioDesignSurface.tsx outside doPublish. Seat 3's objectui#11823 holds InterfacesPillar (from about :2492), which is disjoint.
packages/components/src/ui/** and packages/i18n/**.
Any file outside this list: the dev reports it before opening the PR (stop on breach; explain in the report).
Container & model: M, mode:subagent, model: opus (dispatch-gates --tier --repo objectstack-ai/objectui over these paths: no path-derived mandate; default tier)
Clause-②: no
Responsibility: objectui app-shell Studio Changes panel: drafts are listed by machine name with New/Update only, a draft identical to the published item is listed as Update, and Publish all gives no count of what it published | the panel already fetches the published and draft bodies for its drill-in, so the comparison is local | every Studio author reviewing a publish, seen by the filer's 2026-10-07 browser QA pass on the showcase (OBJECT · repairs_repair_ticket Update with no change)
Thread-read: 6043265917
Serial constraints cleared: overlaps declared, not blocking.
objectui#11799 (seat 1, in flight): its surface is the /layers call in LayeredDiff.tsx and the draft-existence probes. This card imports LayeredDiff without editing it, and leaves fetchItemBody's request unchanged.
objectui#11823 (seat 3, in flight):StudioDesignSurface.tsx, InterfacesPillar only. This card's one hunk sits in doPublish, about 1,100 lines away.
objectui#11787 (queued, unclaimed): its "Changes count refreshes on every save" item is the header count, not this panel. Left to that card.
In-flight claims read 2026-10-08T05:18Z: none names DraftChangesPanel.tsx, draftStatus.ts or usePublishAllDrafts.ts.
Open objectui PRs, read 2026-10-08T05:18Z: none touches these files.
Why Clause-②: no: this is Studio review UX. Nothing a server stores changes, and no export, prop or packages/i18n key is added. New strings live in app-shell's own metadata-admin/i18n.ts defaults table.
os-dev-report
{
"issue": 11807,
"status": "done",
"branch": "claude/issue-11807-changes-panel-diff",
"pr": "#11916",
"session": "session_01MgfduSkFrfM3eorB3UGfAU (subagent mode: the parent's id, per claim 6052893161)",
"premise_still_valid": true,
"summary": "H0, measured on b6f8fc8 by rendering the real DraftChangesPanel and the real StudioDesignSurface (real sheet, real MetadataClient, mocked reads). Obs 1 was partly stale. The field-level drill-in has existed since 7283d18 (PR objectui#2271), but every other top-level key was one list: 'Also changed: description, icon, label'. Obs 2 reproduced: the row kept Update even after its drill-in printed 'No differences detected'. Obs 3 was stale: the success toast fires (since 20c1695, PR objectui#2203) but names no number. What changed: (1) computeChangeDetail returns keys {added, changed, removed}, and the drill-in prints them with the field rows' +/~/- marks. (2) Option (a): once an entry's drill-in finds both bodies present and equal (same stripReadDecorations normalisation), the row drops the Update badge and shows an Equal glyph whose accessible name is the existing preview.changes.detailNone sentence. A badge word would need a packages/i18n key, which is outside the surface. H1: deciding no-op at open costs one published body read per draft plus one draft body read per non-object draft, so it is O(drafts). (b) is falsified: a _drafts row carries only type, name, organizationId, packageId, updatedAt and updatedBy. The list read cannot stand in, because it is projected (doc content slim, object mask). (3) doPublish names N from the batch's published[] (declared by PublishPackageDraftsResponseSchema, returned by the client), with singular and plural rows; the package-less scope does the same. Zero answers now show info 'No drafts pending publish': outcome nothing_to_publish (was thrown as 'Action failed') and a package-less read with nothing pending (was the success toast). (4) A refused batch now names each refused item with its reason. Measured before, with the producer's envelope: toast.error 'Action failed'. Since e16f504 (objectui#10039) doPublish read the batch's own success === false before failed[]. This PR rewrites that same chain, so the shipped defect in touched lines is repaired here. Route deviations: no-op drafts stay in the counts, because the server still promotes them and they appear in published[], so the panel's counts agree with the toast's N. LayeredDiff's computeDiffRows is not imported, because that module imports the 456 KB metadata-admin i18n table and this panel is on the console's eager graph.",
"tests": "All at HEAD 358e461 (git rev-parse --short HEAD after the last commit, equal to the remote tip). New tests: DraftChangesPanel.noChangeAndKeySplit-11807.test.tsx (7) and StudioDesignSurface.publishCount-11807.test.tsx (6). Updated: DraftChangesPanel.test.tsx (keys shape). Union run (preview/tests/, 4 Studio publish suites, column-identity.ratchet): 'Test Files 17 passed (17) / Tests 142 passed (142)'. Every test file naming StudioDesignSurface, DraftChangesPanel, usePublishAllDrafts or DraftPreviewBar (101 files; one scripts/ text-only match left out): 'Test Files 101 passed (101) / Tests 603 passed (603)'. pnpm --filter @object-ui/app-shell type-check: exit 0, after the app-shell^... build; tsconfig.test.json includes src/**/.test.tsx. Reverse verification, each leg on committed HEAD with restore proven by blob hash and an empty git diff HEAD. A1: the 3 source files at b6f8fc8, tests at HEAD, gave 'Tests 15 failed | 11 passed (26)', with the NEW/gone guards, the route tests and the panel controls green. A2: ablation-replace dropped the both-bodies guard, gave 'Tests 2 failed | 5 passed (7)' (exactly the two guard pins), anchor 1 to 0, blob c0164c9bc8cc to 35283355b155, restored. A3: the first attempt was a no-op; ablation-replace refused it because the replacement contained the anchor (1 to 1). It was re-run with a dropping anchor that sends a refusal through the success-false throw: 'Tests 1 failed | 5 passed (6)' (only the refusal pin), blob cf06c12f6e45 to c8f0dd0e9c84, restored. No dist preflight: the tests import from source.",
"mcp_calls": "0",
"api_writes": "3 REST writes, each one relay stroke (POST /repos/objectstack-ai/objectstack/dispatches, 204) executed as objectstack-fleet[bot]: (1) POST /repos/objectstack-ai/objectui/pulls, draft PR #11916, read back byte-identical (10108 bytes); (2) POST /repos//issues/11916/assignees, os-bill, via label-write.mjs, read back matches; (3) POST /repos//issues/11807/comments, this os-dev-report, via post-stamped.mjs. Plus git pushes of the branch (the empty-branch probe and 7 commits), which are not REST.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: named producer: objectstack metadata-protocol publishPackageDrafts returns { success: false, outcome: 'refused', failed: [...] } with no error key, and the runtime dispatcher wraps it as { success: true, data } · evidence: PackagesPage's publishDrafts (r.success === false throws readEnvelopeFailureText(r) || 'Action failed' before it reads failedCount) and usePublishAllDrafts (success === false throws 'publish-drafts did not publish this package') both read the batch's own success before failed[]. MetadataClient.publishPackageDrafts unwraps the envelope (since objectui#6965 / PR objectui#10038), so a refused package publish from the Packages page or the Home / draft-preview bar loses the per-item reason. The Studio twin of this ordering was measured on the producer shape (toast.error 'Action failed') and repaired in PR #11916; these two sites are not on this card's surface · dedupe words: publish refused reason Action failed · publish-drafts failed[] success false · MetadataClient unwrap envelope refusal",
"carrier: 承接者:无 · noted, not filed (no count is a missing nicety, not a defect) · usePublishAllDrafts (Home banner, draft-preview bar) still toasts 'Published! Your changes are live.' without a count; a count there needs a home.pendingDrafts. key in packages/i18n · dedupe words: home publish all toast count",
"carrier: 承接者:无 · noted, not filed · a visible 'No change' badge word on the Changes panel row needs a preview.changes.* key in packages/i18n; this PR uses a named glyph instead · dedupe words: changes panel no change badge"
],
"gates": [
{
"command": "pnpm exec vitest run packages/app-shell/src/preview/tests/ + StudioDesignSurface.{publishCount-11807,publishAdvisoryReachesAuthor,heldPublish-11786,packageLessFlows-11553}.test.tsx + packages/core/src/utils/tests/column-identity.ratchet.test.ts",
"exit": 0,
"verdict": "Test Files 17 passed (17) / Tests 142 passed (142)",
"head": "358e4619a"
},
{
"command": "pnpm exec vitest run (101 test files naming StudioDesignSurface / DraftChangesPanel / usePublishAllDrafts / DraftPreviewBar)",
"exit": 0,
"verdict": "Test Files 101 passed (101) / Tests 603 passed (603)",
"head": "358e4619a"
},
{
"command": "pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build && pnpm --filter @object-ui/app-shell type-check",
"exit": 0,
"verdict": "os-verify-lock VERDICT command-exit 0; type-check (tsc --noEmit && tsc -p tsconfig.test.json) exit 0",
"head": "358e4619a"
},
{
"command": "pnpm exec eslint (6 touched source and test files)",
"exit": 0,
"verdict": "0 errors, 15 warnings, none on a changed line (DraftChangesPanel: existing computeChangeDetail export and existing load effect; none inside doPublish)",
"head": "358e4619a"
},
{
"command": "node scripts/check-changeset-presence.mjs",
"exit": 0,
"verdict": "6 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/11807-changes-panel-diff.md.",
"head": "358e4619a"
},
{
"command": "node scripts/check-changeset-no-major.mjs",
"exit": 0,
"verdict": "No changeset declares a major bump.",
"head": "358e4619a"
},
{
"command": "pnpm check:new-line-citations",
"exit": 0,
"verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0",
"head": "358e4619a"
},
{
"command": "pnpm check:control-bytes",
"exit": 0,
"verdict": "check-control-bytes: OK (scanned 7924 tracked text file(s); skipped 85 binary).",
"head": "358e4619a"
},
{
"command": "pnpm check:changeset-claims",
"exit": 0,
"verdict": "No pending changeset names a file this change touches.",
"head": "358e4619a"
},
{
"command": "pnpm check:i18n-keys",
"exit": 0,
"verdict": "Every in-scope call-site key resolves against the en pack (3300 keys) ...",
"head": "358e4619a"
},
{
"command": "pnpm check:i18n-drift",
"exit": 0,
"verdict": "No en value changed in this range. No designer-table en value changed in this range. (designer table: 4 key(s) added, 1 removed)",
"head": "358e4619a"
},
{
"command": "pnpm check:i18n-designer-parity",
"exit": 0,
"verdict": "Every en row has a zh row, and every shared row carries the same placeholders.",
"head": "358e4619a"
},
{
"command": "pnpm check:spec-symbols / check:vi-mock-specifiers / check:vi-mock-inherit / check:vi-mock-override-shape",
"exit": 0,
"verdict": "all OK",
"head": "358e4619a"
},
{
"command": "pnpm lint (repo-wide eslint) and the full pnpm test",
"exit": null,
"verdict": "NOT MEASURED: CI-owned runs; locally only the touched files were linted, and that narrowing is not claimed as a repo-wide reading",
"head": "358e4619a"
}
],
"files_changed": [
".changeset/11807-changes-panel-diff.md",
"packages/app-shell/src/preview/DraftChangesPanel.tsx",
"packages/app-shell/src/preview/tests/DraftChangesPanel.noChangeAndKeySplit-11807.test.tsx",
"packages/app-shell/src/preview/tests/DraftChangesPanel.test.tsx",
"packages/app-shell/src/views/metadata-admin/i18n.ts",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.publishCount-11807.test.tsx",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx"
],
"deviations": [
"i18n rows: besides the new engine.studio.publishedAllCount(/One) and engine.studio.publishedAllFlows(/One) rows (en+zh), the now-unreferenced engine.studio.org.published row (en+zh) is removed. It is outside the claim's named engine.studio.publishedAll* / engine.studio.changes.* prefixes. No engine.studio.changes.* row was needed: the panel's strings live in packages/i18n (preview.changes.*), and the panel cannot import the 456 KB metadata-admin table without putting it on the eager graph.",
"Route (a), with 'leave it out of the count' not followed: publish still promotes a no-op draft (it is in published[]), so the panel's counts stay 'drafts released' and agree with the toast's N.",
"H2 'otherwise the panel's listed count' fallback not used: published[] is present on today's producer, and a response without it falls back to the count-less existing sentence.",
"doPublish's refused-batch ordering (failed[] read before success === false) is repaired in this PR because the restructure touches those lines. It is outside the card's three observations and is declared in the PR body and the changeset.",
"LayeredDiff.tsx is reused by neither import nor edit: the import would carry the metadata-admin i18n table onto the console's eager graph (reasoned from the import graph; eager-closure bytes NOT MEASURED).",
"usePublishAllDrafts.ts is untouched: a count in its toast needs a packages/i18n key (out of surface).",
"Commit trailers follow AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By."
]
}
Implemented-by: os-dev subagent of session_01MgfduSkFrfM3eorB3UGfAU, branch claude/issue-11807-changes-panel-diff, head 358e461.
Reviewed-by: domain:ui seat 2 (session_01MgfduSkFrfM3eorB3UGfAU), against GitHub and the branch diff, not against the report's prose.
Checklist:
PR shape: draft, base main, first line Fixes #11807. No closing keyword sits next to another card. Clause-②: no is at the start of a line.
Scope: 7 files (+713 −46).
DraftChangesPanel.tsx with its tests.
StudioDesignSurface.tsx: all three hunks sit inside doPublish (about :1296–:1400), about 1,100 lines from seat 3's InterfacesPillar.
App-shell's metadata-admin/i18n.ts: new engine.studio.publishedAll* rows, en and zh. The surface is amended to cover removing engine.studio.org.published, which this change leaves unread.
The changeset.
LayeredDiff.tsx and every draft-existence probe (seat 1's objectui#11799), usePublishAllDrafts.ts, packages/i18n/** and packages/components/src/ui/** are untouched.
H0, measured on b6f8fc8:
The field-level drill-in already existed (PR objectui#2271), but every other key sat in one mixed list.
A no-op draft kept Update even after its drill-in said "No differences detected".
The success toast fired (PR objectui#2203) but named no number.
Each is now answered:
computeChangeDetail returns { added, changed, removed } keys, printed with the field rows' marks.
Once a drill-in finds both bodies present and equal, under the same stripReadDecorations normalisation, the row shows an "equal" glyph named by the existing preview.changes.detailNone sentence.
doPublish names N from the batch's own published[], which PublishPackageDraftsResponseSchema declares.
H1: deciding "no change" at open is O(drafts): one published read per draft, plus one draft read per non-object draft. Option (b) is falsified, because a _drafts row carries no equality field. Option (a), marking on expand, is taken.
Deviation, accepted: no-op drafts stay in the counts, because the server still promotes them and they appear in published[]. The panel's counts and the toast's N therefore agree.
Deviation, accepted, and repaired here: since objectui#10039, doPublish read the batch's success === false before failed[], so every refused publish read "Action failed". This PR restructures those same lines and restores the per-item reasons. It is declared in the PR body and the changeset. Measured before the change with the producer's envelope.
LayeredDiff not imported: that module pulls in the 456 KB metadata-admin string table, and this panel is on the console's first load. The key split is done locally instead.
Clause-②: no holds: no export, prop, @object-ui/types member or packages/i18n key. check:i18n-drift reports no en value changed in packages/i18n.
Changeset:@object-ui/app-shell: patch. Every sentence was checked against the diff.
Evidence:
The union run passes, 17 files and 142 tests, the column-identity ratchet included.
Every test naming the four modules passes, 101 files and 603 tests.
The type-check passes.
Reverse checks:
with the base sources, 15 of 26 fail;
dropping the both-bodies guard turns exactly its 2 pins red;
sending a refusal through the old ordering turns exactly the refusal pin red.
Each restore was proven by blob hash.
CI on 358e461: no failure so far, 6 checks still running. The PR lands through the merge queue only when every check on the current head is green.
Out-of-scope findings:
Filed: the same refused-publish ordering on the Packages page and the Home / draft-preview Publish all, as its own card.
Not filed,carrier: none: a count in usePublishAllDrafts's toast, and a visible "No change" badge word on the panel. Both need packages/i18n keys. They are niceties, not defects.
Activity
objectstack-fleet commentedon Oct 7, 2026
Path: write metadata — the author sees what publishing will change | 缺项 | P3
Triage: first grade,
enhancement·priority:p3·domain:ui·area:studio·pm:queue. Filed as ②, but routed as a Studio UX improvement: no contract or product question is open. Direction: the body's option ATriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-07T17:30Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/app-shell/src/preview/DraftChangesPanel.tsxanddraftStatus.ts⇒domain:ui; rationale: a Studio or console surface in objectui. Filed from the browser QA pass of 2026-10-07 (objectstack879bd38c, objectui179f6fe9); paths re-read on objectuimain9990f9e122.LayeredDiff.tsxwhere it fits)Clause-②: no. Patch changeset in objectui.objectstack-fleet commentedon Oct 8, 2026
Claim: PM loop round 1
Session:
session_01MgfduSkFrfM3eorB3UGfAUAccount:
os-billBranch:
claude/issue-11807-changes-panel-diffWorktree:
objectui-issue-11807Domain:
domain:uiSeat:
domain:ui#2File surface (line numbers on
b6f8fc8):packages/app-shell/src/preview/DraftChangesPanel.tsx:new/updateclassification (kind, about:92–:150): a draft equal to its published item is not listed as Update;computeChangeDetail, about:216–:300): it shows added, removed and changed keys.LayeredDiff.tsxis reused by import where it fits, never edited.packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx: onlydoPublish's success path (about:1271–:1365), for a toast that names how many items were published. The dev first measures whether the existingengine.studio.publishedAlltoast (about:1352) already answers the card.packages/app-shell/src/preview/usePublishAllDrafts.ts: only its success toast, if Home and the preview bar need the same count.packages/app-shell/src/views/metadata-admin/i18n.ts: only new or rewordedengine.studio.changes.*/engine.studio.publishedAll*rows (en and zh)..changeset/11807-*.md.⛔ Not on it:
LayeredDiff.tsxand any draft-existence probe: seat 1's objectui#11799 holds the/layerscall and the?state=draftexistence probes.fetchItemBody's draft read (about:180–:190) reads a body for the diff and is left as it is.StudioDesignSurface.tsxoutsidedoPublish. Seat 3's objectui#11823 holdsInterfacesPillar(from about:2492), which is disjoint.packages/components/src/ui/**andpackages/i18n/**.Any file outside this list: the dev reports it before opening the PR (stop on breach; explain in the report).
Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier --repo objectstack-ai/objectuiover these paths: no path-derived mandate; default tier)Clause-②: no
Responsibility:
objectui app-shell Studio Changes panel: drafts are listed by machine name with New/Update only, a draft identical to the published item is listed as Update, and Publish all gives no count of what it published | the panel already fetches the published and draft bodies for its drill-in, so the comparison is local | every Studio author reviewing a publish, seen by the filer's 2026-10-07 browser QA pass on the showcase (OBJECT · repairs_repair_ticket Update with no change)Thread-read: 6043265917
Serial constraints cleared: overlaps declared, not blocking.
/layerscall inLayeredDiff.tsxand the draft-existence probes. This card importsLayeredDiffwithout editing it, and leavesfetchItemBody's request unchanged.StudioDesignSurface.tsx,InterfacesPillaronly. This card's one hunk sits indoPublish, about 1,100 lines away.DraftChangesPanel.tsx,draftStatus.tsorusePublishAllDrafts.ts.Why
Clause-②: no: this is Studio review UX. Nothing a server stores changes, and no export, prop orpackages/i18nkey is added. New strings live in app-shell's ownmetadata-admin/i18n.tsdefaults table.Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
os-dev-report
{
"issue": 11807,
"status": "done",
"branch": "claude/issue-11807-changes-panel-diff",
"pr": "#11916",
"session": "session_01MgfduSkFrfM3eorB3UGfAU (subagent mode: the parent's id, per claim 6052893161)",
"premise_still_valid": true,
"summary": "H0, measured on b6f8fc8 by rendering the real DraftChangesPanel and the real StudioDesignSurface (real sheet, real MetadataClient, mocked reads). Obs 1 was partly stale. The field-level drill-in has existed since 7283d18 (PR objectui#2271), but every other top-level key was one list: 'Also changed: description, icon, label'. Obs 2 reproduced: the row kept Update even after its drill-in printed 'No differences detected'. Obs 3 was stale: the success toast fires (since 20c1695, PR objectui#2203) but names no number. What changed: (1) computeChangeDetail returns keys {added, changed, removed}, and the drill-in prints them with the field rows' +/~/- marks. (2) Option (a): once an entry's drill-in finds both bodies present and equal (same stripReadDecorations normalisation), the row drops the Update badge and shows an Equal glyph whose accessible name is the existing preview.changes.detailNone sentence. A badge word would need a packages/i18n key, which is outside the surface. H1: deciding no-op at open costs one published body read per draft plus one draft body read per non-object draft, so it is O(drafts). (b) is falsified: a _drafts row carries only type, name, organizationId, packageId, updatedAt and updatedBy. The list read cannot stand in, because it is projected (doc content slim, object mask). (3) doPublish names N from the batch's published[] (declared by PublishPackageDraftsResponseSchema, returned by the client), with singular and plural rows; the package-less scope does the same. Zero answers now show info 'No drafts pending publish': outcome nothing_to_publish (was thrown as 'Action failed') and a package-less read with nothing pending (was the success toast). (4) A refused batch now names each refused item with its reason. Measured before, with the producer's envelope: toast.error 'Action failed'. Since e16f504 (objectui#10039) doPublish read the batch's own success === false before failed[]. This PR rewrites that same chain, so the shipped defect in touched lines is repaired here. Route deviations: no-op drafts stay in the counts, because the server still promotes them and they appear in published[], so the panel's counts agree with the toast's N. LayeredDiff's computeDiffRows is not imported, because that module imports the 456 KB metadata-admin i18n table and this panel is on the console's eager graph.",
"tests": "All at HEAD 358e461 (git rev-parse --short HEAD after the last commit, equal to the remote tip). New tests: DraftChangesPanel.noChangeAndKeySplit-11807.test.tsx (7) and StudioDesignSurface.publishCount-11807.test.tsx (6). Updated: DraftChangesPanel.test.tsx (keys shape). Union run (preview/tests/, 4 Studio publish suites, column-identity.ratchet): 'Test Files 17 passed (17) / Tests 142 passed (142)'. Every test file naming StudioDesignSurface, DraftChangesPanel, usePublishAllDrafts or DraftPreviewBar (101 files; one scripts/ text-only match left out): 'Test Files 101 passed (101) / Tests 603 passed (603)'. pnpm --filter @object-ui/app-shell type-check: exit 0, after the app-shell^... build; tsconfig.test.json includes src/**/.test.tsx. Reverse verification, each leg on committed HEAD with restore proven by blob hash and an empty git diff HEAD. A1: the 3 source files at b6f8fc8, tests at HEAD, gave 'Tests 15 failed | 11 passed (26)', with the NEW/gone guards, the route tests and the panel controls green. A2: ablation-replace dropped the both-bodies guard, gave 'Tests 2 failed | 5 passed (7)' (exactly the two guard pins), anchor 1 to 0, blob c0164c9bc8cc to 35283355b155, restored. A3: the first attempt was a no-op; ablation-replace refused it because the replacement contained the anchor (1 to 1). It was re-run with a dropping anchor that sends a refusal through the success-false throw: 'Tests 1 failed | 5 passed (6)' (only the refusal pin), blob cf06c12f6e45 to c8f0dd0e9c84, restored. No dist preflight: the tests import from source.",
"mcp_calls": "0",
"api_writes": "3 REST writes, each one relay stroke (POST /repos/objectstack-ai/objectstack/dispatches, 204) executed as objectstack-fleet[bot]: (1) POST /repos/objectstack-ai/objectui/pulls, draft PR #11916, read back byte-identical (10108 bytes); (2) POST /repos//issues/11916/assignees, os-bill, via label-write.mjs, read back matches; (3) POST /repos//issues/11807/comments, this os-dev-report, via post-stamped.mjs. Plus git pushes of the branch (the empty-branch probe and 7 commits), which are not REST.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: named producer: objectstack metadata-protocol publishPackageDrafts returns { success: false, outcome: 'refused', failed: [...] } with no error key, and the runtime dispatcher wraps it as { success: true, data } · evidence: PackagesPage's publishDrafts (r.success === false throws readEnvelopeFailureText(r) || 'Action failed' before it reads failedCount) and usePublishAllDrafts (success === false throws 'publish-drafts did not publish this package') both read the batch's own success before failed[]. MetadataClient.publishPackageDrafts unwraps the envelope (since objectui#6965 / PR objectui#10038), so a refused package publish from the Packages page or the Home / draft-preview bar loses the per-item reason. The Studio twin of this ordering was measured on the producer shape (toast.error 'Action failed') and repaired in PR #11916; these two sites are not on this card's surface · dedupe words: publish refused reason Action failed · publish-drafts failed[] success false · MetadataClient unwrap envelope refusal",
"carrier: 承接者:无 · noted, not filed (no count is a missing nicety, not a defect) · usePublishAllDrafts (Home banner, draft-preview bar) still toasts 'Published! Your changes are live.' without a count; a count there needs a home.pendingDrafts. key in packages/i18n · dedupe words: home publish all toast count",
"carrier: 承接者:无 · noted, not filed · a visible 'No change' badge word on the Changes panel row needs a preview.changes.* key in packages/i18n; this PR uses a named glyph instead · dedupe words: changes panel no change badge"
],
"gates": [
{
"command": "pnpm exec vitest run packages/app-shell/src/preview/tests/ + StudioDesignSurface.{publishCount-11807,publishAdvisoryReachesAuthor,heldPublish-11786,packageLessFlows-11553}.test.tsx + packages/core/src/utils/tests/column-identity.ratchet.test.ts",
"exit": 0,
"verdict": "Test Files 17 passed (17) / Tests 142 passed (142)",
"head": "358e4619a"
},
{
"command": "pnpm exec vitest run (101 test files naming StudioDesignSurface / DraftChangesPanel / usePublishAllDrafts / DraftPreviewBar)",
"exit": 0,
"verdict": "Test Files 101 passed (101) / Tests 603 passed (603)",
"head": "358e4619a"
},
{
"command": "pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build && pnpm --filter @object-ui/app-shell type-check",
"exit": 0,
"verdict": "os-verify-lock VERDICT command-exit 0; type-check (tsc --noEmit && tsc -p tsconfig.test.json) exit 0",
"head": "358e4619a"
},
{
"command": "pnpm exec eslint (6 touched source and test files)",
"exit": 0,
"verdict": "0 errors, 15 warnings, none on a changed line (DraftChangesPanel: existing computeChangeDetail export and existing load effect; none inside doPublish)",
"head": "358e4619a"
},
{
"command": "node scripts/check-changeset-presence.mjs",
"exit": 0,
"verdict": "6 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/11807-changes-panel-diff.md.",
"head": "358e4619a"
},
{
"command": "node scripts/check-changeset-no-major.mjs",
"exit": 0,
"verdict": "No changeset declares a
majorbump.","head": "358e4619a"
},
{
"command": "pnpm check:new-line-citations",
"exit": 0,
"verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0",
"head": "358e4619a"
},
{
"command": "pnpm check:control-bytes",
"exit": 0,
"verdict": "check-control-bytes: OK (scanned 7924 tracked text file(s); skipped 85 binary).",
"head": "358e4619a"
},
{
"command": "pnpm check:changeset-claims",
"exit": 0,
"verdict": "No pending changeset names a file this change touches.",
"head": "358e4619a"
},
{
"command": "pnpm check:i18n-keys",
"exit": 0,
"verdict": "Every in-scope call-site key resolves against the en pack (3300 keys) ...",
"head": "358e4619a"
},
{
"command": "pnpm check:i18n-drift",
"exit": 0,
"verdict": "No en value changed in this range. No designer-table en value changed in this range. (designer table: 4 key(s) added, 1 removed)",
"head": "358e4619a"
},
{
"command": "pnpm check:i18n-designer-parity",
"exit": 0,
"verdict": "Every en row has a zh row, and every shared row carries the same placeholders.",
"head": "358e4619a"
},
{
"command": "pnpm check:spec-symbols / check:vi-mock-specifiers / check:vi-mock-inherit / check:vi-mock-override-shape",
"exit": 0,
"verdict": "all OK",
"head": "358e4619a"
},
{
"command": "pnpm lint (repo-wide eslint) and the full pnpm test",
"exit": null,
"verdict": "NOT MEASURED: CI-owned runs; locally only the touched files were linted, and that narrowing is not claimed as a repo-wide reading",
"head": "358e4619a"
}
],
"files_changed": [
".changeset/11807-changes-panel-diff.md",
"packages/app-shell/src/preview/DraftChangesPanel.tsx",
"packages/app-shell/src/preview/tests/DraftChangesPanel.noChangeAndKeySplit-11807.test.tsx",
"packages/app-shell/src/preview/tests/DraftChangesPanel.test.tsx",
"packages/app-shell/src/views/metadata-admin/i18n.ts",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.publishCount-11807.test.tsx",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx"
],
"deviations": [
"i18n rows: besides the new engine.studio.publishedAllCount(/One) and engine.studio.publishedAllFlows(/One) rows (en+zh), the now-unreferenced engine.studio.org.published row (en+zh) is removed. It is outside the claim's named engine.studio.publishedAll* / engine.studio.changes.* prefixes. No engine.studio.changes.* row was needed: the panel's strings live in packages/i18n (preview.changes.*), and the panel cannot import the 456 KB metadata-admin table without putting it on the eager graph.",
"Route (a), with 'leave it out of the count' not followed: publish still promotes a no-op draft (it is in published[]), so the panel's counts stay 'drafts released' and agree with the toast's N.",
"H2 'otherwise the panel's listed count' fallback not used: published[] is present on today's producer, and a response without it falls back to the count-less existing sentence.",
"doPublish's refused-batch ordering (failed[] read before success === false) is repaired in this PR because the restructure touches those lines. It is outside the card's three observations and is declared in the PR body and the changeset.",
"LayeredDiff.tsx is reused by neither import nor edit: the import would carry the metadata-admin i18n table onto the console's eager graph (reasoned from the import graph; eager-closure bytes NOT MEASURED).",
"usePublishAllDrafts.ts is untouched: a count in its toast needs a packages/i18n key (out of surface).",
"Commit trailers follow AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Review: ACCEPT — PR objectui#11916 ·
domain:uiseat 2 · 2026-10-08T06:32ZImplemented-by:
os-devsubagent ofsession_01MgfduSkFrfM3eorB3UGfAU, branchclaude/issue-11807-changes-panel-diff, head358e461.Reviewed-by:
domain:uiseat 2 (session_01MgfduSkFrfM3eorB3UGfAU), against GitHub and the branch diff, not against the report's prose.Checklist:
PR shape: draft, base
main, first lineFixes #11807. No closing keyword sits next to another card.Clause-②: nois at the start of a line.Scope: 7 files (+713 −46).
DraftChangesPanel.tsxwith its tests.StudioDesignSurface.tsx: all three hunks sit insidedoPublish(about:1296–:1400), about 1,100 lines from seat 3'sInterfacesPillar.metadata-admin/i18n.ts: newengine.studio.publishedAll*rows, en and zh. The surface is amended to cover removingengine.studio.org.published, which this change leaves unread.LayeredDiff.tsxand every draft-existence probe (seat 1's objectui#11799),usePublishAllDrafts.ts,packages/i18n/**andpackages/components/src/ui/**are untouched.H0, measured on
b6f8fc8:Each is now answered:
computeChangeDetailreturns{ added, changed, removed }keys, printed with the field rows' marks.stripReadDecorationsnormalisation, the row shows an "equal" glyph named by the existingpreview.changes.detailNonesentence.doPublishnames N from the batch's ownpublished[], whichPublishPackageDraftsResponseSchemadeclares.H1: deciding "no change" at open is O(drafts): one published read per draft, plus one draft read per non-object draft. Option (b) is falsified, because a
_draftsrow carries no equality field. Option (a), marking on expand, is taken.Deviation, accepted: no-op drafts stay in the counts, because the server still promotes them and they appear in
published[]. The panel's counts and the toast's N therefore agree.Deviation, accepted, and repaired here: since objectui#10039,
doPublishread the batch'ssuccess === falsebeforefailed[], so every refused publish read "Action failed". This PR restructures those same lines and restores the per-item reasons. It is declared in the PR body and the changeset. Measured before the change with the producer's envelope.LayeredDiffnot imported: that module pulls in the 456 KB metadata-admin string table, and this panel is on the console's first load. The key split is done locally instead.Clause-②: noholds: no export, prop,@object-ui/typesmember orpackages/i18nkey.check:i18n-driftreports no en value changed inpackages/i18n.Changeset:
@object-ui/app-shell: patch. Every sentence was checked against the diff.Evidence:
The union run passes, 17 files and 142 tests, the column-identity ratchet included.
Every test naming the four modules passes, 101 files and 603 tests.
The type-check passes.
Reverse checks:
Each restore was proven by blob hash.
CI on
358e461: no failure so far, 6 checks still running. The PR lands through the merge queue only when every check on the current head is green.Out-of-scope findings:
carrier: none: a count inusePublishAllDrafts's toast, and a visible "No change" badge word on the panel. Both needpackages/i18nkeys. They are niceties, not defects.Generated by Claude Code
fix(app-shell): the Changes panel splits added/changed/removed keys a…