Filed unassigned by the dev seat working #4139, per the "measure the side-symptom, don't fix out of scope" split. The QA run objectstack-ai/objectstack#7439 (saved-view-management FAIL) reported two symptoms together; #4139's fix covers one of them, and this issue records the measurement showing the other has a different root cause, in a different layer.
What #4139 did and did not cover
#4139 was the data-adapter defect: updateView read the published overlay while an ADR-0034 runtime-created view lives only in its draft row, so the read 404ed, a catch {} substituted current = {}, and the merge went out as a {label, name, object} partial the server rejects (422).
Set-default drives that same read-merge-write cycle with an {isDefault: true} patch, so it was emitting the same partial write. That half is fixed and pinned by #4139 (updateView.draft.test.ts, "carries the full document for a set-default patch too, not just renames").
The other reported symptom — set-default fires no write at all — is not explained by that fix, and the measurement below shows it cannot originate in the adapter.
Measurement
ObjectStackAdapter.updateView writes unconditionally for every patch shape: there is no early return between the read and saveItem. Measured directly against the pre-fix code, an {isDefault: true} patch produced a write every time (a partial one, hence the 422 — but a write). So "no write at all" cannot be produced by this method.
Walking up from there, the set-default path has exactly one place that can end in zero writes — packages/app-shell/src/views/ObjectView.tsx:
handleSetDefaultView (:982)
└─ if (!isSavedView(vid)) { toast.error(...); return; } ← the ONLY early return
isSavedView (:909) = savedViews.some(sv => (sv.id || sv._id) === vid)
Past that guard a write is guaranteed: the handler builds updates from savedViews.filter(...) and then unconditionally updates.push(updateView(objectName, vid, { isDefault: true })) (:992), so the array is never empty.
savedViews is populated from listViews(objectName, { previewDrafts }) (:604) and normalized with id: sv.name || sv.id (:616). The tab id used by the caller comes from a separate merge path (:794). So a tab id that does not match any normalized savedViews row produces the observed behaviour, and it does so twice over:
isSavedView(vid) is false → handleSetDefaultView toasts and returns without writing;
- and the same mismatch makes
saved undefined at :1927, which sets readonly: isSystem (:1945) on the tab, and ViewTabBar renders the set-default menu item only under onSetDefaultView && !isReadonly (packages/plugin-view/src/ViewTabBar.tsx:564, :667) — so the entry is absent from the menu rather than present-and-inert.
Both are UI-layer, above the adapter. Note the guard is shared with rename (handleRenameView :913 uses the same isSavedView), so a session where rename did reach the adapter (as #4139's repro did, producing the 404 → 422 sequence) is one where the guard passed — which is why the two symptoms need separating rather than assuming one cause.
What is not yet established
Which of the two mismatch routes the QA run actually hit, and why the tab id and the normalized savedViews key diverged for that view, are not determined here — settling it needs the running app, not static reading. The identity normalization at ObjectView.tsx:616 / :794 and the #2767 P1 qualified-name rule (<object>.<key> as both row key and body.name) are the places to look first.
Scope note
Left unfixed deliberately: the fix lands in ObjectView.tsx / the view-tabs UI, which is #4155's surface, and #4139's dispatch scoped the dev to the data adapter. No pm:queue label — this is a measurement record plus a concrete next step, for PM triage to grade.
Searched open issues for set default / saved view / isSavedView / ViewTabBar before filing: no existing issue covers this path.
Generated by Claude Code
Filed unassigned by the dev seat working #4139, per the "measure the side-symptom, don't fix out of scope" split. The QA run objectstack-ai/objectstack#7439 (
saved-view-managementFAIL) reported two symptoms together; #4139's fix covers one of them, and this issue records the measurement showing the other has a different root cause, in a different layer.What #4139 did and did not cover
#4139 was the data-adapter defect:
updateViewread the published overlay while an ADR-0034 runtime-created view lives only in its draft row, so the read 404ed, acatch {}substitutedcurrent = {}, and the merge went out as a{label, name, object}partial the server rejects (422).Set-default drives that same read-merge-write cycle with an
{isDefault: true}patch, so it was emitting the same partial write. That half is fixed and pinned by #4139 (updateView.draft.test.ts, "carries the full document for a set-default patch too, not just renames").The other reported symptom — set-default fires no write at all — is not explained by that fix, and the measurement below shows it cannot originate in the adapter.
Measurement
ObjectStackAdapter.updateViewwrites unconditionally for every patch shape: there is no early return between the read andsaveItem. Measured directly against the pre-fix code, an{isDefault: true}patch produced a write every time (a partial one, hence the 422 — but a write). So "no write at all" cannot be produced by this method.Walking up from there, the set-default path has exactly one place that can end in zero writes —
packages/app-shell/src/views/ObjectView.tsx:Past that guard a write is guaranteed: the handler builds
updatesfromsavedViews.filter(...)and then unconditionallyupdates.push(updateView(objectName, vid, { isDefault: true }))(:992), so the array is never empty.savedViewsis populated fromlistViews(objectName, { previewDrafts })(:604) and normalized withid: sv.name || sv.id(:616). The tab id used by the caller comes from a separate merge path (:794). So a tab id that does not match any normalizedsavedViewsrow produces the observed behaviour, and it does so twice over:isSavedView(vid)is false →handleSetDefaultViewtoasts and returns without writing;savedundefined at :1927, which setsreadonly: isSystem(:1945) on the tab, andViewTabBarrenders the set-default menu item only underonSetDefaultView && !isReadonly(packages/plugin-view/src/ViewTabBar.tsx:564, :667) — so the entry is absent from the menu rather than present-and-inert.Both are UI-layer, above the adapter. Note the guard is shared with rename (
handleRenameView:913 uses the sameisSavedView), so a session where rename did reach the adapter (as #4139's repro did, producing the 404 → 422 sequence) is one where the guard passed — which is why the two symptoms need separating rather than assuming one cause.What is not yet established
Which of the two mismatch routes the QA run actually hit, and why the tab id and the normalized
savedViewskey diverged for that view, are not determined here — settling it needs the running app, not static reading. The identity normalization at ObjectView.tsx:616 / :794 and the#2767 P1qualified-name rule (<object>.<key>as both row key andbody.name) are the places to look first.Scope note
Left unfixed deliberately: the fix lands in
ObjectView.tsx/ the view-tabs UI, which is #4155's surface, and #4139's dispatch scoped the dev to the data adapter. Nopm:queuelabel — this is a measurement record plus a concrete next step, for PM triage to grade.Searched open issues for
set default/saved view/isSavedView/ViewTabBarbefore filing: no existing issue covers this path.Generated by Claude Code