Skip to content

Set-default on a saved view can fire no write at all — the symptom does not originate in updateView (measured while fixing #4139) #4211

Description

@yinlianghui

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions