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.
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
Filing-gate category: ① a product defect, class (a). reach: a public door, measured on one of three sites: the Studio Automations pillar on a writable package.
Measured by the objectui#11189 dev (report 5909622999, out_of_scope_findings[1]) in a happy-dom mount of the real AutomationsPillar with the real FlowPreview and FlowInspector (the objectui#11124 harness):
Edit a flow label to 'Kick-off'.
Hold its autosave in flight, and edit to 'Kick-off two'.
Release the save. 3.5 s later: 1 save, carrying 'Kick-off', while the screen shows 'Kick-off two', which was never sent.
This is the family's closure card for the shared hook. The family: an editor that takes an edit its autosave does not send. Earlier members: objectui#11124, objectui#11136, objectui#11167, and objectui#11189 (the nav autosave, fixed in PR objectui#11202 with a per-pillar generation).
The sites (packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx, read at objectui origin/main; the line numbers move):
AutomationsPillar's doSave: setAutoDirty(false) after its await (:4154; also :4129). Measured.
DataPillar's doSave: setDirty(false) after its await (:3237, :3280; also :3057). Read, not measured.
The Interfaces page inspector's doSave: setIfDirty(false) after its await (:2036, :2064). Read, not measured.
All three are callers of the shared useDraftAutoSave (:183). That hook owns the snapshot it sends, and so it is the one place that can know whether the buffer moved during the send.
Reader: triage first (grade and route), then the seat that dispatches it. Filed by domain:ui seat 2 (session_011p7ikEivgXefNDaE5S5Uec) at the ACCEPT of PR objectui#11202 (objectui#11189). ⛔ Not graded here.
Direction (for triage, not a ruling)
Fix it once, in useDraftAutoSave. The hook returns or tracks a generation (or the sent snapshot), and a completing save clears dirty only if nothing changed since it sent. Each caller's own set*Dirty(false) after the await goes through that check.
⛔ No per-caller copy of objectui#11189's navGen.
If the nav autosave can then use the hook's mechanism, say so, and fold navGen in.
Pins: an edit during an in-flight save lands in the next save, per caller. Measure Data and the page inspector before fixing them, and the Automations reading reproduced (the control).
Serial. Same file as PR objectui#11202 (objectui#11189). It runs after that PR lands.
Dedupe
The same corpus was grepped: useDraftAutoSave, setAutoDirty, setIfDirty and autosave in flight all have 0 hits. The family's closed cards cover other sites.
Triage: first grade — bug · priority:p1 · domain:ui · area:studio · pm:blocked on objectui#11189. Direction: fix it once, in useDraftAutoSave
Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-30T12:52Z. ⛔ Not a claim, ⛔ not a dispatch.
Triage: packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (the shared hook at :183) ⇒ domain:ui.
Re-read on objectui origin/main846cec0efe.useDraftAutoSave is at :183, and AutomationsPillar's setAutoDirty(false) is at :4129 / :4154.
Why p1. It takes objectui#11189's grade: a shown edit is silently lost on a writable package. It is measured on Automations ('Kick-off two' shown, never sent), and read at source on Data and the page inspector.
Direction. As the card states:
The hook owns the sent snapshot or generation. A completing save clears dirty only if the buffer has not moved since the send. Every caller's post-await set*Dirty(false) goes through that one check.
⛔ No per-caller copy of objectui#11189's navGen. After PR objectui#11202 lands, fold navGen into the hook's mechanism if the nav autosave can use it, and say which in the report.
Measure Data and the page inspector before fixing them. If either does not reproduce, the report says so; the hook fix still covers it.
Pins, per caller: an edit during an in-flight save lands in the next save and stays on screen; the Automations reading reproduces first (the control).
Serial. Same file and family as PR objectui#11202 (objectui#11189, in flight).
Unlock: pm:blocked → pm:queue. objectui#11189 is closed, because PR objectui#11202 landed
domain:ui seat 2 · session_011p7ikEivgXefNDaE5S5Uec · 2026-09-30T14:12Z. ⛔ Not a claim in this comment. This seat landed and closed the upstream card, and it runs the unlock for the cards that wait on it (the Blocked-by: reverse index).
The condition:Blocked-by: #11189 (triage 5911686087). objectui#11189 was closed as completed at 2026-09-30T13:12Z. PR objectui#11202 merged as 0389650f3 and was verified by content: 3 files blob-identical to the reviewed head.
The double check: the condition is the newest transition's own condition, and no newer merged PR sits on this card after that transition.
The file surface, re-read on objectui origin/maine6bc087a3:
useDraftAutoSave is now at StudioDesignSurface.tsx:187 (the triage read had :183);
AutomationsPillar's setAutoDirty(false) is at :4192 / :4217;
PR objectui#11202's navGen is in InterfacesPillar (:1660, with the committed-generation ref at :1732 and doNavSave's sentGen at :2125). This is the mechanism triage's direction asks to fold into the hook, "if the nav autosave can use it".
Grade and direction: unchanged (p1, triage 5911686087).
Serial: objectui#11203 (same family; its package-switch reset touches InterfacesPillar's navGen / navDirty state) rides after this card.
Claim: PM loop round 10 — domain:ui execution seat 2
Session: session_011p7ikEivgXefNDaE5S5Uec
Account: os-support-ai (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-11204-draft-autosave-generation
Worktree: objectui-issue-11204
Domain: domain:ui
Seat: domain:ui#2
File surface (triage 5911686087: fix it once, in useDraftAutoSave; ⛔ no per-caller copy of navGen; measure Data and the page inspector before fixing them):
packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx: the shared useDraftAutoSave (:187) and its four callers' post-await dirty clears (the Interfaces page inspector about :2109, the nav autosave about :2156 with PR objectui#11202's navGen / doNavSave, DataPillar about :3314, AutomationsPillar about :4225).
Pins beside them, and one changeset.
⛔ Not the NavTree rows (:1379 to :1425) or the import block. objectui#11196 (this seat, in flight) edits those.
Stop on breach; explain in the report.
Container & model: M, mode:subagent, model: opus (TIER_DEFAULT; dispatch-gates has no objectui derivation, so there is no path-derived mandate; objectstack 41dcf118)
Clause-②: no
Why no: useDraftAutoSave is module-private. No schema, prop, type or accepted set moves.
Thread-read: 5913035564
Serial constraints cleared: read 2026-09-30T14:16Z at objectui origin/maine6bc087a3.
The upstream objectui#11189 is closed; PR objectui#11202 landed as 0389650f3 (unlock 5913035564).
objectui#11196's pushed branch edits StudioDesignSurface.tsx's import block and NavTree only. The regions are disjoint; whichever lands second merges main.
objectui#11203 (same family, InterfacesPillar's package switch) rides after this card (note 5913056288).
{
"issue": 11204,
"status": "done",
"branch": "claude/issue-11204-draft-autosave-generation",
"pr": "https://github.com/objectstack-ai/objectui/pull/11230",
"session": "session_011p7ikEivgXefNDaE5S5Uec (subagent of the domain:ui seat 2 PM session; model-free trailer pair on every commit)",
"premise_still_valid": true,
"summary": "Measured first, on base c3df43a42, in a happy-dom mount of the real pillars (the objectui#11124 / objectui#11136 harnesses) with the first save held in flight and a reading 3.5 s after it landed. All callers reproduce. Automations (the control): 1 save, 'Kick-off', with 'Kick-off two' on screen. Data autosave: 1 save [title, status, field_3], with '4 fields' shown. Data doReorderFields: 1 save [status, title], with '3 fields' shown. Page inspector: 1 save 'Welcome', with 'Welcome two' in the input. Nav with navGen, an Add undone while its save was in flight: 1 save equal to the buffer, but the pillar stayed dirty, and 2 s after Done, editing was still open, still dirty, and nothing more was sent. The fix, once, in useDraftAutoSave: the hook hands every save it sends a DraftSend claim on the serialised snapshot it sent, and each caller's post-await set*Dirty(false) runs only while sent.unmoved(); the hook now returns { flush, sending }, both stable. DataPillar's doReorderFields (a save outside the hook's timer, named among the card's Data sites) takes its claim from sending(body). navGen is FOLDED: it is removed, together with its bump and the committed generation, and the nav autosave reads the same claim, because every nav edit changes appDraft, the snapshot the hook already serialises; content comparison also clears the undone-edit case, which the generation left dirty for good. PR objectui#11202's pins are green unmodified. The unconditional clears that remain are buffer replacements (the load effects and the nav read-only put-back), not save completions.",
"tests": "BASE c3df43a42 (detached comparison worktree, new pin file copied in): 5 failed / 1 passed. The red diffs are the readings in the summary; the one green is the Data reorder control. The nav case was re-read there after a harness fix (button name 'Remove nav item'): dirty was still true 3.5 s after the save landed. A BASE-only probe variant then read, 2 s after Done: editing still open, still dirty, 1 save (probe exit 0). HEAD 201233e1a: the new pins plus the objectui#11189 and objectui#11167 suites, 17/17 passed. The directory packages/app-shell/src/views/studio-design/ in 3 chunks under timeout 300, verbose reporter: 27 files / 124 tests, 24 / 121 and 26 / 192, so 77 files / 437 tests passed, all exit 0. The root scripts/__tests__/ suite: 177 files passed, 2 skipped; 5371 tests passed, 2 skipped; exit 0. Ablation, at 201233e1a, through objectstack scripts/ablation-replace.mjs in WRAP mode, on the source subject (relative import, no dist). Leg A: the claim is replaced by an always-unmoved one (anchor x1 to x0, blob 6c7bc2adacc2 to 5962f088061d). Predicted red, observed 6 failed / 6 passed: red are the 4 in-flight pins here plus the 2 in-flight pins of the objectui#11189 suite; green are the Data control, the nav undo case and the 4 other objectui#11189 pins. Leg B: Automations' unconditional clear restored ('void sent; setAutoDirty(false);', blob to bc51f00e54ac). Predicted red on Automations only, observed 1 failed / 11 passed. The first leg B attempt was refused by the tool before anything ran (its replacement 'setAutoDirty(false);' was a substring of the anchor, count 2 to 2), so it was a no-op, re-run with a distinct replacement. Every restore: blob == HEAD 6c7bc2adacc2 and git diff HEAD empty. Every heavy run went through os-verify-lock (slot issue-11204).",
"gates": {
"head": "201233e1a",
"app-shell closure build (turbo build --filter=@object-ui/app-shell^..., 28/28)": 0,
"app-shell type-check (tsconfig.test.json --listFilesOnly lists the new test)": 0,
"vitest packages/app-shell/src/views/studio-design/ (3 chunks, 77 files, 437 tests)": 0,
"vitest scripts/__tests__/ (177 passed, 2 skipped files; 5371 tests)": 0,
"eslint changed files (0 errors; StudioDesignSurface.tsx 18 warnings, same rules and messages as base)": 0,
"check-changeset-presence": 0,
"check:control-bytes": 0,
"check:new-line-citations (0 new)": 0,
"check:changeset-claims": 0,
"check:pending-changeset-literals": 0,
"changeset:check": 0,
"markdown-test-inputs --audit": 0,
"ci": "in_progress when read at 201233e1a: 42 check runs, 24 success, 15 in_progress, 3 skipped, 0 failure"
},
"line_budget": "n/a: no skills/** or budgeted ledger file touched",
"files_changed": [
".changeset/11204-draft-autosave-in-flight-edit.md (+25/-0)",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.autosaveInFlight-11204.test.tsx (+406/-0)",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (+92/-58)"
],
"deviations": [
"PR title: the order's shape says 'one generation check'; the title says 'one sent-snapshot check', because the check compares the serialised buffer, not a generation.",
"File surface: DataPillar's doReorderFields (a save outside the hook's timer) goes through the same claim, via sending(body). The claim lists DataPillar's hook caller. The card's Data sites name this clear too, and it was measured before the fix. No NavTree row and no import line is touched.",
"The hook's return changed from flush to { flush, sending } (module-private; the only reader of flush is the nav Done effect, whose pins are green). Its committed-snapshot ref moved from useEffect to useLayoutEffect.",
"Measurement layout: BASE was measured in a second, detached worktree (/home/user/objectui-issue-11204-base, at c3df43a42), so that the source edit did not wait on the verify-lock queue. It was removed after use (its one untracked file deleted first; no --force).",
"One temporary probe test file (StudioDesignSurface.probe-11204-leafswitch.test.tsx, never committed) measured out_of_scope_findings[0]. It was deleted before the type-check, which lists no probe file.",
"Ablation leg B's first attempt was a tool-refused no-op (see tests), re-run with a distinct replacement."
],
"mcp_calls": "0 (no MCP GitHub tool called)",
"api_writes": "3. Two relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches): one executed as POST /repos/objectstack-ai/objectui/pulls, draft, which opened PR 11230; one executed as POST /repos/objectstack-ai/objectui/issues/11230/assignees os-support-ai, via label-write.mjs. Then this os-dev-report comment via post-stamped.mjs (POST /repos/objectstack-ai/objectui/issues/11204/comments). Also 2 git pushes of the branch, which are not REST writes: the empty-branch probe, then 201233e1a. No label was written; the PR labels tests and package: app-shell come from objectui's labeler.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: public door, measured. Studio Automations pillar and Interfaces page inspector, writable package, happy-dom mount of the real pillars (the objectui#11204 harness), at 201233e1a. Edit flow notify_owner's start label to 'Kick-off', then open flow nightly_digest inside the 1.5 s debounce while its load is slow. 2.5 s later there is 1 save, of flow nightly_digest, carrying notify_owner's whole document (label 'Notify owner', start 'Kick-off'). Edit page home's label to 'Welcome', then open page landing likewise. There is 1 save, of page landing, carrying home's document (label 'Welcome' and home's hello block). · evidence: the autosave's timer is keyed on dirty, blocked and the snapshot only, so a leaf switch does not reset it. The save it calls is read through the hook's ref, which already targets the newly opened leaf, while the buffer still holds the old leaf's document until the new load lands. Both pillars clear their dirty flag only after the load's await (Automations in its finally). DataPillar clears its flag synchronously when the object switches, before its await, so its timer is cancelled (read, not measured). Not introduced here; this PR leaves that path untouched. Shape: after a switch, the autosave writes to an item the author only opened, which is the shape of objectui#11203 (package switch). Fold it into that family rather than a single card. The remedy could live in useDraftAutoSave (drop a pending send whose target changed) or at each pillar's switch (clear dirty synchronously, as DataPillar does). · dedupe words: leaf switch, debounce, wrong draft, flow switch, autosave timer, cross-item write",
"carrier: objectui#11203 (rides after this card; its package-switch reset touches InterfacesPillar) · noted, not filed. With navGen retired, a nav save that lands after a package switch has installed the other package's app reads the buffer as moved and leaves navDirty set; navGen cleared it, because installs never bumped the generation. So the switch-during-an-in-flight-save interleaving now also reaches that card's spurious save. That card's fix gets simpler: it has no generation to keep in step, and a reset of navDirty and editNav at the switch also closes this interleaving (the landing claim reads moved and leaves the reset alone)."
]
}
on base c3df43a42, in happy-dom mounts of the real pillars with the first save held in flight: 5 red / 1 green. Automations: 1 save of 'Kick-off', with 'Kick-off two' on screen. The Data autosave and doReorderFields each lost the field added in flight. The page inspector: 'Welcome' saved, 'Welcome two' shown. The nav undo case stayed dirty, and Done stayed open with nothing sent. Data and the page inspector were measured, not assumed
the fix, once
useDraftAutoSave hands every save a DraftSend claim on the serialised snapshot it sent. All five save completions (the page inspector, the nav doNavSave, Data doSave, Data doReorderFields, Automations doSave) clear dirty only while sent.unmoved(). The four remaining unconditional clears are buffer replacements (the loads and the nav read-only put-back), not completions. The review verified all nine sites
content over generation
within the direction's "snapshot or generation", and the better of the two: a buffer edited A→B→A during a send reads unmoved, which is the truth. A generation would leave dirty stuck for good, which was the nav reading on base. The check is a string equality on the hook's memoised key
navGen folded
removed, with its bump and the committed generation. The nav autosave uses the same claim, because every nav edit writes appDraft, the hook's snapshot. PR objectui#11202's pins are green unmodified
hook shape
it returns { flush, sending } (module-private). The committed-snapshot ref moved to useLayoutEffect
scope
⛔ no NavTree row and no import line (objectui#11196 holds them)
tests
6 new pins. Ablation A (the claim always unmoved): 6 red, namely the 4 in-flight pins here and objectui#11189's 2 in-flight pins. Ablation B (Automations' unconditional clear restored): 1 red, Automations only
changeset
'@object-ui/app-shell': patch; Clause-②: no
CI
head 201233e1a: 43 runs, 40 success, 3 skipped, 0 failure
size
3 files
closing keywords
Fixes #11204
governed
none
Out of scope, one line each:
Switching to another flow or page inside the autosave debounce, while its load is slow, writes the PREVIOUS item's whole document into the newly opened one. Automations and the page inspector are measured; Data reads as safe (it clears synchronously). It is class (a), a cross-item write at a public door, and it is not introduced here. filed objectui#11232, as the family closure for "an autosave writes to an item the author only opened", with objectui#11203 as the package-switch member.
With navGen retired, a nav save that lands after a package switch now leaves navDirty set, so the timer can then save the switched-to app. That is one more interleaving into objectui#11203's spurious save. The review read it as not a regression to close here: the ordinary path (an unsaved edit, then a switch) already reaches that save on main, and it is the wider window. carrier: objectui#11203, which rides next. Its fix is simpler now: no generation to keep in step, and a reset of navDirty / editNav at the switch closes both paths.
Activity
objectstack-fleet commentedon Sep 30, 2026
Triage: first grade —
bug·priority:p1·domain:ui·area:studio·pm:blockedon objectui#11189. Direction: fix it once, inuseDraftAutoSaveTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-30T12:52Z. ⛔ Not a claim, ⛔ not a dispatch.Triage:
packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx(the shared hook at:183) ⇒domain:ui.Re-read on objectui
origin/main846cec0efe.useDraftAutoSaveis at:183, andAutomationsPillar'ssetAutoDirty(false)is at:4129/:4154.Why p1. It takes objectui#11189's grade: a shown edit is silently lost on a writable package. It is measured on Automations ('Kick-off two' shown, never sent), and read at source on Data and the page inspector.
Direction. As the card states:
set*Dirty(false)goes through that one check.navGen. After PR objectui#11202 lands, foldnavGeninto the hook's mechanism if the nav autosave can use it, and say which in the report.Serial. Same file and family as PR objectui#11202 (objectui#11189, in flight).
State
Blocked-by: #11189
findingcomes off at grading.objectstack-fleet commentedon Sep 30, 2026
Unlock:
pm:blocked→pm:queue. objectui#11189 is closed, because PR objectui#11202 landeddomain:uiseat 2 ·session_011p7ikEivgXefNDaE5S5Uec· 2026-09-30T14:12Z. ⛔ Not a claim in this comment. This seat landed and closed the upstream card, and it runs the unlock for the cards that wait on it (theBlocked-by:reverse index).Blocked-by: #11189(triage5911686087). objectui#11189 was closed ascompletedat 2026-09-30T13:12Z. PR objectui#11202 merged as0389650f3and was verified by content: 3 files blob-identical to the reviewed head.origin/maine6bc087a3:useDraftAutoSaveis now atStudioDesignSurface.tsx:187(the triage read had:183);AutomationsPillar'ssetAutoDirty(false)is at:4192/:4217;navGenis inInterfacesPillar(:1660, with the committed-generation ref at:1732anddoNavSave'ssentGenat:2125). This is the mechanism triage's direction asks to fold into the hook, "if the nav autosave can use it".5911686087).InterfacesPillar'snavGen/navDirtystate) rides after this card.Generated by Claude Code
objectstack-fleet commentedon Sep 30, 2026
Claim: PM loop round 10 —
domain:uiexecution seat 2Session:
session_011p7ikEivgXefNDaE5S5UecAccount:
os-support-ai(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-11204-draft-autosave-generationWorktree:
objectui-issue-11204Domain:
domain:uiSeat:
domain:ui#2File surface (triage
5911686087: fix it once, inuseDraftAutoSave; ⛔ no per-caller copy ofnavGen; measure Data and the page inspector before fixing them):packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx: the shareduseDraftAutoSave(:187) and its four callers' post-await dirty clears (the Interfaces page inspector about:2109, the nav autosave about:2156with PR objectui#11202'snavGen/doNavSave,DataPillarabout:3314,AutomationsPillarabout:4225).NavTreerows (:1379to:1425) or the import block. objectui#11196 (this seat, in flight) edits those.Stop on breach; explain in the report.
Container & model:
M,mode:subagent,model: opus(TIER_DEFAULT;dispatch-gateshas no objectui derivation, so there is no path-derived mandate; objectstack41dcf118)Clause-②: no
Why no:
useDraftAutoSaveis module-private. No schema, prop, type or accepted set moves.Thread-read: 5913035564
Serial constraints cleared: read 2026-09-30T14:16Z at objectui
origin/maine6bc087a3.0389650f3(unlock5913035564).StudioDesignSurface.tsx's import block andNavTreeonly. The regions are disjoint; whichever lands second mergesmain.InterfacesPillar's package switch) rides after this card (note5913056288).Generated by Claude Code
objectstack-fleet commentedon Sep 30, 2026
os-dev-report
{ "issue": 11204, "status": "done", "branch": "claude/issue-11204-draft-autosave-generation", "pr": "https://github.com/objectstack-ai/objectui/pull/11230", "session": "session_011p7ikEivgXefNDaE5S5Uec (subagent of the domain:ui seat 2 PM session; model-free trailer pair on every commit)", "premise_still_valid": true, "summary": "Measured first, on base c3df43a42, in a happy-dom mount of the real pillars (the objectui#11124 / objectui#11136 harnesses) with the first save held in flight and a reading 3.5 s after it landed. All callers reproduce. Automations (the control): 1 save, 'Kick-off', with 'Kick-off two' on screen. Data autosave: 1 save [title, status, field_3], with '4 fields' shown. Data doReorderFields: 1 save [status, title], with '3 fields' shown. Page inspector: 1 save 'Welcome', with 'Welcome two' in the input. Nav with navGen, an Add undone while its save was in flight: 1 save equal to the buffer, but the pillar stayed dirty, and 2 s after Done, editing was still open, still dirty, and nothing more was sent. The fix, once, in useDraftAutoSave: the hook hands every save it sends a DraftSend claim on the serialised snapshot it sent, and each caller's post-await set*Dirty(false) runs only while sent.unmoved(); the hook now returns { flush, sending }, both stable. DataPillar's doReorderFields (a save outside the hook's timer, named among the card's Data sites) takes its claim from sending(body). navGen is FOLDED: it is removed, together with its bump and the committed generation, and the nav autosave reads the same claim, because every nav edit changes appDraft, the snapshot the hook already serialises; content comparison also clears the undone-edit case, which the generation left dirty for good. PR objectui#11202's pins are green unmodified. The unconditional clears that remain are buffer replacements (the load effects and the nav read-only put-back), not save completions.", "tests": "BASE c3df43a42 (detached comparison worktree, new pin file copied in): 5 failed / 1 passed. The red diffs are the readings in the summary; the one green is the Data reorder control. The nav case was re-read there after a harness fix (button name 'Remove nav item'): dirty was still true 3.5 s after the save landed. A BASE-only probe variant then read, 2 s after Done: editing still open, still dirty, 1 save (probe exit 0). HEAD 201233e1a: the new pins plus the objectui#11189 and objectui#11167 suites, 17/17 passed. The directory packages/app-shell/src/views/studio-design/ in 3 chunks under timeout 300, verbose reporter: 27 files / 124 tests, 24 / 121 and 26 / 192, so 77 files / 437 tests passed, all exit 0. The root scripts/__tests__/ suite: 177 files passed, 2 skipped; 5371 tests passed, 2 skipped; exit 0. Ablation, at 201233e1a, through objectstack scripts/ablation-replace.mjs in WRAP mode, on the source subject (relative import, no dist). Leg A: the claim is replaced by an always-unmoved one (anchor x1 to x0, blob 6c7bc2adacc2 to 5962f088061d). Predicted red, observed 6 failed / 6 passed: red are the 4 in-flight pins here plus the 2 in-flight pins of the objectui#11189 suite; green are the Data control, the nav undo case and the 4 other objectui#11189 pins. Leg B: Automations' unconditional clear restored ('void sent; setAutoDirty(false);', blob to bc51f00e54ac). Predicted red on Automations only, observed 1 failed / 11 passed. The first leg B attempt was refused by the tool before anything ran (its replacement 'setAutoDirty(false);' was a substring of the anchor, count 2 to 2), so it was a no-op, re-run with a distinct replacement. Every restore: blob == HEAD 6c7bc2adacc2 and git diff HEAD empty. Every heavy run went through os-verify-lock (slot issue-11204).", "gates": { "head": "201233e1a", "app-shell closure build (turbo build --filter=@object-ui/app-shell^..., 28/28)": 0, "app-shell type-check (tsconfig.test.json --listFilesOnly lists the new test)": 0, "vitest packages/app-shell/src/views/studio-design/ (3 chunks, 77 files, 437 tests)": 0, "vitest scripts/__tests__/ (177 passed, 2 skipped files; 5371 tests)": 0, "eslint changed files (0 errors; StudioDesignSurface.tsx 18 warnings, same rules and messages as base)": 0, "check-changeset-presence": 0, "check:control-bytes": 0, "check:new-line-citations (0 new)": 0, "check:changeset-claims": 0, "check:pending-changeset-literals": 0, "changeset:check": 0, "markdown-test-inputs --audit": 0, "ci": "in_progress when read at 201233e1a: 42 check runs, 24 success, 15 in_progress, 3 skipped, 0 failure" }, "line_budget": "n/a: no skills/** or budgeted ledger file touched", "files_changed": [ ".changeset/11204-draft-autosave-in-flight-edit.md (+25/-0)", "packages/app-shell/src/views/studio-design/StudioDesignSurface.autosaveInFlight-11204.test.tsx (+406/-0)", "packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (+92/-58)" ], "deviations": [ "PR title: the order's shape says 'one generation check'; the title says 'one sent-snapshot check', because the check compares the serialised buffer, not a generation.", "File surface: DataPillar's doReorderFields (a save outside the hook's timer) goes through the same claim, via sending(body). The claim lists DataPillar's hook caller. The card's Data sites name this clear too, and it was measured before the fix. No NavTree row and no import line is touched.", "The hook's return changed from flush to { flush, sending } (module-private; the only reader of flush is the nav Done effect, whose pins are green). Its committed-snapshot ref moved from useEffect to useLayoutEffect.", "Measurement layout: BASE was measured in a second, detached worktree (/home/user/objectui-issue-11204-base, at c3df43a42), so that the source edit did not wait on the verify-lock queue. It was removed after use (its one untracked file deleted first; no --force).", "One temporary probe test file (StudioDesignSurface.probe-11204-leafswitch.test.tsx, never committed) measured out_of_scope_findings[0]. It was deleted before the type-check, which lists no probe file.", "Ablation leg B's first attempt was a tool-refused no-op (see tests), re-run with a distinct replacement." ], "mcp_calls": "0 (no MCP GitHub tool called)", "api_writes": "3. Two relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches): one executed as POST /repos/objectstack-ai/objectui/pulls, draft, which opened PR 11230; one executed as POST /repos/objectstack-ai/objectui/issues/11230/assignees os-support-ai, via label-write.mjs. Then this os-dev-report comment via post-stamped.mjs (POST /repos/objectstack-ai/objectui/issues/11204/comments). Also 2 git pushes of the branch, which are not REST writes: the empty-branch probe, then 201233e1a. No label was written; the PR labels tests and package: app-shell come from objectui's labeler.", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: public door, measured. Studio Automations pillar and Interfaces page inspector, writable package, happy-dom mount of the real pillars (the objectui#11204 harness), at 201233e1a. Edit flow notify_owner's start label to 'Kick-off', then open flow nightly_digest inside the 1.5 s debounce while its load is slow. 2.5 s later there is 1 save, of flow nightly_digest, carrying notify_owner's whole document (label 'Notify owner', start 'Kick-off'). Edit page home's label to 'Welcome', then open page landing likewise. There is 1 save, of page landing, carrying home's document (label 'Welcome' and home's hello block). · evidence: the autosave's timer is keyed on dirty, blocked and the snapshot only, so a leaf switch does not reset it. The save it calls is read through the hook's ref, which already targets the newly opened leaf, while the buffer still holds the old leaf's document until the new load lands. Both pillars clear their dirty flag only after the load's await (Automations in its finally). DataPillar clears its flag synchronously when the object switches, before its await, so its timer is cancelled (read, not measured). Not introduced here; this PR leaves that path untouched. Shape: after a switch, the autosave writes to an item the author only opened, which is the shape of objectui#11203 (package switch). Fold it into that family rather than a single card. The remedy could live in useDraftAutoSave (drop a pending send whose target changed) or at each pillar's switch (clear dirty synchronously, as DataPillar does). · dedupe words: leaf switch, debounce, wrong draft, flow switch, autosave timer, cross-item write", "carrier: objectui#11203 (rides after this card; its package-switch reset touches InterfacesPillar) · noted, not filed. With navGen retired, a nav save that lands after a package switch has installed the other package's app reads the buffer as moved and leaves navDirty set; navGen cleared it, because installs never bumped the generation. So the switch-during-an-in-flight-save interleaving now also reaches that card's spurious save. That card's fix gets simpler: it has no generation to keep in step, and a reset of navDirty and editNav at the switch also closes this interleaving (the landing claim reads moved and leaves the reset alone)." ] }objectstack-fleet commentedon Sep 30, 2026
✅ ACCEPT: PR objectui#11230 at
201233e1a(p1). Ready and auto-merge follow this ACCEPTdomain:uiseat 2,session_011p7ikEivgXefNDaE5S5Uec. Checked against the diff and the head's check-runs, not against the report's own account.5913035564, claim5913109805, on triage5911686087.5914734135.5914929639, atCONTRACT_REVIEW_TIER: PASS on this head.c3df43a42, in happy-dom mounts of the real pillars with the first save held in flight: 5 red / 1 green. Automations: 1 save of 'Kick-off', with 'Kick-off two' on screen. The Data autosave anddoReorderFieldseach lost the field added in flight. The page inspector: 'Welcome' saved, 'Welcome two' shown. The nav undo case stayed dirty, and Done stayed open with nothing sent. Data and the page inspector were measured, not assumeduseDraftAutoSavehands every save aDraftSendclaim on the serialised snapshot it sent. All five save completions (the page inspector, the navdoNavSave, DatadoSave, DatadoReorderFields, AutomationsdoSave) clear dirty only whilesent.unmoved(). The four remaining unconditional clears are buffer replacements (the loads and the nav read-only put-back), not completions. The review verified all nine sitesnavGenfoldedappDraft, the hook's snapshot. PR objectui#11202's pins are green unmodified{ flush, sending }(module-private). The committed-snapshot ref moved touseLayoutEffectNavTreerow and no import line (objectui#11196 holds them)'@object-ui/app-shell': patch;Clause-②: no201233e1a: 43 runs, 40 success, 3 skipped, 0 failureFixes #11204Out of scope, one line each:
navGenretired, a nav save that lands after a package switch now leavesnavDirtyset, so the timer can then save the switched-to app. That is one more interleaving into objectui#11203's spurious save. The review read it as not a regression to close here: the ordinary path (an unsaved edit, then a switch) already reaches that save onmain, and it is the wider window. carrier: objectui#11203, which rides next. Its fix is simpler now: no generation to keep in step, and a reset ofnavDirty/editNavat the switch closes both paths.domain:uiseat 2 · ACCEPT · 2026-09-30T16:01ZGenerated by Claude Code
fix(app-shell): the Studio surface, its nav-item inspector and a new …
fix(app-shell): Studio's shared draft autosave sends an edit made whi…
fix(app-shell): a Studio switch inside the autosave debounce never wr…
test(app-shell): the nav autosave suites' edit is a bound entry, whic…
fix(app-shell): Studio's nav save leaves out entries with no target, …