fix(app-shell): write a served view's toolbar change inside config so it survives reload (objectui#11625) - #11639
Merged
objectstack-fleet[bot] merged 3 commits intoOct 5, 2026
Conversation
…en the row is an envelope (objectui#11625)
The density toggle on a served view sent the whole active tab, which
carries the stored ViewItem envelope `{ name, object, viewKind, config }`,
with `rowHeight` spread beside `config`. The save door judges a body with
a `config` by the spec's `viewItem` member, strips its top level, answers
200 and stores no density. `buildPersistedViewBody` now writes the patch
keys `ListViewSchema` declares into `config` for an envelope-shaped base,
leaves the row-owned keys (`columnState`, `isDefault`, ...) on the
envelope, and keeps the flat spread for a flat base and the overlay
branch unchanged.
Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Co-authored-by: Claude <noreply@anthropic.com>
…e `config` (objectui#11625) Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ 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
|
objectstack-fleet
Bot
deleted the
claude/issue-11625-density-inside-config
branch
October 5, 2026 04:58
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #11625
Clause-②: no
What changed
buildPersistedViewBodybuilds the bodypersistViewPatchsends throughupdateViewConfigwhen a grid toolbar control changes. It now writes the patch insideconfigwhen the active tab carries the stored ViewItem envelope{ name, object, viewKind, config }:ListViewSchemadeclares goes intoconfig:rowHeight,sort,hiddenFieldsorinlineEdit. The key set is read off the spec schema, the same derivationbuildViewConfigSaveBodyuses. A stale copy of that key on the envelope is removed, so the request carries the key once, in the place the door keeps.columnState, plus the row state the envelope already carries (isDefault,isPinned,sortOrder,visibility), which is never moved. This is the objectui#11013 fence.config) still gets the flat spread. The overlay branch (isSavedView: false) is unchanged.The server side is untouched. The door still drops undeclared envelope keys, which triage (
5986778563) keeps as designed. Nothing changes on the@object-ui/app-shellentry:buildPersistedViewBodyis exported fromObjectView.tsx, not from the package.Measured live, before and after
Stack: the console from this worktree (Vite, workspace
srcaliases) against objectstackmain045f764c, showcase app,objectstack dev --seed-admin --fresh, admin session. Each row is one toolbar action in Chromium. I captured thePUT /api/v1/meta/view/NAMEbody and answer in the browser, read the row back withGET /api/v1/meta/view/NAME, and then reloaded in a fresh browser context. "Before" ran this branch withObjectView.tsxchecked out at the base1c2e2c4. "After" ran the fix on a new--freshdatabase.showcase_task.default, densityrowHeight: "medium"besideconfig. Answer200"Saved customization overlay". Stored row has norowHeight, either top-level or inconfig. Reload shows "Density: Compact".config.rowHeight: "medium", no top-level copy,200. Stored row hasconfig.rowHeight: "medium". Reload shows "Density: Comfortable" with 45 px rows.showcase_task.grid, densityconfig.rowHeight: "medium"stored. Reload shows Comfortable, 41 px rows before and 45 px after.showcase_task.my_tasks, densityconfig.rowHeight: "medium"stored. Reload shows Comfortable, 45 px.showcase_task.default, column-header sort on Titlesort,200. Stored row has nosort.config.sortstored as[{ field: "title", order: "asc" }]; the door strips the console's rowid. The reload's data request carriessort=title.showcase_task.urgent, hide-fields toggle, hiding PriorityhiddenFields,200. Stored row has nohiddenFields. Reload shows the Priority column again.config.hiddenFields: ["priority"]stored. Reload shows no Priority column.e7ed7fd, stockshowcase_task.tabular, densityconfig.rowHeight: "medium"stored beside the view's declaredconfig.sort, which is kept. Reload shows Comfortable, 45 px.The
my_tasksrow was created with the envelope "Add View" writes (viewEnvelope), PUT as active. On every showcase view the hide-fields toggle is off by default (userActions.hideFields). ForurgentI turned it on with an envelope save carryingconfig.userActions.hideFields: true, the shape "Edit view config → Save" writes.What the PM's mechanism assumptions measured:
_isOverride. The server serves each list view as a view item, solistViews()returns it andisSavedViewIdanswers true. The tab carriesconfigbecauseMetadataProvidermerges every served view intoobjectDef.listViews, andloadViewOverrideslays the raw stored row over it.showcase_task.done. The density toggle there sent a flat{ rowHeight: "tall", viewKind, object, name, _isOverride: true }and got200. The read-back has a top-levelrowHeight: "tall", and the reload shows "Density: Spacious". This is identical before and after, so that branch needs noconfigand is left alone.config.rowHeight: "tall"onshowcase_task.in_progressreads backconfig.rowHeight: "tall".isDefaultstayed on the envelope. The unit pins below cover the full row-state set andcolumnState.Tests
ObjectView.densityInsideConfig-11625.test.ts.ObjectStackAdapterinto a store whosesaveItemjudges every PUT with the spec'sViewMetadataSchemaand keeps the parsed value, as the live door does. It then reads the row back throughlistViews(),loadViewOverridesandbuildViewTabs.ObjectView.overlayPatchOnly.test.ts. This is the saved-view describe, two cases. TheirSAVED_TABcarries aconfig. The live door was measured stripping a top-level patch key from exactly that shape, so the two assertions that pinned the patch at the top level pinned the defect.carries the whole bodynow asserts thatbody.configequals the row'sconfigplus the sort, and that the body has no top-levelsort.leaves its definition intact at restnow asserts that the storedconfigequals the row'sconfigplusrowHeight, and that the row has no top-levelrowHeight.columns,filter,label, and the withheld_isOverride. The overlay-branch cases and both source ratchets are untouched. Each changed case gains an absence assertion, so the cases are tighter than before, not looser.0113970, before the merge.buildPersistedViewBodyor its writes passed:pnpm exec vitest runoverObjectView.densityInsideConfig-11625.test.ts,ObjectView.overlayPatchOnly.test.ts,InterfaceListPage.hollowOverlayHydration.test.tsxandObjectView.emptyColumnsNoProjection-10694.test.tsxgaveTest Files 4 passed (4)andTests 40 passed (40).pnpm exec vitest run packages/app-shell/gaveTest Files 1001 passed | 1 skipped (1002)andTests 9940 passed | 9 skipped (9949), exit 0.pnpm --filter @object-ui/app-shell lintexited 0 with 0 errors.check:new-line-citations,check:control-bytes,check:test-path-roots,check:metadata-write-doors,check:changeset-claims,check:pending-changeset-literals,scripts/check-changeset-presence.mjsandscripts/check-changeset-no-major.mjs.os-dev-reportcomment on the card. All of these are queued under the shared verify lock behind another seat's run:pnpm exec turbo run type-check --filter=@object-ui/app-shell. The plainpnpm --filter @object-ui/app-shell type-checkexited 2 onTS2307(cannot find@object-ui/componentsand the other dependencies), because the worktree had none of the dependencydist/built. That is an unmet prerequisite, not a reading on this diff.ablation-replace.mjs, the two pin files run, then restored.Acceptance notes
showcase_task.in_progress: change density, wait past the debounce, then sort by header. The second PUT'sconfigcarries the sort but not the density, because every save is a whole-document PUT built from the tab as it was at page load. After a reload only the sort is left. The overlay branch does the same: onshowcase_task.done, the second PUT{ sort, … }replaces the{ rowHeight, … }row. This predates this change and affects every kind of row. It is reported to the seat with the reproduction, not fixed here: the right shape (accumulate written patches in the session, re-read after a write, or read-modify-write in the adapter) is not pinned. The changeset says it plainly.showcase_account), the density toggle sendsPUT /api/v1/meta/view/allwith the fallback tab's idalland noviewKind. The door answers422 INVALID_METADATA(container branch, "Unrecognized key(s) on this view container:rowHeight,_isOverride"). Reported to the seat, not touched.showcase_task.doneoverlay above,GET /api/v1/meta/view/showcase_task.donereturns the overlay with noconfig,columnsorfilter. The console then loads the Done view without itsstatus = donefilter. From a clean state the console does not originate such a row, since every served list view is classed saved. Reported to the seat with the measurement.inlineEditis routed by the same rule and unit-pinned. Its live toggle was not driven, so that is NOT MEASURED. The same holds for a livecolumnStatedrag.Generated by Claude Code