Repository navigation
fix(plugin-list): read userActions.editInline with the spec default, folding a stored inlineEdit into it (objectui#5144) - #12036
Conversation
…folding a stored inlineEdit into it (objectui#5144) The maintainer's B-fold. normalizeListViewSchema folds a boolean inlineEdit into userActions.editInline: the value carries over, and an explicit editInline wins. inlineEdit stays on the result, because ListView seeds the grid's edit mode from it and the toolbar toggle writes it. ListView's inlineEditOffered now reads editInline === true, the spec's .default(false). A grid view with neither key no longer offers the inline-edit toggle. A stored inlineEdit: true still offers it, and a stored inlineEdit: false reads off. The interim pin "an ABSENT editInline defers to the host channel" flips. New pins cover the fold's table in core, the toolbar's reading of the folded key, the toggle's write path, and the interface page's composed schema through the fold. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
…(objectui#5144) Two ListView.test.tsx cases exercise the toggle's mechanics on a view that declared neither userActions.editInline nor inlineEdit. They relied on the old absent default, which now reads off, so they declare the opt-in. The interface-page pin's opt-in case was labelled CONTROL, but it goes red with the fold removed like the others, so it is a positive case and is named as one. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
✅ 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
|
Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
…iew's inlineEdit keeps its precedence (objectui#5144) Triage's ruling E. The view's inlineEdit and userActions.editInline are the author's permission keys, and the fold reads the first as the second. The console's toolbar toggle persisted a user's edit mode into inlineEdit through persistViewPatch, so switching it off stored inlineEdit: false and the toggle was gone from the next load. ObjectView now wires onInlineEditChange to a callback that writes nothing. ListView keeps the edit mode for the session, seeded from the view's inlineEdit on each load. The callback stays wired because ListView offers the wide toolbar toggle only to a host that wires one. The seat's decision on the named layer. plugin-view's renderListView relay folded the node's and the host view's userActions but spread the named view's raw, so a host-layer inlineEdit outranked the named view's own for the offer. The named layer now goes through the same fold. Texts that described a persisted toggle are rewritten: ListView's Gap 2 docblock, the prop and mode comments, the fold docblock, the changeset (app-shell minor, plugin-view patch) and the plugin-view docs table. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
…nce the toggle stopped persisting (objectui#5144) The objectui#5233 ratchet's anti-vacuity floor counted five distinct keys written through persistViewPatch. Ruling E removed the inline-edit write, so the floor is four, and the ratchet now also states that inlineEdit is not written. The adapter still lists inlineEdit as an owned key, so an overlay an earlier console wrote is still read. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
…ers by name (objectui#5144) The fence's read census (objectViewHostSurface.test.tsx) pins the named-view members the renderListView relay reads, by name. Passing the whole named view to normalizeListViewSchema hid the userActions read from it. The fold is now handed the two members it reads into userActions on a named view, userActions and inlineEdit. A named view's strict record refuses the show* flags, so nothing else feeds that block. Same output. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
Fix round: ruling E and the named-layer fold. What this supersedes in the PR bodyHead Superseded sections of the body, and what replaces them:
Generated by Claude Code |
✅ 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
|
Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
…e-edit toggle persists inlineEdit (objectui#5144) Comment-only. Since ruling E the console's inline-edit toggle is session state, so three comments this PR made false are rewritten: - data-objectstack: the updateViewConfig production-caller note no longer lists inlineEdit among the persistViewPatch toggles. - data-objectstack: the VIEW_OVERLAY_OWNED_KEYS docblock says inlineEdit stays an owned key so overlays the old toggle wrote are still read, but no console call site writes it. The key list is unchanged. - plugin-list: ViewSettingsPopover's prop comment says the host decides what the callback does, and the console keeps it session-only. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude <noreply@anthropic.com>
✅ 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
|
Contract-tier review of record — PR objectui#12036 (objectui#5144) · PASS on head
|
Fixes #5144
Clause-②: no (narrowing)
The maintainer's B-fold (hold record 5328162879, released and graded by triage in 6075214383). The accepted input set is unchanged: a stored
inlineEditis still accepted, and now folds into the spec key. What narrows is the object-list toolbar's reading of a view that carries noeditInline: it was "defer to the host", and it is now off, the spec's.default(false).Landing waits for the owning seat's contract-tier review of record on this PR. This PR is a dispatched draft; it is not mine to mark ready or queue.
What changes
@object-ui/core,normalizeListViewSchema). A booleaninlineEditfolds intouserActions.editInline: the value carries over, and an explicituserActions.editInlinewins in both directions. It sits besideSHOW_FLAG_TO_USER_ACTIONand shares its fold block, the one-vocabulary pattern from Migrate the remaining ListView legacy vocabulary to spec-canonical keys, and audit ObjectView/DetailView (#2231 phases 4–5) #2890. A view with nothing to fold is still returned by reference.inlineEditis kept, not deleted. Everyshow*flag is deleted after it folds.inlineEditcannot be: it is the spec's ownListView.inlineEdit, andListViewseeds the grid's edit mode from it, which the toolbar toggle then flips and the console persists. Deleting it would open every stored inline-edit view out of edit mode. This is the departure the file already documents fordatatoobjectName("Every other fold deletes because the legacy key has one meaning and one home"), and the new docblockfoldsInlineEditIntoEditInlinesays so.@object-ui/plugin-list,ListView).inlineEditOfferednow readsuserActions.editInline === true, which was!== false. The "Gap 2" docblock is rewritten to say what is true now. The edit mode is still theinlineEditstate, seeded from the view'sinlineEdit.InterfaceListPageis not edited. ItsuserActions.editInline === truereading is not made redundant by the fold. It supplies the edit mode asinlineEdit, and the page forwards noeditInlineof its own, so the fold is what givesListViewthe page's off/on. Pinned below.content/docs/plugins/plugin-view.mdx, the page that describes a list view'sinlineEdit: a paragraph and a table under "On a host'srenderListView" for the spec default and the fold..changeset/5144-editinline-fold.md:@object-ui/plugin-listminor and@object-ui/coreminor. The rule followed is objectui AGENTS.md section 9, version policy: a breaking change in this repo is markedminor, nevermajor, with the breaking meaning written in the body. Core isminorbecause the fold changes the published function's output for every view that carriesinlineEdit, and both in-repo relays merge that output. The changeset names the remedy: declareuserActions.editInline: true, or keep the storedinlineEdit: true.H1: the fold, per stored shape (measured)
The toggle is offered only to a principal who may update the object, as before. Rows are pinned in
normalize-list-view.inlineEditFold-5144.test.ts(the fold) andListView.permissions.test.tsx(the toolbar).main: toggle / edit modeinlineEdit: trueinlineEdit: falseuserActions.editInline: trueuserActions.editInline: falseeditInline: false,inlineEdit: trueeditInline: true,inlineEdit: falseH1's last clause does not hold as hypothesised: two rows narrow, not one. A stored
inlineEdit: falsereads off too, which is what a value-preserving fold means. The next section says who writes that value.H2: the host channel (measured)
onInlineEditChange(next).@object-ui/app-shell'sObjectViewpersists it withpersistViewPatch(viewDef.id, viewDef, { inlineEdit: next }), which is debounced and does not re-feed the schema in the session. Pinned: switching off reportsfalseand leaves edit mode in the same interaction.userActionsfromnormalizeListViewSchema(listSchema)andnormalizeListViewSchema(viewDef), merged view over list, and passesinlineEdit: viewDef.inlineEdit ?? listSchema.inlineEdit. Both now carry the fold, with the same view-over-list precedence.ListViewfolds again; an explicit key is left alone.inlineEditis true still offers inline editing and opens in edit mode. Pinned.editInline. Switching it off storesinlineEdit: false. From the next load that view reads off and the toggle is gone. Nothing in the console UI writesuserActions.editInlineorinlineEditback:ViewConfigPanelhas no inline-edit control (measured by grep). WithuserActions.editInline: truedeclared, the toggle stays two-way and the storedinlineEditonly sets the mode. That is the remedy the changeset and the docs table state. Raised as an open question in the report, not changed here.H3: every reader after the fold
ListViewinlineEditOffereduserActions.editInline=== true(was!== false)ListViewedit-mode stateschema.inlineEditListViewgrideditableViewSettingsPopovershowInlineEditandinlineEditprops fromListViewinlineEditOfferedInterfaceListPageuserActions.editInline === true, asinlineEditListViewnow agrees with itObjectViewrelayuserActionsthrough the fold;inlineEditview over listeditInlineper layer, same precedenceObjectViewtoggle writerinlineEditObjectViewactiveViewUserActionssearch/filter/sortonlyObjectViewrenderListViewrelayuserActionsthrough the fold, named view's raw;inlineEditnamed firstObjectViewregistered grid pathinlineEditaseditableeditInline(pre-existing)StudioDesignSurfaceinlineEdit: trueinlineEditin the toolbar-owned config keys (write routing)Other
inlineEditkeys are different properties (field-level master-detail, record details, gantt, detail view). The fold only runs on list-view schemas.H4: first-load bytes
apps/console,CI=true pnpm exec vite build, under the verify lock,eagerGzipBytesfromdist/eager-closure.json:8f815f4fe: 3,238,404 B, 289 eager chunks;f9e4b5b5d: 3,238,492 B, 289 eager chunks;pnpm check:eager-closureon the head build: "Console eager closure is 3162.6 KB gzipped across 289 of 2474 chunks (budget: 3204.6 KB, headroom: 42.0 KB)". The budget script is untouched.Reverse leg
main's blobs ofnormalize-list-view.tsandListView.tsxchecked out under the new pins (tests at head). The mutation was proven on disk by blob hash, the restore wasgit checkout HEAD, and a trap fired on exit. Direction predicted before the run: 12 red.main's blobs:Tests 12 failed | 26 passed (38). Red: 5 in the core fold file (true row, false row, other toggles kept, same pass asshow*, spec-valid document), 3 inListView.permissions.test.tsx(absent reads off, compact entry absent reads off, storedinlineEdit: falsereads off), 4 in the interface-page pin.git hash-objectequal their HEAD blobs, andgit diff HEADis empty.Tests 38 passed (38).Fixture triage
ListView.permissions.test.tsx, the Grid toolbar inline-edit toggle is not gated oncan(object, 'update');userActions.editInlineis declared in spec but has no consumer #4647 gap-1 permission cases: they now declareuserActions.editInline: true. Without it, "a principal WITHOUT update loses it" would pass for every principal.ListView.test.tsx, the two toggle-mechanics cases: they declare the same opt-in. They relied on the old absent default. Both were found by the wide run and are green after the change.Gates, at
f9e4b5b5done-authority-per-exported-name-6273, all sixnormalize-list-viewsuites,ListView.permissions,ListView,ListView.userActionsCollision, and the interface-page pin:Test Files 12 passed (12),Tests 331 passed (331).normalize-list-viewsuite, everyListView*suite in plugin-list, every suite that referencesInterfaceListPage, and every suite that referencesnormalizeListViewSchema,inlineEditoreditInline, 170 files in three runs. Run A: 84/84 files, 1712 passed, 9 skipped. Run B: 43/43 files, 316 passed. Run C: 2 failures, the fixture triage above, then green on re-run (ListView.test.tsxand the interface pin: 152 passed).pnpm --filter @object-ui/core type-checkandpnpm --filter @object-ui/plugin-list type-check: exit 0, with the plugin-list closure built first.--listFilesshows both test projects compile the edited and new test files. The app-shell test project compiles the new interface pin with 0 errors, after the app-shell closure build.check-changeset-presence✅ "6 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)";check-changeset-no-major✅;check-changeset-claims✅;check-changeset-fixed✅;check-changeset-overwrite✅.pnpm check:new-line-citations: "0 new citation(s)".pnpm check:control-bytes✅.pnpm docs:check-links: "Links are valid across 17 scan roots".pnpm check:spec-symbols✅.check-vi-mock-specifiers/-inherit/-override-shape✅.check-test-path-roots✅.check:unreferenced-sources✅.check-doc-fence-languages,-component-types,-example-idsand-expression-carriage✅.pnpm exec eslinton the 6 touched source and test files: 0 errors, 6 files in its JSON output, none ignored. This is a narrowed run, and the narrowing is safe:eslint.config.jsenables no type-aware linting (noparserOptions.projectorprojectService), and no rule ineslint-rules/reads the filesystem, so this diff cannot move a verdict on an untouched file. Whole-repo lint is CI's.check-doc-snippet-typesandcheck-doc-example-typesexit 2, "PRECONDITION NOT MET", because they need a 34-package build. The docs change adds no fenced code for them to type.Acceptance notes
renderListViewrelay: a lower layer can outrank the named view. Measured with a throwaway probe, not committed. The relay spreadsnormalizeListViewSchema(node).userActions, then the host view's, then the named view'suserActionsRAW.inlineEdittakes the named view first. After the fold, a node's or host view'sinlineEditbecomes a foldededitInline, and that outranks a named view's owninlineEditfor the offer. Namedtruewith hostfalsereads off; namedfalsewith hosttruereads offered. No in-repo host reaches it: the console passesviewsand nolistViews, and Studio passes neither. The likely remedy is one line, folding the named layer like the other two. It is not made here, becausepackages/plugin-view/src/ObjectView.tsxis outside the claim's file surface.editInline(pre-existing onmain). It hands the named view'sinlineEdittoObjectGridaseditable, soeditInline: falsewithinlineEdit: trueis editable there and not onListView.editInline: truemeans two things (pre-existing). The interface page opens cells editable.ListViewoffers the toggle and opens out of edit mode unlessinlineEditis true.Generated by Claude Code