Repository navigation
fix(app-shell,core): the record page's Undo captures only what the record carries, through core's one capture rule (objectui#11082) - #11121
Conversation
…cord carries (objectui#11082) RecordDetailView's own `api` handler captured its Undo snapshot as `pageRecord[k] ?? null`, so a written field the page record does not carry (the server strips fields the reader may not read) was captured as `null` and Undo wrote `null` over the stored value. `captureUpdateUndoData` is now a named export of `@object-ui/core`, and both app-shell `api` handlers (RecordDetailView and useConsoleActionRuntime) call it instead of restating the rule. When any written field is not carried the record page offers no Undo and warns with the missing fields. Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Inputs: card #11082 (body; triage Gate verdicts (the head's check-runs, ① Derived judgments
② Semver level
③ Boundary flagsDev deviations (os-dev-report
Out-of-scope inference (carrier none; judged here, not widened into this PR):
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #11082
Clause-②: yes —
captureUpdateUndoDatabecomes a named export of@object-ui/core, a new published symbol (minoron@object-ui/core). Its answers do not change. No declared type or accepted key moves.What was wrong
RecordDetailView's ownapihandler built the Undo snapshot of an undoable single-record update aspageRecord[k] ?? null. That is the shape objectui#10404 removed fromActionRunneranduseConsoleActionRuntime. The page record is read byfindOnewith no column list, but ObjectStack'sFieldMaskerdeletes every field the reader may not read. So when an undoable record-page action writes a field the reader cannot read, the field is absent frompageRecord,nullis captured, and pressing Undo writesnullover the stored value.What changed
@object-ui/core:captureUpdateUndoData(writtenFields, rowRecord)inActionRunner.tsis now exported by name. The package barrel already re-exportsActionRunner.tsthroughactions/index.ts, so no barrel line changed. The function body is unchanged.rowCarriesstays private.RecordDetailView(apihandler, genericdefaultbranch): the snapshot iscaptureUpdateUndoData(Object.keys(params), pageRecord). When it answersundefined, no undoable op is returned, so the runner pushes nothing and the success toast carries no Undo button. The write still happens. Aconsole.warnnaming the missing fields fires after the successful write, the same diagnostic the runner and the console runtime give. A carried value,nullincluded, is captured as before.useConsoleActionRuntime(apihandler, generic branch): the inline copy of the rule is replaced by a call tocaptureUpdateUndoData, so one rule remains. Its warning and itsmissinglist are unchanged. The list is derived per field through the same helper (captureUpdateUndoData([k], row) === undefined), so there is no second copy of the "carries" predicate.packages/core/README.md: a short section documents the new export (AGENTS.md Add automated testing infrastructure and CI/CD workflows #2). The README documents exported helpers section by section, andcheck:readme-exportsnow judges this import..changeset/11082-detail-undo-capture.md:'@object-ui/core': minor,'@object-ui/app-shell': patch.The
description: action.label || `Undo ${targetObject}`fallback that objectui#11080 owns is kept byte-for-byte. It moved two spaces right into the newif (undoData)block, so objectui#11080 will rebase onto the re-indented line.Pins
packages/app-shell/src/views/RecordDetailView.undoCapture-11082.test.tsxruns the real view. A probe inside the view's ownActionProviderhands backexecute, so the action runs through the real runner, the view'sapihandler, and the view's toast handler. The Undo button is the success toast'saction; pressing it runs the realuseGlobalUndo.missing: ['status']. The write still happens.nullis restored asnull.packages/core/src/actions/__tests__/captureUpdateUndoData.export-11082.test.ts: the package entry exports the function, it is the runner module's own function, and it answers as before. That covers carried values, carriednull, an absent field, a partial set, an own key holdingundefined, and an inherited key.Ablation (fix committed first; mutation and restore through objectstack
scripts/ablation-replace.mjsin wrap mode): the anchorconst undoData = captureUpdateUndoData(written, record);inRecordDetailView.tsxwas replaced by the pre-fixrecord[k] ?? nullloop. Result: 1 hit, blob33aab695363dchanged to131c67079e99. The pin then readTests 2 failed | 2 passed (4): the uncarried case and the partial case failed withAssertionError: expected { …(8) } to be undefined. The carried-nulland carried-value cases stayed green, as expected, since?? nullalso answers correctly for them. Restore: blob equals HEAD (33aab695363d) andgit diff HEADis empty. Nodist/is involved, because the test imports the view by relative path and vitest aliases@object-ui/coretopackages/core/src.Local verification (at
251bd2a05)pnpm exec vitest run --maxWorkers=2 --reporter=verbose packages/core/src/actions/ packages/app-shell/src/views/RecordDetailView.undoCapture-11082.test.tsx:Test Files 31 passed (31)/Tests 479 passed (479). All 5 core pins and all 4 app-shell pins are listed.useConsoleActionRuntimeorRecordDetailView(73 files, found bygit grepover import,import()andvi.mockspecifiers; the 10404, 11056 and 11082 pins are included):Test Files 73 passed (73)/Tests 591 passed (591).turbo run build --filter=@object-ui/app-shell^...(28/28 tasks), then thecheck:doc-snippetsbuild scope (35/35).pnpm --filter @object-ui/app-shell type-check: exit 0.pnpm --filter @object-ui/core type-check: exit 0. Both runtsc --noEmit && tsc -p tsconfig.test.json, so the tests are compiled too.pnpm exec eslinton the 5 changed TS files: exit 0, 0 errors. The diff adds noanyand removes oneas any.check-changeset-presence,check-changeset-no-major,check-control-bytes,check:new-line-citations(0 new citation(s)),check:changeset-claims,check:readme-exports(552 real, 0 wrong-path, 0 fabricated, with the newcaptureUpdateUndoDatarow judgedreal),check:doc-snippets(682 of 682 block(s) judged, 0 failed),check:doc-fences,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check-type-check-coverage,check:self-import,check:pending-changeset-literals,check:test-path-roots,check:unreferenced-sources,check:handler-key-reads.check-governed-queue-guard --teston the 7 paths: NOT GOVERNED.Declared narrowing: the whole app-shell suite was not run locally. The run covers the direct importers of both changed modules, not modules that reach them through a barrel. Repo-wide
pnpm lintandpnpm testwere not run either; those belong to CI.Acceptance notes
content/docspage documents the Undo capture rule.content/docs/core/enhanced-actions.mdxonly names the Undo button's label. No docs page was added, since that is outside this card's file surface. The rule is documented in the core README section above.$expandon declared reference fields, and objectql writes the expanded record back under the same key. So an undoable record-page action that writes a lookup field would capture the expanded object, not the id, as the prior value. Whether the update route accepts that shape was not measured.Session:
https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec(PM seatdomain:ui#2, branchclaude/issue-11082-detail-undo-capture).Generated by Claude Code