Skip to content

Commit 5330afd

Browse files
feat(plugin-list,app-shell): read an absent userActions.editInline as on, the v18 spec default (objectui#12086) (#12092)
Fixes #12086 Clause-②: no The consumer half of objectstack-ai/objectstack#22605 (the v18 default flip, landed as objectstack-ai/objectstack#22629). List views read an absent `userActions.editInline` as on (`!== false`): a list view is editable in place by default, under the permission gate that already exists, and `userActions: { editInline: false }` makes it read-only in place. The `inlineEdit` fold is unchanged, and so is the gate. ## What changed - **`ListView`** (`@object-ui/plugin-list`): `inlineEditOffered` returns `false` only for an explicit `editInline: false`; anything else falls through to the unchanged `listInlineEdit` affordance verdict (the object editable in place, and the principal's `update` grant). The Gap 2 docblock now states the v18 default and the opt-out. - **ADR-0047 interface page** (`@object-ui/app-shell`, `InterfaceListPage`): `inlineEdit: userActions.editInline !== false`, and the "default off" comment is corrected. - **Pins** (each kept load-bearing by a twin that differs in one input): - `ListView.permissions.test.tsx`: a view that declares nothing is offered the toggle for a principal with `update` and not for one without it (same schema, permission differs); with no `PermissionProvider` it is offered (fail-open kept); an absent `editInline` reads on and the grid opens out of edit mode; the compact popover offers the entry for an absent key, and its control withholds it for `editInline: false`; `editInline: false` withholds the toggle from a fully permitted principal. The stored `inlineEdit: true` case (offered, opens in edit mode) is unchanged and still green. - `InterfaceListPage.editInlineFold-5144.test.tsx`: an absent page `editInline`, and a page with no `userActions` block, now read on on both sides of the fold; an explicit `editInline: false` is the opt-out. - `normalize-list-view.inlineEditFold-5144.test.ts`: the fold's table is unchanged (the fold applies no default); its docblock now says which reader the flip changes. - **Docs**: `content/docs/plugins/plugin-view.mdx`, the "neither key" row of the inline-editing table now reads "offered | no" (opens out of edit mode), and the prose says on by default, with `editInline: false` as the opt-out. - **Changeset**: `.changeset/12086-edit-inline-default-on.md`, `minor` for `@object-ui/plugin-list` and `@object-ui/app-shell`, with the behaviour change in the prose. ## Measured before writing (the PM's three mechanism assumptions) 1. **The spec half is on objectstack `origin/main`.** `packages/spec/src/ui/view.zod.ts` declares `editInline: z.boolean().default(true)` (read with `git show origin/main:`; `git merge-base --is-ancestor f53f14bb36 origin/main` answered exit 0, which self-certifies). The installed `@objectstack/spec` here is 17.7.0, whose dist still declares `editInline: z.boolean().default(false)`. **Does any objectui code read the installed default?** No runtime reader does. The quoted-name grep for `UserActionsConfigSchema` and for `ListViewSchema` / `ViewSchema` `.parse(` / `.safeParse(` across `packages/*/src` and `apps/` finds the parse calls only in tests and in two `FormViewSchema` reads (positive control: the same grep finds those two). The one test that parses the installed defaults, `normalize-list-view.foldOutputAuthorable-5435.test.ts` ("the ON/OFF default asymmetry"), asserts `group` / `hideFields` / `rowColor` only, not `editInline`, so it does not fight the new read. One display-only reader exists: the metadata-admin View inspector derives its JSON Schema from the installed `ViewSchema` with `z.toJSONSchema`. It writes nothing, and its boolean switch renders `!!value` and ignores a schema default. See Acceptance notes. 2. **The two sites are the only absent-as-off readers.** Every `editInline` reader in `packages/*/src` outside tests, from the quoted exact-name grep (positive control: the same grep hits the known `ListView.tsx` and `InterfaceListPage.tsx` sites): `ListView` `inlineEditOffered` (was `!== true`); `InterfaceListPage` (was `=== true`); the `normalizeListViewSchema` fold (`typeof === 'boolean'`, no default); and the `@object-ui/plugin-view` `ObjectView` `userActions` merge (a spread of three folded layers, no default). The other hits are comments, the `detail.editInline*` i18n keys (a different namespace), and the grid's own `userActions` comments. The assumption holds. The `inlineEdit` readers carry the edit MODE (`!!schema.inlineEdit`, the named-view relay, `ObjectGrid`'s `editable`); none reads an absent `editInline` as off. 3. **The cloud window (see below).** No example app or fixture in this repo declares nothing and relies on the old default for a read-only list through a `ListView`-rendered path. The quoted grep for `editInline`, `inlineEdit` and `userActions` over `apps/` and `examples/` hits 0 (positive control: the same grep finds `listViews` / `object-grid` / `object-view` there). The list views that do exist there (`examples/byo-backend-console`'s `object-view` with a named `grid` view, the schema-catalog `object-view` / `object-grid` entries, `apps/console`'s dev `object-grid`) render through `ObjectGrid` directly, with no host `renderListView`. On that path `ObjectGrid`'s `editable` comes from the view's `inlineEdit` and never from `editInline`, so this change does not move them. ## The cloud window (named, as triage directs) Cloud consumes objectui `main` on its frozen v17 framework. There, a list view that declares neither `userActions.editInline` nor `inlineEdit` now offers the inline-edit toggle on the console object page. An ADR-0047 interface page that declares nothing now opens its grid editable in place. Both happen before cloud's spec says the default is on. Both stay under the permission gate: a principal without `update` on the object, or an object that is not editable in place, gets neither. This is the maintainer's chosen posture, so no hold is asked for. The cloud apps are not in this container, so whether any cloud view relied on the v17 default for a read-only list was **not measured here**. The spec card's own measurement names one cloud interface page that already declares `editInline: false` explicitly. That is a reading carried from objectstack-ai/objectstack#22605, not re-taken here. ## One mechanism note for the seat (the interface page) The interface page wires no inline-edit toggle: no `onInlineEditChange`, no `compactToolbar`. Its `userActions.editInline` therefore reaches `ListView` as the grid's edit MODE (`inlineEdit`), which the fold also turns into the "offered" key. So with this flip, an interface page that declares nothing does not "gain the toggle". It opens its grid editable in place, for a principal the gate admits. That is what the card's `!== false` orders for this site, and it is what the spec's new `describe` says ("a user who may update the object edits a cell in place"). A literal toggle on this page would be a new host wiring and is not done here. It is raised in the report. ## File surface Within the claim: `ListView.tsx` (`inlineEditOffered` and its docblock), `InterfaceListPage.tsx` (the read and its comment), `ListView.permissions.test.tsx`, `normalize-list-view.inlineEditFold-5144.test.ts`, the interface-page tests (`InterfaceListPage.editInlineFold-5144.test.tsx`, and the `inlineEdit` reason string in `InterfaceListPage.relayCensus-11572.test.ts`), `plugin-view.mdx`, and one changeset. **In-surface consequences: comments and test titles this flip made false.** These are text-only, with no code or assertion change, and they sit outside the claim's file list: - `packages/core/src/utils/normalize-list-view.ts`: two docblocks said the spec default is `.default(false)` and that `ListView` reads an absent pair as off. - `packages/app-shell/src/views/ObjectView.tsx`: the `keepInlineEditModeForTheSession` docblock quoted the v17 `describe` ("the list is read-only unless the author opts in"). - `packages/app-shell/src/views/ObjectView.inlineEditSessionOnly-5144.test.tsx`: one test title said "reads off by the spec default". Its assertion (the fold leaves both keys absent) is unchanged. - `packages/plugin-list/src/__tests__/ListView.test.tsx`: two comments cited the spec's `.default(false)`. - `packages/plugin-view/src/__tests__/ObjectView.namedViewEditInlineFold-5144.test.tsx`: the docblock said `ListView` offers inline editing "only where that reads `true`". Nothing is added to a package entry: no prop, export, type member or locale key. ## Verification All runs below are on this branch. Heavy runs went through the shared verify lock, and each line quotes that run's own `VERDICT command-exit` line. Test runs use the repo-root form `pnpm exec vitest run --maxWorkers=2 PATHS`. - **Targeted pins, on the final head `9cd778d1d`** (after merging `origin/main` `de302c73`): the four touched pin files, the five other `InterfaceListPage*` tests, `ObjectView.inlineEditSessionOnly-5144`, the two `normalize-list-view` fold tests and the plugin-view named-view fold test. Result: `Test Files 13 passed (13)`, `Tests 264 passed (264)`, `VERDICT command-exit 0`. The same 13 files gave the same counts on `efa9e08f1`, before the merge. - **`@object-ui/plugin-list`, whole package, on `efa9e08f1`**, plus the four app-shell `ObjectView` tests that render the real `ListView` with role-based button queries or import the interface page (`kanbanLane-8193`, `storedOptionsBag-10380`, `listUrlState-11860`, `viewWriteRefusal-11583`). Result: `Test Files 129 passed (129)`, `Tests 1444 passed (1444)`, `VERDICT command-exit 0`. - **Ablation (a one-time proof; no test file kept).** Each leg was run from the committed head through `ablation-replace.mjs`, whose anchor hit x1 and went to x0, and whose blob changed. Each was restored to the HEAD blob with `git diff HEAD` empty, so the restore was proven on disk. - Leg A put the v17 read back into `inlineEditOffered` (`?.editInline !== true`). `ListView.permissions.test.tsx` went `4 failed | 19 passed (23)`. The four red cases are exactly the predicted ones: offered with `update` on a view that declares nothing; offered with no `PermissionProvider`; an absent `editInline` reads on; the compact entry is offered for an absent key. "WITHOUT update loses it" stays green under both reads, as expected. - Leg B put `inlineEdit: userActions.editInline === true` back on the interface page. `InterfaceListPage.editInlineFold-5144.test.tsx` went `2 failed | 2 passed (4)`: the two absent cases. - **Type-check** (`pnpm --filter PKG type-check`, which is `tsc --noEmit && tsc -p tsconfig.test.json`), after building the dependency closure with `turbo run build --filter=PKG^... --concurrency=2` (`28 successful, 28 total`). `@object-ui/core`, `@object-ui/plugin-list`, `@object-ui/plugin-view` and `@object-ui/app-shell` all exit 0 with 0 `error TS` on `efa9e08f1`. `@object-ui/app-shell` exits 0 again on `9cd778d1d`, since main's two merged PRs touched app-shell source. - **eslint on the 11 touched `.ts` / `.tsx` files** (`--format json`, counted from its output): 0 errors. Every file's error/warning count is identical to its base-blob count (lint run over `git show 023f00d:FILE` via `--stdin`). The repo's `eslint.config.js` sets no `parserOptions` / `projectService`, so linting is not type-aware, and this diff cannot move a verdict on an untouched file. - **Gates, re-run on `9cd778d1d`:** `check:new-line-citations` (`0 new citation(s)`), `check:control-bytes`, `check-changeset-presence`, `changeset:check` (no `major`), `check-changeset-overwrite`, `check:changeset-claims`, `check:pending-changeset-literals`, `check:doc-fences`, `check:doc-types` and `check-doc-links`. All exit 0. The four `vi.mock` / test-path gates exit 0 on `efa9e08f1`. - **NOT MEASURED: `check:doc-snippets` and `check:doc-examples`.** Reason: exit 2, PREREQUISITE NOT MET. Both need a scoped build of more packages than this run built (`app-shell`, `cli`, `plugin-ai` … unbuilt). Their input is unchanged by this diff, measured instead. The fenced blocks of `plugin-view.mdx` extracted at base and at head are byte-identical (md5 `3fd26d2a…` both, 18 blocks). The diff touches 0 lines carrying `@example` or a fence in `packages/*/src`. Control: `ListView.tsx` carries one `@example`, which is untouched. - **Declared narrowing: `@object-ui/app-shell`'s full suite was not run locally**; CI runs it. The local app-shell set is every test that imports `InterfaceListPage`, the session-only toggle pin, and the `ObjectView` tests above. This is a declared narrowing, not a proof that nothing else in app-shell observes the toggle. ## Acceptance notes Observations only. Not filed, and not changed here. - **Metadata-admin View inspector shows default-true booleans as off.** `SchemaForm`'s boolean branch renders `checked={!!value}` and ignores the JSON Schema `default`. So in the View inspector, an absent `userActions.search` / `sort` / `filter` (default `true`) already shows OFF while the toolbar reads it ON. `userActions.editInline` joins that set once objectui installs a spec 18 build. The installed 17.x schema still says `default: false`, so the switch shows off either way, and after this PR the list reads on. This was read from source; no public door was exercised, so it is not filed. Carrier: none. - **The direct `ObjectGrid` path does not read `editInline`.** On an authored `object-view` with a named `grid` view and no host `renderListView`, `ObjectGrid`'s `editable` is the named view's `inlineEdit` (or `table.editable`). `userActions.editInline` is not read there. So `editInline: false` does not withhold edit mode from a named view that also says `inlineEdit: true`, and a view with neither key has no toggle to enter edit mode. This predates and is unchanged by this PR. No named producer declares both keys on such a view, so it is not filed. Carrier: none. - **The pending objectui#5144 changeset — corrected in this PR** (patch round, the seat's answer B; written into this body by the seat, `domain:ui` seat 3). `.changeset/5144-editinline-fold.md` is unreleased, and in net it stated the opposite default to this release. This PR's change made it false, so it is corrected here. Its frontmatter is byte-identical (same bump declarations). Before → after: 1. "List views read `userActions.editInline` with the spec's default, off." → "… with the spec's default, on since objectstack-ai/objectstack#22605 (`editInline: z.boolean().default(true)`), and `editInline: false` is the opt-out." 2. "**Breaking for a view that relied on the old default.**" → "**The spec's default, read as declared.**" 3. "The spec declares `userActions.editInline` with `.default(false)`: the list is read-only unless the author opts in." → "… with `.default(true)` (objectstack-ai/objectstack#22605): a list view is editable in place by default, under the permission gate, and `editInline: false` is the opt-out." 4. "The interface page already read it that way." → deleted. The paragraph now closes with "The flip of the default, and the interface page that reads it, are the objectui#12086 entry of this release." 5. "The object-list toolbar did not: it read …" → "The object-list toolbar used to read …". Only the lead-in changed, because it leaned on the deleted sentence 4. 6. "A grid view that declares neither … no longer offers the inline-edit toggle … and never opens in edit mode." → "… offers the inline-edit toggle, on the wide toolbar and in the compact settings popover, and opens out of edit mode." 7. "- a view with neither key reads off." → "- a view with neither key reads on, the spec's default." 8. "**What to do.** A view that should offer inline editing declares `userActions.editInline: true`. The toggle then stays offered whatever the user does with it." → "**What to do.** A list whose records are read-only by nature declares `userActions.editInline: false`. A view that declares `userActions.editInline: true` keeps the toggle offered whatever the user does with it." Unchanged: the other fold bullets, the session-only toggle, the named-view precedence, the two costs and the no-export line. This PR's own `.changeset/12086-edit-inline-default-on.md` was corrected in the same round, because its quote of the deleted 5144 sentence became false. It now says the previous release (17.7.0) also offered the toggle for an undeclared view, measured from the parent of objectui#12036, and the interface-page heading carries the behaviour change. --- _Generated by [Claude Code](https://claude.ai/code/session_01B1gHb9baeX7oioD5sHVm7z)_ --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: objectstack-fleet[bot] <332303061+objectstack-fleet[bot]@users.noreply.github.com>
1 parent 029bdaf commit 5330afd

14 files changed

Lines changed: 163 additions & 117 deletions
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@object-ui/plugin-list': minor
3+
'@object-ui/app-shell': minor
4+
---
5+
6+
List views read an absent `userActions.editInline` as on, the v18 protocol default (objectui#12086, the consumer half of objectstack#22605). A list view is editable in place by default, under the permission gate that already exists, and `userActions: { editInline: false }` makes it read-only in place.
7+
8+
**A list view that declares neither key.** The maintainer's v18 ruling flipped the spec's `ListViewSchema.userActions.editInline` from `.default(false)` to `.default(true)`. `ListView` (`@object-ui/plugin-list`) reads an absent `editInline` as on (`!== false`). A grid view that declares neither `userActions.editInline` nor `inlineEdit` offers the inline-edit toggle, on the wide toolbar and in the compact settings popover, and opens out of edit mode. The previous release offered it there too, reading an absent key as "defer to the host"; objectui#5144 read it off on unreleased `main` only, and its entry in this release states the default as on. The fold objectui#5144 added is unchanged.
9+
10+
**Behaviour change: the ADR-0047 interface page (`@object-ui/app-shell`).** The page reads its config's `userActions.editInline` with the same default and hands it to `ListView` as the grid's edit mode, because the page wires no inline-edit toggle. A page that declares nothing now opens its grid editable in place; `editInline: false` keeps it read-only.
11+
12+
**Unchanged.**
13+
14+
- The permission gate. The toggle, the compact entry and the edit mode are offered only where the object is editable in place and the current principal holds `update` on it. A principal without the grant sees no toggle.
15+
- The `inlineEdit` fold (objectui#5144): `inlineEdit: true` reads on and opens the grid in edit mode, `inlineEdit: false` reads off, and an explicit `editInline` wins either way.
16+
- The console toggle stays session-only (objectui#5144, ruling E).
17+
18+
**What to do.** A list whose records are read-only by nature (a log, an audit trail, a history, a roll-up) declares `userActions: { editInline: false }`. A view or overlay where an earlier console stored `inlineEdit: false` still reads off; declaring `userActions.editInline: true` brings the toggle back.
19+
20+
Nothing is added to a package entry: no export, prop, type member or language-pack key. (The bump is `minor` by this repo's release model: objectui's major follows the `@objectstack` family major, and its own behaviour changes ship as `minor` with the semantics stated here.)

‎.changeset/5144-editinline-fold.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,24 @@
55
'@object-ui/plugin-view': patch
66
---
77

8-
List views read `userActions.editInline` with the spec's default, off. A view's `inlineEdit` folds into it, and the console's inline-edit toggle no longer writes to the view (objectui#5144).
8+
List views read `userActions.editInline` with the spec's default, on since objectstack-ai/objectstack#22605 (`editInline: z.boolean().default(true)`), and `editInline: false` is the opt-out. A view's `inlineEdit` folds into it, and the console's inline-edit toggle no longer writes to the view (objectui#5144).
99

10-
**Breaking for a view that relied on the old default.** The spec declares `userActions.editInline` with `.default(false)`: the list is read-only unless the author opts in. The interface page already read it that way. The object-list toolbar did not: it read an absent `editInline` as "defer to the host", so every console grid offered the inline-edit toggle unless a view said `editInline: false`. The toolbar now reads it as the spec does. A grid view that declares neither `userActions.editInline` nor `inlineEdit` no longer offers the inline-edit toggle, on the wide toolbar or in the compact settings popover, and never opens in edit mode.
10+
**The spec's default, read as declared.** The spec declares `userActions.editInline` with `.default(true)` (objectstack-ai/objectstack#22605): a list view is editable in place by default, under the permission gate, and `editInline: false` is the opt-out. The object-list toolbar used to read an absent `editInline` as "defer to the host", so every console grid offered the inline-edit toggle unless a view said `editInline: false`. The toolbar now reads it as the spec does. A grid view that declares neither `userActions.editInline` nor `inlineEdit` offers the inline-edit toggle, on the wide toolbar and in the compact settings popover, and opens out of edit mode. The flip of the default, and the interface page that reads it, are the objectui#12086 entry of this release.
1111

1212
**The fold.** `normalizeListViewSchema` (`@object-ui/core`) now folds a boolean `inlineEdit` into `userActions.editInline`, the way it already folds the `show*` flags into the other toggles:
1313

1414
- `inlineEdit: true` reads as `editInline: true`: the toggle is offered and the grid opens in edit mode;
1515
- `inlineEdit: false` reads as `editInline: false`: no toggle;
1616
- an explicit `userActions.editInline` wins over `inlineEdit`, in both directions;
17-
- a view with neither key reads off.
17+
- a view with neither key reads on, the spec's default.
1818

1919
`inlineEdit` stays on the folded view, because `ListView` opens the grid in edit mode from it. Nothing migrates stored views.
2020

2121
**The console toggle is session-only (`@object-ui/app-shell`).** Both keys are the author's permission. The console's toolbar toggle used to store a user's edit mode in the view's `inlineEdit`; after the fold, switching it off would have taken the toggle away for good. It now writes nothing. The toggle switches edit mode for the session, and each load starts from the view's own `inlineEdit`. `ListView` still reports the toggle through `onInlineEditChange`.
2222

2323
**A named view's `inlineEdit` keeps its precedence (`@object-ui/plugin-view`).** On a host's `renderListView`, `ObjectView` merges `userActions` from the node, the host's `views` entry and the active named view, the named view last. The named view's `userActions` now go through the same fold as the other two. So a named view's `inlineEdit` decides whether inline editing is offered ahead of a host entry's, as it already decided the edit mode.
2424

25-
**What to do.** A view that should offer inline editing declares `userActions.editInline: true`. The toggle then stays offered whatever the user does with it. Two costs come with the session-only toggle:
25+
**What to do.** A list whose records are read-only by nature declares `userActions.editInline: false`. A view that declares `userActions.editInline: true` keeps the toggle offered whatever the user does with it. Two costs come with the session-only toggle:
2626

2727
- the edit mode is not remembered across loads;
2828
- a view, or a personalization overlay, where the old toggle stored `inlineEdit: false` still reads off. That is existing data, and it is not migrated. Declaring `userActions.editInline: true` on the view brings the toggle back.

‎content/docs/plugins/plugin-view.mdx‎

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -518,16 +518,18 @@ node takes the value from the active named view first, ahead of the host's
518518
`views` entry (objectui#10758).
519519
`src/__tests__/ObjectView.namedViewProtocolKeys-8980.test.tsx` pins each family.
520520

521-
**Inline editing on that node is opt-in.** Both keys below are the author's
522-
permission. `ListView` reads the node's `userActions.editInline` with the
523-
spec's default, off, so a view that declares neither key offers no inline
524-
editing (objectui#5144). When the node carries no `editInline` of its own, its
525-
`inlineEdit` stands in for it. The named view's keys win over the host
526-
`views` entry's, as for every member above:
521+
**Inline editing on that node is on by default.** Both keys below are the
522+
author's permission. `ListView` reads the node's `userActions.editInline` with
523+
the spec's default, on since the v18 ruling (objectstack#22605), so a view that
524+
declares neither key offers the inline-edit toggle (objectui#12086), and
525+
`userActions.editInline: false` is how a view withdraws it. When the node
526+
carries no `editInline` of its own, its `inlineEdit` stands in for it
527+
(objectui#5144). The named view's keys win over the host `views` entry's, as
528+
for every member above:
527529

528530
| The view declares | Inline-edit toggle | Grid opens in edit mode |
529531
| --- | --- | --- |
530-
| neither key | not offered | no |
532+
| neither key | offered | no |
531533
| `inlineEdit: true` | offered | yes |
532534
| `inlineEdit: false` | not offered | no |
533535
| `userActions.editInline: true` | offered | only with `inlineEdit: true` |
@@ -537,9 +539,11 @@ The toggle switches the edit mode for the session and reports it through
537539
`ListView`'s `onInlineEditChange`. The console stores nothing for it: each load
538540
opens in the mode the view's `inlineEdit` declares, so a user's choice is not
539541
remembered across loads. A view where an earlier console stored
540-
`inlineEdit: false` still reads off; that data is not migrated. To offer inline
541-
editing, declare `userActions.editInline: true`. Neither key opens editing past
542-
the object's own editability or the user's permission to update the object.
542+
`inlineEdit: false` still reads off; that data is not migrated, and declaring
543+
`userActions.editInline: true` brings the toggle back. A list whose records are
544+
read-only by nature declares `userActions.editInline: false`. Neither key opens
545+
editing past the object's own editability or the user's permission to update
546+
the object.
543547

544548
### Update
545549

‎packages/app-shell/src/views/InterfaceListPage.editInlineFold-5144.test.tsx‎

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
22

33
/**
4-
* objectui#5144, the interface-page half.
4+
* objectui#5144, the interface-page half, with the v18 default (objectui#12086).
55
*
66
* An ADR-0047 interface page reads the page config's `userActions.editInline`
7-
* as `=== true` and hands it to `ListView` as the view's `inlineEdit`. It does
8-
* not forward `editInline` itself, so the `userActions` block it builds carries
9-
* no `editInline` key. Before objectui#5144, `ListView` read that absent key as
10-
* "defer to the host channel" and offered inline editing on this page's compact
11-
* toolbar. The page itself read the absent case as off.
7+
* with the protocol's default, on (`!== false`, objectstack#22605), and hands
8+
* it to `ListView` as the view's `inlineEdit`. It does not forward `editInline`
9+
* itself, so the `userActions` block it builds carries no `editInline` key. The
10+
* page wires no inline-edit toggle, so that `inlineEdit` is the grid's edit
11+
* mode: a page that declares nothing opens its grid editable in place, and
12+
* `editInline: false` is the opt-out.
1213
*
13-
* After the B-fold, `ListView` reads what this page composes through
14-
* `normalizeListViewSchema`, which folds the `inlineEdit` the page sets into
15-
* `userActions.editInline`. So the two readings agree. This file pins it by
16-
* capturing the schema the page hands `ListView` and running it through the
17-
* real fold, which is the input `ListView`'s `inlineEditOffered` reads. What
18-
* `ListView` does with `editInline` is pinned in `@object-ui/plugin-list`'s
19-
* `ListView.permissions.test.tsx`.
14+
* `ListView` reads what this page composes through `normalizeListViewSchema`,
15+
* which folds the `inlineEdit` the page sets into `userActions.editInline`. So
16+
* the two readings agree. This file pins it by capturing the schema the page
17+
* hands `ListView` and running it through the real fold, which is the input
18+
* `ListView`'s `inlineEditOffered` reads. What `ListView` does with
19+
* `editInline`, the permission gate included, is pinned in
20+
* `@object-ui/plugin-list`'s `ListView.permissions.test.tsx`.
2021
*/
2122

2223
import { describe, it, expect, vi, beforeEach } from 'vitest';
@@ -104,29 +105,29 @@ describe('InterfaceListPage: editInline reads the same as ListView after the fol
104105
testObjects = [];
105106
});
106107

107-
it('an ABSENT page editInline reads off on both: no edit mode, and `editInline` folds to false', async () => {
108+
it('an ABSENT page editInline reads on, on both: edit mode, and `editInline` folds to true (objectui#12086)', async () => {
108109
const schema = await composedSchema({ search: true });
109-
// The page's own reading (unchanged): `=== true`.
110-
expect(schema.inlineEdit).toBe(false);
110+
// The page's own reading: the protocol default, `!== false`.
111+
expect(schema.inlineEdit).toBe(true);
111112
// The page forwards no `editInline` of its own…
112113
expect(schema.userActions).not.toHaveProperty('editInline');
113-
// …so `ListView` reads the folded one, which is off.
114-
expect(editInlineRead(schema)).toBe(false);
114+
// …so `ListView` reads the folded one, which is on.
115+
expect(editInlineRead(schema)).toBe(true);
115116
});
116117

117-
it('a page with no `userActions` block at all reads off the same way', async () => {
118+
it('a page with no `userActions` block at all reads on the same way', async () => {
118119
const schema = await composedSchema(undefined);
119-
expect(schema.inlineEdit).toBe(false);
120-
expect(editInlineRead(schema)).toBe(false);
120+
expect(schema.inlineEdit).toBe(true);
121+
expect(editInlineRead(schema)).toBe(true);
121122
});
122123

123-
it('a page that opts in reads on, on both', async () => {
124+
it('an explicit page `editInline: true` reads on, on both', async () => {
124125
const schema = await composedSchema({ editInline: true });
125126
expect(schema.inlineEdit).toBe(true);
126127
expect(editInlineRead(schema)).toBe(true);
127128
});
128129

129-
it('an explicit page `editInline: false` stays off', async () => {
130+
it('an explicit page `editInline: false` is the opt-out: off on both', async () => {
130131
const schema = await composedSchema({ editInline: false });
131132
expect(schema.inlineEdit).toBe(false);
132133
expect(editInlineRead(schema)).toBe(false);

‎packages/app-shell/src/views/InterfaceListPage.relayCensus-11572.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ const NOT_RELAYED: Record<string, NotRelayed> = {
499499
showRecordCount: { kind: 'page-owned', from: 'showRecordCount', reason: "The page config's record-count toggle, page presentation policy; the referenced view's toggle does not stand in for it." },
500500
allowPrinting: { kind: 'page-owned', from: 'allowPrinting', reason: "The page config's \"Allow users to print the page\". It went unwritten until objectui#11572, so the declared toggle drew no print button; the page's value only, like its siblings." },
501501
userActions: { kind: 'page-owned', from: 'userActions', reason: "The page config's toolbar toggles, with interface-mode defaults (closed unless opted in, #2890). The view's toolbar vocabulary is the object page's policy, not this page's." },
502-
inlineEdit: { kind: 'page-owned', from: 'userActions', reason: "Read from the page's `userActions.editInline` (default off). Whether this page's cells are editable is the page author's decision, not the referenced view's." },
502+
inlineEdit: { kind: 'page-owned', from: 'userActions', reason: "Read from the page's `userActions.editInline` (default on since objectstack#22605; `editInline: false` opts out). Whether this page's cells are editable is the page author's decision, not the referenced view's." },
503503
navigation: { kind: 'page-prop', from: 'recordAction', prop: 'onRowClick', reason: "The page config's `recordAction` (drawer / page / modal / none) decides how a record opens, wired through `onRowClick` to this page's own overlay. The view's `navigation` would be a second, competing answer." },
504504

505505
// ── The page's own binding and text ──────────────────────────────────────

‎packages/app-shell/src/views/InterfaceListPage.tsx‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -565,10 +565,15 @@ export function InterfaceListPage({ page, className, onConfigChange, reserveEdit
565565
rowColor: userActions.rowColor === true,
566566
},
567567
allowExport: false,
568-
// Inline record editing is a page-authored property: a list block opts in
569-
// via `userActions.editInline` (default off). When on, clicking a cell
570-
// edits it with the dedicated field widgets, same as the object views.
571-
inlineEdit: userActions.editInline === true,
568+
// Inline record editing is a page-authored property read from
569+
// `userActions.editInline`, on by default (the v18 protocol default,
570+
// objectstack#22605): a page opts out with `editInline: false`. This page
571+
// wires no inline-edit toggle, so the value is the grid's edit mode
572+
// itself. When on, a cell edits in place with the dedicated field
573+
// widgets, same as the object views, and only where `ListView`'s
574+
// permission gate admits the principal (the object editable in place, and
575+
// the `update` grant).
576+
inlineEdit: userActions.editInline !== false,
572577
};
573578
// eslint-disable-next-line react-hooks/exhaustive-deps
574579
}, [objectDefName, viewDefJson, cfg, orgAttribution]);

‎packages/app-shell/src/views/ObjectView.inlineEditSessionOnly-5144.test.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ describe('objectui#5144 (ruling E) — the console inline-edit toggle writes not
367367
expect(folded()).toEqual({ editInline: false, inlineEdit: false });
368368
});
369369

370-
it('control: the same served view with no overlay declares nothing, and reads off by the spec default', async () => {
370+
it('control: the same served view with no overlay declares nothing, which `ListView` reads on by the v18 spec default (objectui#12086)', async () => {
371371
const { meta } = makeStore([]);
372372
await openPage(meta, PLAIN_OBJECT, 'all');
373373
expect(folded()).toEqual({ editInline: undefined, inlineEdit: undefined });

‎packages/app-shell/src/views/ObjectView.tsx‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,21 +1523,23 @@ export interface ConsoleObjectViewProps {
15231523
* nothing (objectui#5144, triage's ruling E).
15241524
*
15251525
* The view's `inlineEdit` and `userActions.editInline` are the AUTHOR's
1526-
* permission keys. The spec says so for both ("the list is read-only unless
1527-
* the author opts in"), and `normalizeListViewSchema` folds the first into the
1528-
* second. This toggle used to persist a USER's edit mode into `inlineEdit`
1529-
* through `persistViewPatch`. After the fold, switching it off stored
1530-
* `inlineEdit: false`, which reads as "not offered", so the toggle was gone
1531-
* from the next load and nothing in the console could bring it back.
1526+
* permission keys, and `normalizeListViewSchema` folds the first into the
1527+
* second. `editInline` is on by default since the v18 ruling (objectstack#22605,
1528+
* objectui#12086), so a view that declares neither key offers the toggle, and
1529+
* `editInline: false` withdraws it. This toggle used to persist a USER's edit
1530+
* mode into `inlineEdit` through `persistViewPatch`. After the fold, switching
1531+
* it off stored `inlineEdit: false`, which reads as "not offered", so the
1532+
* toggle was gone from the next load and nothing in the console could bring it
1533+
* back.
15321534
*
15331535
* The edit mode is now session state. `ListView` keeps it, and seeds it from
15341536
* the view's `inlineEdit` on each load. The callback stays wired because
15351537
* `ListView` offers the wide toolbar toggle only to a host that wires one.
15361538
*
15371539
* Recorded costs: the edit mode is not remembered across loads, and an overlay
15381540
* that already stores `inlineEdit: false` still reads off. That is existing
1539-
* data, and the maintainer's ruling rejects migrating it. A view that should
1540-
* offer inline editing declares `userActions.editInline: true`.
1541+
* data, and the maintainer's ruling rejects migrating it. Declaring
1542+
* `userActions.editInline: true` on such a view brings the toggle back.
15411543
*/
15421544
function keepInlineEditModeForTheSession(): void {
15431545
// Deliberately empty: see the docblock.

0 commit comments

Comments
 (0)