Repository navigation
Commit fc65038
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)
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 the `domain:ui` seat 1 PM loop, session
`https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ`. Triage
`5915804318` is the ruling executed; claim `5918635427`.
## What changed
| where | before | now |
|:--|:--|:--|
| `useDraftAutoSave` | the timer was keyed on the dirty flag, the
blocked flag and the snapshot; the save it called was read through a ref
that already addressed the newly opened item, while the buffer still
held the previous item's document | takes `target`, the item its `save`
addresses (a primitive `type: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 by `flush`, 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
changed |
| the four callers | none | pass `flow:NAME` (Automations), the page
leaf key (Interfaces page inspector), `object:NAME` (Data), `app:NAME`
(Interfaces nav) |
| where `StudioDesignSurface` renders `InterfacesPillar` | not keyed: a
package switch kept the route mounted, and with it the nav editor's
dirty flag, its open editing and the open page | `key={packageId}`: one
reset of the whole pillar on a package switch (triage's preferred shape
for objectui#11203) |
No other line of `DataPillar` changes: its hook call gains the required
`target`, 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:
- It is what already happens on every pillar whenever the new item loads
inside the debounce: the load installs the new buffer and clears the
dirty flag, which cancels the timer. The defect was only the slow-load
path. Dropping adds no new write; re-targeting would add a save nobody
sends today.
- It is what the Data pillar's switch has always done (its synchronous
clear), the card's named control.
- It matches the confirmed discard of a package switch (objectui#11203),
where the author said "discard".
- PR objectui#11230's sent-snapshot claim: a save that lands after a
switch reads moved, so it never clears the flag of the item opened
since. objectui#11189's nav-flush rule ("Done" sends a pending edit at
once) is a close of the SAME item's editor and is unchanged; `flush` now
also refuses a period that began on another item.
## Which fix covers which member (the claim asked)
- Automations flow switch, page-inspector page switch: the hook
(ablation leg A).
- A save in flight across a page switch: the hook's claim binding (leg
C). The save itself always addressed its own item (the save call
captured it).
- Data object switch: the control, green at base and at HEAD.
- The nav autosave on a package switch (objectui#11203): the key (leg
B). The hook alone cannot reach it: the nav editor's dirty flag and open
editing are the pillar's state, so after a confirmed discard a flag
still stood over the next package's app, and no edit after the switch
could start a period the hook would send. The page inspector's
package-switch state, which triage asked measured in the same pass, is
the key's too.
## Premises re-measured at `origin/main` `02a22957c0` (after PR
objectui#11259)
The objectui#11204 harness reading still holds on the tip. Pin file
first, fix absent (the red run):
- Automations: edit flow `notify_owner`'s start label to 'Kick-off',
open `nightly_digest` (load held) inside the debounce: **1 save, of
`nightly_digest`, carrying `notify_owner`'s document** (label 'Notify
owner', start 'Kick-off').
- Page inspector: edit page `home`'s label to 'Welcome', open `landing`
(load held): **1 save, of `landing`, carrying `home`'s document** (label
'Welcome', the hello block).
- Package switch, confirmed discard, fast load: **1 save of app
`beta_app` to package `com.beta.app`**, B's own navigation, unedited
(objectui#11203's reading). With B's app load held: **1 save of
`acme_app` to `com.acme.app`**, the edit the author had just confirmed
discarding.
- Page inspector on a package switch: page `home` of package A stays
open on package B, and an edit then saves page `home` with package
`com.beta.app`.
- Data (the control) and the cancelled discard: green.
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 registered `FlowPreview` / `FlowInspector` /
`PagePreview` / `PageBlockInspector` / `PageDefaultInspector`, a server
double that records every save with its package and can hold an item's
load or a save.
- Automations: a flow switch inside the debounce sends nothing; the edit
after the new flow's load saves to it (the control).
- Page inspector: the same for a page switch.
- Page inspector: a save in flight across a page switch lands on its own
page, and an edit typed on the previous page's buffer before the next
page's load lands is not sent there.
- Data: an object switch inside the debounce sends nothing (the control,
as it always has), and an edit after the load saves to it.
- objectui#11203, through the real surface and its `PackageSwitcher`: a
confirmed discard sends nothing to the new package and closes editing;
an edit after the switch saves normally, to `beta_app` in
`com.beta.app`; the same with B's app load held; a cancelled discard
keeps package A and its edit, which then saves to `acme_app` in
`com.acme.app`.
- Page inspector on a package switch: the new package's page opens and
an edit saves to it, in the new package.
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 run` on the pin file (8 cases) at HEAD
`3cd7a994d1`. The pins import the pillar source directly (a relative
import, no `dist/`), so no build leg applies. Legs A to C went through
objectstack's `scripts/ablation-replace.mjs` in WRAP mode (anchor x1 to
x0, blob moved, restore proven: blob == HEAD `f8072729aaf6` and `git
diff HEAD` empty). The base leg swapped in the base file under a trap,
with the same two restore proofs.
| leg | mutation | predicted | observed |
|:--|:--|:--|:--|
| base | `StudioDesignSurface.tsx` at `02a22957c0` (blob `a2259cfc6561`,
verified on disk) | red except the two controls | 6 failed / 2 passed:
the Data control and the cancelled discard green. The in-flight pin
reads 2 saves, `home` 'Welcome' then `landing` 'Typed during load'; the
package-switch page pin finds `home`'s hello block still open on package
B |
| A | the timer's `owned()` check made a no-op | red: Automations, page
switch, in-flight | 3 failed / 5 passed, exactly those |
| B | `key={packageId}` deleted | red: the confirmed-discard pin
(editing stays open over a standing flag), the package-switch page pin |
3 failed / 5 passed: those two AND the slow-load discard pin, which read
1 save of `acme_app` to `com.acme.app` (the discarded edit). Direction
differs from the prediction: an instrumented re-run read `target` still
`app:acme_app` when 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 pin |
| C | the claim's target comparison removed | red: the in-flight pin
only | 1 failed / 7 passed, exactly that |
## Tests and gates at `3cd7a994d1`
Every run below is at `3cd7a994d1`, from the worktree root, with
root-relative paths. Pass counts are read from vitest's own summary
lines.
- Build closure: `turbo run build --filter='@object-ui/app-shell^...'
--concurrency=2`, 28 of 28 tasks, exit 0 (under the shared verify lock,
run at `e4594fdc66`, 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
--listFilesOnly` lists the new pin file once.
- The pin file: 8 of 8 passed.
- `packages/app-shell/src/views/studio-design/`, every test file (82,
the tree listing and `git ls-files` agree), 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 names `useDraftAutoSave`,
`AutomationsPillar`, `InterfacesPillar` or `PackageSwitcher` (`git grep`
finds none outside it), and the objectui#11189 / #11204 / #11167 /
#11196 autosave pins unmodified.
- The surface's other renderers: `StudioRoute.test.tsx`,
`StudioRoute.landingI18n.test.tsx`, `App.uploadAltitude-10131.test.tsx`
and `studio-locale.i18n.test.tsx`: 4 files / 30 tests passed.
- eslint on the 2 changed source and test files: 0 errors.
`StudioDesignSurface.tsx` has 18 warnings, the same rules and counts as
the base version (linted as `git show BASE:path` through `--stdin`). The
pin file has none. No type-aware lint is configured, so the diff cannot
move another file's verdict.
- Gates, each exit 0: `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 imports `SuggestedBindingsPanel`, 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`, and `check-governed-queue-guard --test`
(NOT GOVERNED, 3 paths).
- NOT MEASURED: CI (the full farm, `pnpm lint`, e2e) and a browser run.
## Acceptance notes
- **The dropped edit, on every switch.** An edit made less than 1.5 s
before any switch (flow, page, object or package) is dropped, as above.
On a package switch this now includes a page-inspector edit: before, it
happened to be sent to its own page in its own package (the page save's
callback still held the previous package); now the remount drops it.
Page edits are not held by the surface's leave guard (only nav edits
are), so no prompt announces it. Read at source, not a new class: the
pillar-tab switch unmounts the pillar and drops such an edit the same
way today.
- **An in-flight save's own UI effects are not bound to its item.** A
save of the previous item that lands after a switch still sets the
"unpublished draft" badge, and a failure its error, on the item opened
since. Nothing is written; read at source, not measured.
- **Harness boundary.** `pastDebounce` waits inside one `act()` 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.
- Out of scope, measured on this branch and reported to the seat, not
fixed here: the previous item's buffer stays on screen and editable
under the newly opened item while its load is in flight, and a save from
there writes it into the new item (page inspector autosave, the
Automations enable toggle, the Data pillar autosave); and a package
switch keeps the Automations pillar's open flow of the previous package,
which an edit then saves into the new package.
---
_Generated by [Claude
Code](https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent f61dab1 commit fc65038
3 files changed
Lines changed: 633 additions & 7 deletions
File tree
- .changeset
- packages/app-shell/src/views/studio-design
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
0 commit comments