Repository navigation
console(grid): density / row-height change is saved as a flat top-level rowHeight on the whole served view envelope — the save door drops it with 200 and the density reverts on reload #11625
Description
Activity
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsTriage: first grade —
bug·priority:p2·domain:ui·area:records·pm:queue. The client writes the density patch insideconfig; the save door's drop stays as designedTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-05T01:54Z. ⛔ Not a claim, ⛔ not a dispatch.- Why p2. The save answers 200 and the preference is gone on reload: the user is told it saved, and it did not. The verifier measured it live twice (VF3). It predates 17.6.0, so it does not hold the 17.7.0 release.
- Direction.
buildPersistedViewBody(ObjectView.tsxabout:1181) puts view-config patches insideconfig, not on the envelope.- The card says
sortandhiddenFieldsprobably share the loss. The dev drives both through the same door in the same PR. A measured loss there rides this fix; an unmeasured one is stated in the report.
- The card says
- Not adopted: the optional framework flag for discarded undeclared envelope keys. The drop is by design (ADR-0005 appendix (c)), and a new answer field is a new contract surface with no second occurrence behind it.
- Routing:
app-shellviews, sodomain:ui·area:records.
Generated by Claude Code
- addedarea:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Oct 5, 2026 objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsPath: views to see the data | records-forms.grid-personalization | P2
Triage: the grade above (
5986778563) carries noPath:line. Here it is. This amends my5986778563Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-05T01:58Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/app-shell/src/views/ObjectView.tsx(buildPersistedViewBody) ⇒domain:ui; rationale: the save door drops undeclared envelope keys by design, so the client must write insideconfig.The grade, direction and labels stand.
triage-duties.mdline 60 puts thePath:line first; line 74 asks for the lands-in line.
Generated by Claude Code
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsClaim: PM loop round 2
Session:session_015W8GBu6sBiqus2L2xjMsAL
Account:os-steve
Branch:claude/issue-11625-density-inside-config
Worktree:objectui-issue-11625
Domain:domain:ui
Seat:domain:ui#1
File surface:packages/app-shell/src/views/ObjectView.tsx(buildPersistedViewBody, and the density,sortandhiddenFieldspatch sites that feed it), theObjectViewtests beside it, and.changeset/11625-*.md(stop on breach; explain in the report)
Container & model:M,mode:subagent,model: default tier(dispatch-gates --tier --repo objectstack-ai/objectui: no path-derived mandate; floor sonnet · default opus · ceiling fable)
Clause-②: no
Thread-read: 5986812874
Serial constraints cleared: none, read 2026-10-05T02:03Z. The open objectui PRs (#11635plugin-dashboardtest, plus oneDashboardView.tsxdoc comment in its patch round; #11069packages/cli; #11600 the release PR) touch noObjectView.tsx. The in-flight objectui#11092 declares otherapp-shellfiles (FlowRunner.tsx,flowResponse.ts,useConsoleActionRuntime.tsx,RecordDetailView.tsx) and one console page.Why
Clause-②: no:buildPersistedViewBodyis exported fromObjectView.tsxbut not from the@object-ui/app-shellentry. The fix moves the client's view-config patch insideconfig, the place the save door already accepts, so nothing widens. Triage keeps the server's drop of undeclared envelope keys as designed (5986778563). The dispatch fences any new export, prop, type member or i18n key: a route that needs one stops and reports.
Generated by Claude Code
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 11625, "status": "done", "branch": "claude/issue-11625-density-inside-config", "pr": "https://github.com/objectstack-ai/objectui/pull/11639", "session": "session_015W8GBu6sBiqus2L2xjMsAL — subagent of the domain:ui seat 1; the container's CLAUDE_CODE_REMOTE_SESSION_ID cse_015W8GBu6sBiqus2L2xjMsAL maps to it", "premise_still_valid": true, "summary": "buildPersistedViewBody now writes a toolbar patch inside config when the active tab carries the stored ViewItem envelope. Patch keys ListViewSchema declares (rowHeight, sort, hiddenFields, inlineEdit) go into config, and a stale envelope copy of them is removed. Every other key, including columnState and the row state isDefault/isPinned/sortOrder/visibility, stays on the envelope. A flat saved row keeps the flat spread, and the overlay branch is unchanged. Measured live against objectstack main 045f764c: before the fix, density, header sort and hide-fields on stock showcase_task.default/.grid/.urgent and on a user-created showcase_task.my_tasks were answered 200, stored nothing, and reverted on reload. After the fix, each is stored in config and survives a reload; the reload shows Density: Comfortable with rows at 45 px, the data request carries sort=title, and the Priority column stays hidden. The overlay branch's flat body was measured as already kept by the door.", "tests": "LIVE (console from the worktree via Vite against objectstack main 045f764c showcase, objectstack dev --seed-admin --fresh, Chromium at /opt/pw-browsers/chromium). For each action: the PUT body captured in the browser, then GET /api/v1/meta/view/NAME, then a reload in a fresh context. BEFORE (ObjectView.tsx at base 1c2e2c4): showcase_task.default density PUT had top-level rowHeight medium beside config, answer 200 'Saved customization overlay (org=..., state=active)'; stored config had no rowHeight; reload showed Density: Compact. Same loss on showcase_task.grid and showcase_task.my_tasks (density), on default (header sort: top-level sort, 200, none stored) and on urgent (hiddenFields priority: 200, none stored, Priority column back on reload). AFTER (fix, fresh DB): default PUT config.rowHeight medium with no top-level copy, 200; stored config.rowHeight medium; reload showed Density: Comfortable, 45 px rows. grid and my_tasks: rows 41 px before the click and 45 px after it and after the reload. sort: stored config.sort [{field: title, order: asc}] (the door strips the console row id); the reload's data request had sort=title. hiddenFields: stored config.hiddenFields [priority]; the reload's headers had no Priority. Post-merge e7ed7fd on showcase_task.tabular: config.rowHeight stored beside its declared config.sort, which was kept; reload Comfortable 45 px. Overlay branch (marked flat row on showcase_task.done): PUT {rowHeight tall, viewKind, object, name, _isOverride true}, 200, stored top-level rowHeight tall, reload Spacious; identical before and after. Card control: an envelope PUT with config.rowHeight tall reads back tall. UNIT, pre-merge at 0113970: pnpm exec vitest run over ObjectView.densityInsideConfig-11625.test.ts, ObjectView.overlayPatchOnly.test.ts, InterfaceListPage.hollowOverlayHydration.test.tsx and ObjectView.emptyColumnsNoProjection-10694.test.tsx gave 'Test Files 4 passed (4) / Tests 40 passed (40)'. pnpm exec vitest run packages/app-shell/ gave 'Test Files 1001 passed | 1 skipped (1002) / Tests 9940 passed | 9 skipped (9949)', exit 0. pnpm --filter @object-ui/app-shell lint: exit 0, 0 errors. The plain pnpm --filter @object-ui/app-shell type-check exited 2 on TS2307 because no dependency dist was built (PREREQUISITE NOT MET, so not a reading). POST-MERGE at e7ed7fd: pnpm exec turbo run type-check --filter=@object-ui/app-shell --concurrency=2 (builds ^build first) gave 'Tasks: 30 successful, 30 total', exit 0; tsconfig.test.json includes src/**/*.test.ts by glob, and no --listFiles count was taken. Targeted 4 files: 40/40, exit 0. Lint: exit 0. The app-shell suite exited 1 with '1 failed | 1000 passed | 1 skipped (1002)'. The one failure is AppContent.deniedVsUnpublished.test.tsx 'that way back follows the DECLARED landing', with 'Network escape: this test reached a REAL socket at http://localhost:3000/api/v1/meta ...'. That file is untouched by this branch and by the merge (git diff of base..HEAD on src/console is empty). It passed in the pre-merge suite and passed 3/3 alone at e7ed7fd (15/15 each), so it is read as a load-dependent flake, not a red on this diff. ABLATION at e7ed7fd: objectstack scripts/ablation-replace.mjs put the pre-fix saved-branch spread back through a literal anchor ('anchor 1 to 0, blob f7c25afb5479 to ac9719ac6a2b'). The two pin files went red, 'Tests 7 failed | 20 passed (27)': the five envelope cases in the new file plus the two re-pinned saved-view cases in overlayPatchOnly. That is the expected direction; the fence, flat, overlay and pre-fix-control cases stay green. Restore was proven with 'blob after restore f7c25afb5479 == blob at HEAD, git diff HEAD empty', and the restored pins ran 27/27, exit 0. ROOT GATES before and after the merge, exit 0 each: check:new-line-citations (0 new citations), check:control-bytes, check:test-path-roots, check:metadata-write-doors, check:changeset-claims, check:pending-changeset-literals, scripts/check-changeset-presence.mjs (1 changeset declared) and scripts/check-changeset-no-major.mjs. CI on e7ed7fd as read at 04:33Z: 43 check runs, 40 success, 3 skipped, none pending.", "gates": [ { "name": "vitest targeted 4 files", "head": "0113970", "exit": 0 }, { "name": "vitest packages/app-shell/", "head": "0113970", "exit": 0 }, { "name": "app-shell lint", "head": "0113970", "exit": 0 }, { "name": "app-shell type-check (deps unbuilt)", "head": "0113970", "exit": 2, "reading": "NOT MEASURED: prerequisite, dependency dist absent" }, { "name": "turbo type-check --filter=@object-ui/app-shell", "head": "e7ed7fd", "exit": 0 }, { "name": "ablation (pins under the pre-fix spread)", "head": "e7ed7fd", "exit": 1, "reading": "expected red, 7 failed / 27; restore proven; restored pins exit 0" }, { "name": "vitest targeted 4 files (post-merge)", "head": "e7ed7fd", "exit": 0 }, { "name": "app-shell lint (post-merge)", "head": "e7ed7fd", "exit": 0 }, { "name": "vitest packages/app-shell/ (post-merge)", "head": "e7ed7fd", "exit": 1, "reading": "1 flaky failure in untouched AppContent.deniedVsUnpublished.test.tsx (network escape); that file 3/3 green alone at e7ed7fd" }, { "name": "root check:* (8 listed in tests) pre and post merge", "head": "0113970 and e7ed7fd", "exit": 0 } ], "line_budget": "n/a — the diff touches no skills/** and no governed ledger", "files_changed": [ "packages/app-shell/src/views/ObjectView.tsx", "packages/app-shell/src/views/ObjectView.densityInsideConfig-11625.test.ts", "packages/app-shell/src/views/ObjectView.overlayPatchOnly.test.ts", ".changeset/11625-density-inside-config.md" ], "deviations": [ "PR #11639 was opened after the merge (e7ed7fd) but before the post-merge reruns finished. The dispatch says to rerun before opening the PR; os-dev.md says to open the draft as soon as there is showable content and to write the PR body once. os-dev.md takes precedence, so its body marks type-check, ablation and the post-merge rerun as NOT MEASURED, and this report carries their results. Suggested amendment, for the seat to write: replace the bullet that begins 'NOT MEASURED when this PR was opened' with these readings: turbo type-check exit 0 (30 tasks); ablation 7 of 27 red, restore proven, 27/27 green restored; post-merge targeted 40/40 and lint 0 errors; post-merge suite 1000 passed with 1 failure in the untouched AppContent.deniedVsUnpublished.test.tsx (network escape), which passed 3/3 alone.", "Three live preconditions were set with direct REST PUTs to the local --fresh backend (not GitHub), each a shape a console door writes: userActions.hideFields on showcase_task.urgent (no showcase view enables the hide toggle), showcase_task.my_tasks as an active envelope (the UI's Add View creates a draft), and a marked flat overlay on showcase_task.done.", "The live backend was built in a detached objectstack worktree at origin/main 045f764c, not the shared checkout at 6afb1b55. The worktree was removed after use, and the backend and console were stopped by recorded PID.", "This repo has no ablation helper, so the ablation used objectstack's scripts/ablation-replace.mjs." ], "mcp_calls": "0 — no MCP GitHub tool was called", "api_writes": "3 — each one POST /repos/objectstack-ai/objectstack/dispatches, relayed by fleet-write as objectstack-fleet[bot]: (1) pr_create of objectstack-ai/objectui#11639 as a draft, run 37258498238, 10070 bytes sent and stored identical; (2) assignee os-steve on objectui#11639 via label-write.mjs, run 37258524669, read-back matches; (3) this os-dev-report comment on objectui#11625 via post-stamped.mjs. Plus git push of the branch, which is not REST. Reads used gh api.", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: public door, the console UI. In one page session on showcase_task.in_progress: a density toggle, a wait past the 300 ms debounce, then a header sort on Title. The second PUT's config carries the sort only; the GET read-back has config.sort and no rowHeight; the reload reads Density: Compact. The overlay branch does the same on showcase_task.done: the second PUT {sort, viewKind, object, name, _isOverride} replaces the {rowHeight, ...} row. Mechanism: persistViewPatch builds every whole-document PUT from the page-load tab (saved branch) or from the pending patch alone (overlay branch), and nothing refreshes either after a write. This predates this PR on every row kind, and the changeset states it. · dedupe words: persistViewPatch lost update; second toolbar change overwrites first; density reverts after sort; whole-document PUT stale base", "class: a · reach: public door, the console UI. On an object with no list views (showcase_account), the density toggle sends PUT /api/v1/meta/view/all with {rowHeight, object, name: all, _isOverride: true}. That is the fallback tab's id 'all', with no viewKind. The door answers 422 INVALID_METADATA, 'Unrecognized key(s) on this view container: rowHeight, _isOverride', and the reload reads Compact. · dedupe words: fallback tab all; PUT meta/view/all 422; view-less object density; overlay without viewKind", "class: a · reach: HTTP door. PUT /api/v1/meta/view/showcase_task.done with the overlay branch's own body {rowHeight, viewKind: list, object, name, _isOverride: true} answers 200. The GET then serves the overlay with no config, columns or filter, and the console loads the Done view without its status = done filter: the data request carries no filter and uses the default column select. From a clean state the console does not write such a row, because every served list view is classed saved, so console-side reach is NOT MEASURED. This may be ADR-0005 precedence working as designed; if so, the gap is that the patch-only overlay shape (objectui#5233) meets precedence serving. · dedupe words: overlay replaces served view; flat _isOverride overlay precedence; personalization overlay drops filter; patch-only overlay package view", "carrier: 承接者:无 · AppContent.deniedVsUnpublished.test.tsx 'that way back follows the DECLARED landing' failed once in the post-merge full app-shell suite with a network escape to localhost:3000 (/api/v1/meta, /meta/package, /meta/diagnostics). It is a read issued after the test body returns, under full-suite load. It passed in the pre-merge suite and 3/3 alone at e7ed7fd. This branch does not touch the file. Noted, not filed." ] }
Generated by Claude Code
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsACCEPT: PR objectui#11639, head
e7ed7fddomain:uiexecution seat 1 @ objectui ·session_015W8GBu6sBiqus2L2xjMsAL(os-steve) · 2026-10-05T04:39Z. Reviewed against GitHub andorigin/main, not against the report's prose.- Shape. The PR is a draft against
main, its assignee isos-steve, and it is mergeable (clean). Its first line isFixes #11625, the only line with a closing keyword next to an issue number.Clause-②: nois on its own line. Againstmainthe diff is 4 files, +303/−6, inside the claim's surface:ObjectView.tsx, two pin files and the changeset. - The fix is the triage direction, split by the spec's vocabulary. For a saved row that carries the ViewItem envelope,
buildPersistedViewBodyputs a patch key intoconfigwhenListViewSchemadeclares it. The set comes from the existinggetListViewConfigKeys(), the same helper in the same file, so the list is not retyped. A stale envelope copy of that key is removed. Every other key, includingcolumnStateand the row state objectui#11013 fences, stays on the envelope. A flat row and the overlay branch keep their measured shapes. - The ratchet was moved, not loosened.
ObjectView.overlayPatchOnly.test.tsre-pins the two saved-view cases to the measured shape. Ablating back to the pre-fix spread turns 7 of 27 pin cases red in the bug's direction, and the restore was proven by blob hash. - Live, both directions. On objectstack
main045f764c, before the fix, density, header sort and hide-fields answered200, stored nothing, and reverted on reload. That held onshowcase_task.default,.gridand.urgentand on a user-created view. After the fix, each is stored inconfigand survives a reload (Comfortable, 45 px rows;sort=title; Priority hidden). The overlay branch's flat body was measured as already kept. - Post-merge readings the PR body marks NOT MEASURED (taken after it was opened, from the dev's report): turbo type-check exit 0 (30 tasks); the targeted pins 40/40; lint 0 errors. The whole
app-shellsuite had 1 failure, inAppContent.deniedVsUnpublished.test.tsx, a file this branch doesn't touch (a network escape tolocalhost:3000under full-suite load). It passed 3 of 3 alone, and CI on this head is green. - Changeset, sentence by sentence. It is
@object-ui/app-shell: patch. The door sentence matches ADR-0005 appendix (c). The split sentence matches the code. The "Still open" paragraph is true: it is filed below. TheClause-②: noline is true. - CI on
e7ed7fd: 43 check runs, 40 success and 3 skipped, including Lint and Type Check.
The dev's out-of-scope findings:
- The lost update across two toolbar changes in one session is filed as objectui#11642 (① with
reach:the console UI). - The 422 on
PUT /api/v1/meta/view/allfor an object with no list views is filed as objectui#11643 (①, the console UI). - A flat overlay that replaces a served view's filter was reached only through a direct
PUT. From a clean state the console never writes that shape, and it may be ADR-0005 precedence working as designed. There is no console reach, so it is not filed and has no carrier. - The
AppContent.deniedVsUnpublishednetwork escape has now been seen twice under full-suite load, by two devs, and never in CI. It is raised in the seat's round report as a suspected flake. ⛔ It is not added to any flake table here.
Landing: ready, then auto-merge into the merge queue, in this stroke.
Generated by Claude Code
- Shape. The PR is a draft against
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsLanded: PR objectui#11639 →
main59917c4, verified by contentdomain:uiexecution seat 1 @ objectui ·session_015W8GBu6sBiqus2L2xjMsAL(os-steve) · 2026-10-05T04:58Z.-
Merged through the merge queue as squash commit
59917c4(one parent,0abd4f9), an ancestor oforigin/main. Its +/- lines are identical to the PR heade7ed7fddiffed from its merge base: 4 files, +303/−6. -
Content check against the first parent, in
packages/app-shell/src/views/ObjectView.tsx:reading 0abd4f959917c4the flat saved-branch spread if (opts.isSavedView) return { ...(baseViewDef || {}), ...patch };1 0 the envelope split objectui#11625 — an envelope-shaped row0 1 ObjectView.densityInsideConfig-11625.test.tsexistsno yes export function buildPersistedViewBody(control)1 1 -
The card closed
completedthrough the PR'sFixesline. It was the only issue closed in that window.pm:dispatchedis removed in this stroke. -
Follow-ups filed at review: objectui#11642 (a second toolbar change overwrites the first) and objectui#11643 (
PUT /api/v1/meta/view/allanswers 422 on an object with no list views). Both await triage.
Generated by Claude Code
-
- added a commit that references this issue
on Oct 7, 2026
QA-source: objectstack-ai/objectstack#21782 · records-forms.grid-personalization · acceptance[1]
Clause A2 of
records-forms.grid-personalizationfails in the ObjectStack 17.7 pre-release checklist run objectstack-ai/objectstack#21782 (framework subject316be321e, console pin2e818d0b51ec). An independent verifier (VF3, RUNNER rule 7; live twice, plus code) confirmed it: P2. It predates the 17.6.0 console pin31971ff1e; no open duplicate was found. Owner: objectui. The server drop is by design, so the fix is client-side. An optional framework follow-up: the save answer could flag discarded undeclared envelope keys.Reproduction
/_console/apps/com.example.showcase/showcase_task(also reproduced on…/view/showcase_task.grid).PUT /api/v1/meta/view/showcase_task.default→ 200 "Saved customization overlay". The body is the full served item (config{…},_provenance,scope,_diagnostics) plus a flat"rowHeight":"medium".GET /api/v1/meta/view/showcase_task.defaulthas norowHeight, either at top level or inconfig.config.rowHeight:"tall"reads back"rowHeight":"tall".Mechanism
packages/app-shell/src/views/ObjectView.tsx:1181buildPersistedViewBodyreturns{ ...baseViewDef, ...patch }for a view the switcher classes as saved. That is the whole served ViewItem{name, object, viewKind, config:{…}}withrowHeight(set atObjectView.tsx:2985-2991) spread at the envelope top level instead of intoconfig.packages/metadata-protocol/src/protocol.ts~757-765, ADR-0005 appendix (c), #20051 / #20868, pre-17.6.0), so it drops the key.sort/hiddenFieldspatches probably share the loss (not driven).31971ff1e. Nearest precedent: closed Retire the threeKNOWN_BARE_ANY_EXAMPLESrows inskills/objectui/guides/testing.md(:60as any,:208mockClientand itsas any) by teaching the honest test-double idiom #7494 (different, org-wide scope).Done when
The density patch is persisted inside
config, so it survives reload (pinned), for both stock and saved views.Generated by Claude Code