Repository navigation
fix(app-shell): a Studio switch inside the autosave debounce never writes the previous item's buffer into the one just opened (objectui#11232) - #11265
Conversation
…ites the previous item's buffer into the one just opened useDraftAutoSave binds a dirty period to the item that was open when it began (a new `target` identity from each caller). After a switch nothing is sent, by the timer or by flush, until the caller's dirty flag has fallen, and a save's claim reads moved once the target has changed. The previous item's pending edit is dropped, as the Data pillar's switch has always dropped it. InterfacesPillar is keyed by package where the surface renders it, so a package switch resets the nav editor and the open leaf (objectui#11203, folded in). Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ Co-authored-by: Claude <noreply@anthropic.com>
…11232) The save lands on its own page, and its claim, bound to the item it was sent for, leaves the dirty period standing so an edit typed on the previous page's buffer during the next page's load is not sent there. Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ 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: ① Derived judgmentsInputs: card #11232 (body; triage Accept sets and public surface: none move. (a) The binding, read at source.
⛔ "Never write the old buffer to the new target" holds on every path the card's letter names: the pending send is refused, and an in-flight send addressed its own item when it was taken ( (b) Drop, not re-target. Triage's letter is "cancels or re-targets", and it names (c) The package-switch member. (d) Pins against the ruling's list. Automations and the page inspector reproduce first (4 of 6 red at the branch point; 6 of 8 red on the base leg); Data is the control; a switch inside the debounce sends 0 saves to the newly opened item on each; an edit after the load saves normally on each. All present. Ablation A (the timer's Check-runs on the head (conclusions are the gate verdicts): 35 success, 3 skipped (the two coverage rows and dependabot). On the second read ② Semver level
③ Boundary flagsDev deviations, each answered:
The PR's acceptance notes and the report's
Nothing above blocks. Every path the card's letter and triage's ⛔ name is closed and pinned, the drop is inside "cancels", #11203 rides on its preferred shape with its three pins, and the semver line is right. The seat's ACCEPT waits on the four test shards. Implemented-by: VERDICT: PASS |
Fixes #11232
Clause-②: no
objectui#11203 (the package-switch member, closed by triage as folded into this card) rides here with its three pins.
Implemented on
claude/issue-11232-autosave-send-bound-to-target, dispatched by thedomain:uiseat 1 PM loop, sessionhttps://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ. Triage5915804318is the ruling executed; claim5918635427.What changed
useDraftAutoSavetarget, the item itssaveaddresses (a primitivetype:name, AGENTS.md #10). A dirty period is bound to the item it began on. A period that began on another item is never sent, by the timer or byflush, and it ends when the caller's dirty flag falls (each caller's load clears it as it installs the new buffer). A save's claim (objectui#11204) reads moved once the target has changedflow:NAME(Automations), the page leaf key (Interfaces page inspector),object:NAME(Data),app:NAME(Interfaces nav)StudioDesignSurfacerendersInterfacesPillarkey={packageId}: one reset of the whole pillar on a package switch (triage's preferred shape for objectui#11203)No other line of
DataPillarchanges: its hook call gains the requiredtarget, and its switch (the control) is untouched.The pending edit is dropped, not sent to its own item
A switch inside the debounce drops the previous item's unsent edit. It is never written to the newly opened item, and it is not re-targeted to its own item either. Why this side of the ruling's choice:
flushnow also refuses a period that began on another item.Which fix covers which member (the claim asked)
Premises re-measured at
origin/main02a22957c0(after PR objectui#11259)The objectui#11204 harness reading still holds on the tip. Pin file first, fix absent (the red run):
notify_owner's start label to 'Kick-off', opennightly_digest(load held) inside the debounce: 1 save, ofnightly_digest, carryingnotify_owner's document (label 'Notify owner', start 'Kick-off').home's label to 'Welcome', openlanding(load held): 1 save, oflanding, carryinghome's document (label 'Welcome', the hello block).beta_appto packagecom.beta.app, B's own navigation, unedited (objectui#11203's reading). With B's app load held: 1 save ofacme_apptocom.acme.app, the edit the author had just confirmed discarding.homeof package A stays open on package B, and an edit then saves pagehomewith packagecom.beta.app.PR objectui#11259 did not close any member: its edits (the load effect's first-leaf pick, the
?surface=restore,StudioNavItemInspector) do not touch the hook or the switch paths.Pins
New file
StudioDesignSurface.autosaveSwitch-11232.test.tsx: the real pillars and the real registeredFlowPreview/FlowInspector/PagePreview/PageBlockInspector/PageDefaultInspector, a server double that records every save with its package and can hold an item's load or a save.PackageSwitcher: a confirmed discard sends nothing to the new package and closes editing; an edit after the switch saves normally, tobeta_appincom.beta.app; the same with B's app load held; a cancelled discard keeps package A and its edit, which then saves toacme_appincom.acme.app.The objectui#11189 / objectui#11204 / objectui#11167 / objectui#11196 autosave pins are unmodified and green (see the directory run below).
Reverse verification and ablations (each on a committed HEAD; mutation and restore proven on disk)
Every leg ran
pnpm exec vitest runon the pin file (8 cases) at HEAD3cd7a994d1. The pins import the pillar source directly (a relative import, nodist/), so no build leg applies. Legs A to C went through objectstack'sscripts/ablation-replace.mjsin WRAP mode (anchor x1 to x0, blob moved, restore proven: blob == HEADf8072729aaf6andgit diff HEADempty). The base leg swapped in the base file under a trap, with the same two restore proofs.StudioDesignSurface.tsxat02a22957c0(bloba2259cfc6561, verified on disk)home'Welcome' thenlanding'Typed during load'; the package-switch page pin findshome's hello block still open on package Bowned()check made a no-opkey={packageId}deletedacme_apptocom.acme.app(the discarded edit). Direction differs from the prediction: an instrumented re-run readtargetstillapp:acme_appwhen the debounce fired, because the new package's app name had not committed yet (see the harness note under Acceptance notes). So the key, not the hook, holds that pinTests and gates at
3cd7a994d1Every run below is at
3cd7a994d1, from the worktree root, with root-relative paths. Pass counts are read from vitest's own summary lines.turbo run build --filter='@object-ui/app-shell^...' --concurrency=2, 28 of 28 tasks, exit 0 (under the shared verify lock, run ate4594fdc66, whose source is byte-identical to HEAD's; only the pin file moved since).pnpm --filter @object-ui/app-shell type-check(tsc --noEmit && tsc -p tsconfig.test.json): exit 0.tsc -p tsconfig.test.json --listFilesOnlylists the new pin file once.packages/app-shell/src/views/studio-design/, every test file (82, the tree listing andgit ls-filesagree), in three chunks under the lock: 29 files / 137 tests, 25 / 131, 28 / 207, so 82 files / 475 tests passed, 0 failed. It holds every suite that namesuseDraftAutoSave,AutomationsPillar,InterfacesPillarorPackageSwitcher(git grepfinds none outside it), and the objectui#11189 / finding(app-shell): Studio's shared draft autosave drops an edit made while a save is in flight on its other callers (Automations measured; Data, page inspector) — the family closure, fix in useDraftAutoSave #11204 / finding(app-shell): Studio's Interfaces rail keeps nav editing open after the package answers read-only, so edits are taken on screen and never saved #11167 / finding(app-shell,plugin-designer): the designer's nav surfaces show a label-less nav entry as blank, "unnamed" or its id — the entries objectui#9868 makes producible inherit their target's label only at runtime #11196 autosave pins unmodified.StudioRoute.test.tsx,StudioRoute.landingI18n.test.tsx,App.uploadAltitude-10131.test.tsxandstudio-locale.i18n.test.tsx: 4 files / 30 tests passed.StudioDesignSurface.tsxhas 18 warnings, the same rules and counts as the base version (linted asgit show BASE:paththrough--stdin). The pin file has none. No type-aware lint is configured, so the diff cannot move another file's verdict.check-changeset-presence,check:control-bytes,check:new-line-citations(0 new),check:changeset-claims(the one pending changeset naming this file,6681-declared-lazy-marketplace-routes.md, was read: it says this file statically importsSuggestedBindingsPanel, and that still holds),check:pending-changeset-literals,changeset:check,check:test-path-roots,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:metadata-write-doors, andcheck-governed-queue-guard --test(NOT GOVERNED, 3 paths).pnpm lint, e2e) and a browser run.Acceptance notes
pastDebouncewaits inside oneact()scope, which holds React renders from async continuations until the scope ends while real timers fire. So in leg B the switched-to app's name (set after the app list's await) was not yet committed when the debounce fired, and the hook still read the previous app as the target; that interleaving is also the real one when the new package's app list is slow, and the key is what holds it. Not measured in a browser.Generated by Claude Code