Skip to content

Commit f53f14b

Browse files
feat(spec)!: ListView.userActions.editInline defaults to true on the v18 line; editInline: false is the opt-out (#22605, spec half) (#22629)
Part of #22605 Clause-②: no The spec half of #22605: `ListView.userActions.editInline` defaults to `true` on the v18 line, and `userActions: { editInline: false }` is the opt-out. The `skills/**` paragraph the card also asks for is a separate pull request (Tier H), so this one is `Part of`, not `Fixes`. ## The ruling Maintainer, 2026-10-10, verbatim (filed by the director seat on the card): 「乙 v18 把 spec 默认翻成 true,editInline: false 变成关法。」 A list view is editable in place by default, under the permission gate that already exists. The rule of objectui#5144 stands (one vocabulary, the `inlineEdit` fold, declared = enforced); only the default's value changes. Not taken: declaring `editInline: true` on every existing view (objectstack-ai/hotcrm#2048 and #22604 are superseded by the ruling, and stay open here — this PR does not act on either), and an app-level default. The permission gate is untouched. The consumer half is objectui's, objectstack-ai/objectui#12086; in the window the console at the pinned objectui still reads an absent key as off, so nothing regresses against today. ## What changed - `packages/spec/src/ui/view.zod.ts` — `UserActionsConfigSchema.editInline` is `z.boolean().default(true)`, `describe` rewritten (on by default; a user who may update the object edits a cell in place with the field's type-aware widget, under the permission gate; declare `editInline: false` to make the list read-only in place). The schema docblock records the ruling beside the group/hideFields/rowColor asymmetry it already explains. - `packages/spec/scripts/lib/default-changes.ts` — `DEFAULT_CHANGES_BY_MAJOR[17]` gains `ui/UserActionsConfig:editInline` `false` → `true` with its reason. This is what lets `check:authorable-surface` accept the moved fingerprint; `CURRENT_MAJOR` is the package major (17) until the version pass takes the group to 18. The build printed `📌 1 declared default change(s) accepted (#4666): ui/UserActionsConfig:editInline: false → true`. - `packages/spec/src/migrations/entries/semantic/18.list-view-edit-inline-default-on.ts` — the D3 semantic entry (protocol 18), concatenated into `registry.ts` by `gen:migration-registry`. - `packages/spec/authorable-defaults/ui.json` — regenerated by the build: `ui/UserActionsConfig:editInline = true`. - `content/docs/references/ui/view.mdx` (3 rows), `content/docs/references/ui/page.mdx` (1 row) — regenerated by `gen:docs`: `optional (default: true)` with the new describe. - `.changeset/22605-spec-list-view-edit-inline-default-on.md` — `@objectstack/spec: minor`, `Clause-②: no` (a moved default changes no accept set; the breaking signal is the banner and the ADR-0087 marker), the launch-window BREAKING banner naming the author who relied on the documented default for a read-only list and the remedy `editInline: false`, the FROM → TO table, the measured reach, and the ADR-0087 marker `registered list-view-edit-inline-default-on`. `minor` because the launch-window convention ships a breaking change as `minor` (`check-changeset-no-major`), and the fixed group already carries the pending `major` that opens the v18 line, so the level moves no version on its own. - `packages/spec/src/ui/view.test.ts` — the defaults pin flips to `true`, plus four pins: an absent key parses to `true` (on `UserActionsConfigSchema` and through `ListViewSchema`), an explicit `false` stays `false`, an explicit `true` stays `true`, and the view-level `inlineEdit: true` still parses as before with no `userActions` block materialised (the fold is the renderer's; the spec keeps both spellings as declared). ## Measured first (the PM's mechanism assumptions) 1. **Other sites stating the old default.** `view.zod.ts:1535` read `.default(false)` with the "Off by default" describe — corrected. The four reference rows regenerate. Nothing else in the tree states the old default: `content/docs/ui/views.mdx` lists the view-level `inlineEdit` with no default; `content/docs/protocol/objectui/layout-dsl.mdx:667` is about `record:details`' own `inlineEdit` (a different key, renderer default on); `packages/spec/src/data/object.zod.ts` retired-key guidance says only that `editInline` is a view key; `object-strictness-batch20.test.ts` lists key names. Left as they are, with reasons: `.changeset/console-20c6d351ad74.md` describes the console at pin `20c6d351ad74`, which still reads an absent key as off until objectui#12086 lands and the pin moves (true of that build); `CHANGELOG.md:1677` and `packages/spec/CHANGELOG.md` are release-owned history. 2. **ADR-0087 disposition.** `check:adr-0087-registration` demands a marker on a declared-breaking changeset; `not-required (no-migration-prescription)` is refused when the body carries a prescription, and this one prescribes `editInline: false`. The honest disposition is `registered`: a D3 semantic entry at step 18, the same class as `view-pagination-page-size-default-50` and protocol 12's `rest-requireauth-default-flip`. No key is removed (no tombstone, no `RETIRED_KEYS_BY_MAJOR` row) and no D2 conversion exists — a mechanical pass writing `editInline: false` into every silent view would preserve the old posture and defeat the ruling. The registry regenerated byte-exact after merging `origin/main` (which brought the sibling entry `page-slots-details-beside-tabs-refused`, not adjacent in id order; `check:migration-registry` green). A second ledger also applies: the authorable-defaults ratchet refuses an undeclared default move, so `DEFAULT_CHANGES_BY_MAJOR` carries the hop. 3. **Reach of the remedy**, at `e11ef3a7bd` (`git grep`, tests and changelogs excluded): 0 `editInline: false` and 0 `editInline: true` across `examples/**` and `packages/**` metadata. Views that declare nothing and now gain the toggle: 38 list-kind views with an explicit `type` across 10 example `*.view.ts` files (entries with no `type` are grid by default and count the same way), 12 `interfaceConfig` page lists across 5 showcase page files (11 declare a `userActions` block, none naming `editInline`), 105 list-kind views across 40 platform `*.object.ts` files in `packages/**`. One view declares the view-level `inlineEdit: true` (the showcase `task` grid saved view) and keeps opening in edit mode. The only test that asserted the old default is `view.test.ts` (`expect(config.editInline).toBe(false)`), flipped here; no other package's tests read the view-level `userActions` default (consumer tests on `userActions` are the object-level CRUD block). The card's readings outside this repository (cloud `sys-package.page.ts:105` declares `editInline: false`; HotCRM's 41 undeclared views) were not re-measured here. 4. **The skill file** `skills/objectstack-ui/rules/list-views.md` is hand-written: the only generated lifts inside `skills/**` are `references/_index.md` and `references/react-blocks.md` (`GENERATED_SURFACE_EXCEPTIONS`). Its paragraph is PR 2. ## Tests (post-merge head `24d9bdcbf0`, shared-box readings under the verify lock) - `pnpm --filter @objectstack/spec build` — green; `check:generated` — `✓ All 15 generated artifacts are up to date`. - `pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2` (the package's `test` script) — `Test Files 642 passed (642) · Tests 19150 passed | 1 todo (19151)`. - Targeted, before and after the merge: `src/ui/view.test.ts src/ui/page.test.ts src/migrations/migrations.test.ts src/migrations/semantic-relevance.test.ts` — `4 passed · 740 passed`; the `repo`-project files `scripts/step18-rationale-merge.test.ts scripts/authorable-defaults.test.ts scripts/build-schemas-check-mode.test.ts` — `4 passed · 146 passed` (with `semantic-relevance`). - `pnpm --filter @objectstack/spec typecheck` — `tsc --noEmit`, `check:scripts-typecheck` and `check:test-typecheck` green (`52 file(s) / 246 error(s) / 135 pinned signature(s) held`, the shrink-only debt ledger unchanged). - `test:repo` beyond those three files is declared to CI (Test Core). ## Ablation (reverse verification) `node scripts/ablation-replace.mjs --file packages/spec/src/ui/view.zod.ts --anchor 'editInline: z.boolean().default(true)' --replacement 'editInline: z.boolean().default(false)' -- pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2 src/ui/view.test.ts`, at head `24d9bdcbf0`, after the fix was committed. Mutation proved on disk: anchor 1 → 0, replacement 0 → 1, blob `6979ade6158a` → `e0e02cd7f858`. Direction observed: red, exactly the two default pins — `should apply default values` and `an absent key parses to true` (`expected false to be true`), `2 failed | 462 passed (464)`. Restore proved: blob after restore `6979ade6158a` == blob at HEAD, `git diff HEAD` empty, tree clean. Resolution path: the pin imports the schema from the package's own `src` (no `dist` in the path), so no rebuild sits between the mutation and the reading. ## Gates (local, at `24d9bdcbf0`) Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths; the tool reads the change set off the merge base): 115 families. Reconciled with `--ran`: 115 derived, 114 run, 0 NOT-MEASURED, 1 UNRUN — the UNRUN is `pnpm check:dual-build-cjs-loads`, declared NOT MEASURED by the dispatch (it needs a whole-workspace build). Every other derived family exited 0, after building the prerequisites some of them load (`@objectstack/formula`, `lint`, `client`, `client-react`, `objectql`), except `pnpm check:dts-closure`, which exited 1 for a missing `dist/` under `plugin-approvals` and `plugin-dev`: it reads every package's built declarations, so without the whole-workspace build it is NOT MEASURED, not red (CI's Build Core covers it). The artifact-roster block (49 families): 46 run, every one exit 0 except `pnpm check:published-readme-exports`, exit 3 (44 unbuilt packages, NOT MEASURED for the same reason); the three PR-context guards (`check-closing-target-claim`, `check-partof-closing-keyword`, `check-single-claim-paths`) run once this PR has a number, and their readings go in the report comment on the card. Staleness note the tool prints: the derivation tree is two commits behind `origin/main` `83b8b80728`, and the one derivation input that moved is `scripts/platform-object-tenancy-census.json` (the tenancy-census roster); that family ran green on the older copy, and no path of this diff overlaps those two commits. Lint, as a proven narrowing rather than the CI-owned `pnpm lint`: `pnpm exec eslint --no-inline-config --format json` over the `.ts`/`.mjs` files in the two-point diff — 24 files linted, 0 errors, 0 warnings (the count is from the JSON output; the set is wider than this PR's own 9 files (233 insertions, 7 deletions off the merge base `e22315238f`) because the two-point diff includes the merged sibling commits). Population, read from `eslint.config.mjs`: `**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` minus its `ignores`, so the `.md`, `.mdx` and `.json` files of this diff are outside it (eslint reports them "ignored because no matching configuration"). Invariance: the config declares no `parserOptions.project` and no typed `@typescript-eslint` rules, so this diff cannot move the verdict of any file it does not touch. ## Acceptance notes - `packages/spec/spec-changes.json` and `docs/protocol-upgrade-guide.md` are not regenerated here. Since #22449 B′ the PR-stage `check:spec-changes` and `check:upgrade-guide` project the registry in memory and compare no committed copy (the publish lane generates the shipped one), and `origin/main` turned the upgrade guide into a pointer stub while this branch was open; a regeneration on the pre-merge tree carried 30-odd lines of sibling drift this diff does not own, so it was reverted. The entries README still says to regenerate both; the generator header is the newer authority. Noted, not filed. - The objectui consumer half (`editInline !== false` in `ListView.tsx` and `InterfaceListPage.tsx`, the `plugin-view.mdx` row, the flipped pins) is objectstack-ai/objectui#12086, filed by the claiming seat; nothing in objectui moves here. --- _Generated by [Claude Code](https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 86da194 commit f53f14b

9 files changed

Lines changed: 241 additions & 7 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
`ListView.userActions.editInline` defaults to `true` on the v18 line: a list view is editable in place by default, under the permission gate that already exists, and `userActions: { editInline: false }` is the opt-out.
6+
7+
Clause-②: no
8+
9+
<!-- adr-0087: registered list-view-edit-inline-default-on -->
10+
11+
**BREAKING** — a declared default moves, shipped as `minor` under the launch-window convention. The author who relied on the documented default (`false`, "the list is read-only unless the author opts in") for a list that is read-only by nature — a log, an audit trail, a history, a report roll-up — now gets an inline-edit toggle on it, for every user who may `update` the object. The remedy is one key on that view: `userActions: { editInline: false }`.
12+
13+
**The ruling.** Maintainer, 2026-10-10, verbatim: 「乙 v18 把 spec 默认翻成 true,editInline: false 变成关法。」 The rule of objectui#5144 stands — one vocabulary (`editInline`), a boolean view-level `inlineEdit` folds into it, an explicit `editInline` wins, declared = enforced; only the default's value changes. Not taken: declaring `editInline: true` on every existing view, and an app-level default between the spec default and the view.
14+
15+
**What moves.** `UserActionsConfigSchema.editInline` is `z.boolean().default(true)` where it was `.default(false)`, with the `describe` rewritten: on by default; a user who may update the object edits a cell in place with the field's type-aware widget; declare `editInline: false` to make the list read-only in place. A list view's `userActions` block, and a page's `interfaceConfig.userActions` block (the same schema), that omits `editInline` now parses to `true` where it parsed to `false`; the published JSON Schema and the reference pages (`content/docs/references/ui/view.mdx`, `page.mdx`) say so. A view with no `userActions` block at all parses with none on either side; the renderer reads an absent key as this default. The accept set is unchanged: `editInline` is still a boolean, and every authored value parses exactly as before. The permission gate is untouched: the renderer offers the toggle only where the object is editable in place and the current principal may `update` it.
16+
17+
**The consumer half** is objectui's (objectstack-ai/objectui#12086): `ListView.tsx` and `InterfaceListPage.tsx` read an absent key as `editInline !== false`. Until that lands, the console at the pinned objectui still reads an absent key as off, so nothing regresses against today in the window.
18+
19+
## FROM → TO
20+
21+
| you wrote | what it means now | write instead, to keep the old behaviour |
22+
|:--|:--|:--|
23+
| nothing (no `editInline`) on a list view or a page list | editable in place by a user who may `update` the object | `userActions: { editInline: false }` |
24+
| `editInline: false` | unchanged: read-only in place | nothing |
25+
| `editInline: true` | unchanged: editable in place | nothing |
26+
| a view-level `inlineEdit: true` | unchanged: folds to on and opens the grid in edit mode | nothing |
27+
28+
**The one-line fix: on a list that must stay read-only in place, write `userActions: { editInline: false }`.** An `os compile` artifact that serialised an earlier parse carries a written `editInline: false` and stays read-only in place: recompile it, or delete the key.
29+
30+
**Who is affected, measured** at `e11ef3a7bd`. No metadata in this repository declares `editInline` at all (0 `editInline: false`, 0 `editInline: true` across `examples/**` and `packages/**`, tests and changelogs excluded). Every list view that declares nothing gains the toggle: 38 list-kind views declared with an explicit `type` across 10 example `*.view.ts` files (entries with no `type` are grid by default and count the same way), 12 `interfaceConfig` page lists across 5 showcase page files (11 declare a `userActions` block, none naming `editInline`), and 105 list-kind views across 40 platform `*.object.ts` files in `packages/**`. One list view declares the view-level `inlineEdit: true` (the showcase `task` grid saved view) and keeps opening in edit mode. The card's readings outside this repository: cloud's `sys-package.page.ts` already declares `editInline: false` explicitly; HotCRM's 41 undeclared views regain cell editing after both packages upgrade, with no metadata change there.
31+
32+
### The kit
33+
34+
- **The default and its describe.** `packages/spec/src/ui/view.zod.ts`, `UserActionsConfigSchema.editInline`; the schema docblock records the ruling beside the group/hideFields/rowColor asymmetry it already explains.
35+
- **The declared default change.** `DEFAULT_CHANGES_BY_MAJOR[17]` (`packages/spec/scripts/lib/default-changes.ts`) carries `ui/UserActionsConfig:editInline` `false` → `true`, which is what lets `check:authorable-surface` authorise the moved fingerprint in `authorable-defaults/ui.json`; `CURRENT_MAJOR` is the package major (17) until the version pass takes the group to 18.
36+
- **The ledger.** The D3 semantic entry `list-view-edit-inline-default-on` (protocol 18), one file under `src/migrations/entries/semantic/`, concatenated into `registry.ts` by `gen:migration-registry`, projected into `spec-changes.json` and `docs/protocol-upgrade-guide.md`. No key is removed, so there is no tombstone and no `RETIRED_KEYS_BY_MAJOR` row; no D2 conversion exists, because a mechanical pass writing `editInline: false` into every silent view would preserve the old posture and defeat the ruling, and one writing `true` would add nothing the default does not already do. The same class as `view-pagination-page-size-default-50` and protocol 12's `rest-requireauth-default-flip`.
37+
- **The pins.** `packages/spec/src/ui/view.test.ts`: an absent key parses to `true` (on `UserActionsConfigSchema` and through `ListViewSchema`), an explicit `false` stays `false`, an explicit `true` stays `true`, and the view-level `inlineEdit: true` still parses as before with no `userActions` block materialised.
38+
- **The skill.** `skills/objectstack-ui/rules/list-views.md` gains its paragraph in a separate pull request, because `skills/**` is Tier H.

‎content/docs/references/ui/page.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ View filter rule
145145
| **rowHeight** | `boolean` | optional (default: `true`) | Allow users to toggle row height/density |
146146
| **group** | `boolean` | optional (default: `true`) | Allow users to change record grouping from the toolbar. Toggle only — the grouping itself is configured in the view-level `grouping` block. |
147147
| **addRecordForm** | `boolean` | optional (default: `false`) | Add records through a form instead of inline |
148-
| **editInline** | `boolean` | optional (default: `false`) | Allow users to edit records inline — click a cell to edit it with the field's type-aware widget (the same control the form uses). Off by default: the list is read-only unless the author opts in. |
148+
| **editInline** | `boolean` | optional (default: `true`) | Allow users to edit records inline. On by default: a user who may update the object edits a cell in place with the field's type-aware widget (the same control the form uses), under the permission gate that already decides whether the object is editable at all. Declare `editInline: false` to make the list read-only in place. |
149149
| **hideFields** | `boolean` | optional (default: `false`) | Allow users to hide/show fields from the toolbar (the affordance behind the view-level `hiddenFields` list). Boolean toggle — distinct from the record-details component's `hideFields`, which is an array of field names to omit. Off by default: column hiding is opt-in. |
150150
| **rowColor** | `boolean` | optional (default: `false`) | Allow users to configure row colouring from the toolbar. Boolean toggle — the colour rules themselves live in the view-level `rowColor` block. Off by default: row colouring is opt-in. |
151151
| **buttons** | `string[]` | optional | Custom action button IDs to show in the toolbar |

‎content/docs/references/ui/view.mdx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,7 +1081,7 @@ View filter rule
10811081
| **rowHeight** | `boolean` | optional (default: `true`) | Allow users to toggle row height/density |
10821082
| **group** | `boolean` | optional (default: `true`) | Allow users to change record grouping from the toolbar. Toggle only — the grouping itself is configured in the view-level `grouping` block. |
10831083
| **addRecordForm** | `boolean` | optional (default: `false`) | Add records through a form instead of inline |
1084-
| **editInline** | `boolean` | optional (default: `false`) | Allow users to edit records inline — click a cell to edit it with the field's type-aware widget (the same control the form uses). Off by default: the list is read-only unless the author opts in. |
1084+
| **editInline** | `boolean` | optional (default: `true`) | Allow users to edit records inline. On by default: a user who may update the object edits a cell in place with the field's type-aware widget (the same control the form uses), under the permission gate that already decides whether the object is editable at all. Declare `editInline: false` to make the list read-only in place. |
10851085
| **hideFields** | `boolean` | optional (default: `false`) | Allow users to hide/show fields from the toolbar (the affordance behind the view-level `hiddenFields` list). Boolean toggle — distinct from the record-details component's `hideFields`, which is an array of field names to omit. Off by default: column hiding is opt-in. |
10861086
| **rowColor** | `boolean` | optional (default: `false`) | Allow users to configure row colouring from the toolbar. Boolean toggle — the colour rules themselves live in the view-level `rowColor` block. Off by default: row colouring is opt-in. |
10871087
| **buttons** | `string[]` | optional | Custom action button IDs to show in the toolbar |
@@ -1464,7 +1464,7 @@ View filter rule
14641464
| **rowHeight** | `boolean` | optional (default: `true`) | Allow users to toggle row height/density |
14651465
| **group** | `boolean` | optional (default: `true`) | Allow users to change record grouping from the toolbar. Toggle only — the grouping itself is configured in the view-level `grouping` block. |
14661466
| **addRecordForm** | `boolean` | optional (default: `false`) | Add records through a form instead of inline |
1467-
| **editInline** | `boolean` | optional (default: `false`) | Allow users to edit records inline — click a cell to edit it with the field's type-aware widget (the same control the form uses). Off by default: the list is read-only unless the author opts in. |
1467+
| **editInline** | `boolean` | optional (default: `true`) | Allow users to edit records inline. On by default: a user who may update the object edits a cell in place with the field's type-aware widget (the same control the form uses), under the permission gate that already decides whether the object is editable at all. Declare `editInline: false` to make the list read-only in place. |
14681468
| **hideFields** | `boolean` | optional (default: `false`) | Allow users to hide/show fields from the toolbar (the affordance behind the view-level `hiddenFields` list). Boolean toggle — distinct from the record-details component's `hideFields`, which is an array of field names to omit. Off by default: column hiding is opt-in. |
14691469
| **rowColor** | `boolean` | optional (default: `false`) | Allow users to configure row colouring from the toolbar. Boolean toggle — the colour rules themselves live in the view-level `rowColor` block. Off by default: row colouring is opt-in. |
14701470
| **buttons** | `string[]` | optional | Custom action button IDs to show in the toolbar |
@@ -1635,7 +1635,7 @@ User action toggles for the view toolbar
16351635
| **rowHeight** | `boolean` | optional (default: `true`) | Allow users to toggle row height/density |
16361636
| **group** | `boolean` | optional (default: `true`) | Allow users to change record grouping from the toolbar. Toggle only — the grouping itself is configured in the view-level `grouping` block. |
16371637
| **addRecordForm** | `boolean` | optional (default: `false`) | Add records through a form instead of inline |
1638-
| **editInline** | `boolean` | optional (default: `false`) | Allow users to edit records inline — click a cell to edit it with the field's type-aware widget (the same control the form uses). Off by default: the list is read-only unless the author opts in. |
1638+
| **editInline** | `boolean` | optional (default: `true`) | Allow users to edit records inline. On by default: a user who may update the object edits a cell in place with the field's type-aware widget (the same control the form uses), under the permission gate that already decides whether the object is editable at all. Declare `editInline: false` to make the list read-only in place. |
16391639
| **hideFields** | `boolean` | optional (default: `false`) | Allow users to hide/show fields from the toolbar (the affordance behind the view-level `hiddenFields` list). Boolean toggle — distinct from the record-details component's `hideFields`, which is an array of field names to omit. Off by default: column hiding is opt-in. |
16401640
| **rowColor** | `boolean` | optional (default: `false`) | Allow users to configure row colouring from the toolbar. Boolean toggle — the colour rules themselves live in the view-level `rowColor` block. Off by default: row colouring is opt-in. |
16411641
| **buttons** | `string[]` | optional | Custom action button IDs to show in the toolbar |

‎packages/spec/authorable-defaults/ui.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@
107107
"ui/TimelineConfig:scale = \"week\"",
108108
"ui/UrlNavItem:target = \"_self\"",
109109
"ui/UserActionsConfig:addRecordForm = false",
110-
"ui/UserActionsConfig:editInline = false",
110+
"ui/UserActionsConfig:editInline = true",
111111
"ui/UserActionsConfig:filter = true",
112112
"ui/UserActionsConfig:group = true",
113113
"ui/UserActionsConfig:hideFields = false",

‎packages/spec/scripts/lib/default-changes.ts‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,30 @@ export const DEFAULT_CHANGES_BY_MAJOR: Readonly<Record<number, readonly Declared
554554
+ 'To keep 25 rows per page, write it: `pagination: { pageSize: 25 }`. To take the platform '
555555
+ 'default, change nothing.',
556556
},
557+
{
558+
key: 'ui/UserActionsConfig:editInline',
559+
from: 'false',
560+
to: 'true',
561+
reason:
562+
'A RULED behaviour change, not a correction: the maintainer ruled on 2026-10-10, for the '
563+
+ 'v18 line, 「乙 v18 把 spec 默认翻成 true,editInline: false 变成关法。」 — a list view is '
564+
+ 'editable in place by default, under the permission gate that already exists, and '
565+
+ '`userActions: { editInline: false }` is the opt-out. The one-vocabulary rule objectui '
566+
+ 'ruled for the fold (a boolean `inlineEdit` folds into `editInline`, an explicit `editInline` '
567+
+ 'wins, declared = enforced) stands; only the default\'s value moves. '
568+
+ 'What moves: a list view (or a page `interfaceConfig`) whose `userActions` block omits '
569+
+ '`editInline` now parses to `true` where it parsed to `false`, so the renderer offers the '
570+
+ 'inline-edit toggle to a user who may `update` the object — and to nobody else, because '
571+
+ 'the permission gate is untouched. A view with NO `userActions` block parses with none on '
572+
+ 'either side; the renderer reads an absent key as this default. The accept set does not '
573+
+ 'move: `editInline` is still a boolean, and every authored value parses exactly as before. '
574+
+ 'A document that SERIALISED an earlier parse (an `os compile` artifact) carries a written '
575+
+ '`false` and stays read-only in place: recompile it, or delete the key. '
576+
+ 'Who is affected: the author who relied on the documented default for a list that is '
577+
+ 'read-only by nature (a log, an audit trail, a history, a report roll-up). To keep that '
578+
+ 'list read-only in place, write it: `userActions: { editInline: false }`. To take the '
579+
+ 'platform default, change nothing. D3 entry: `list-view-edit-inline-default-on`.',
580+
},
557581
{
558582
key: 'data/Cube:public',
559583
from: 'false',
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import type { SemanticMigration } from '../../types.js';
4+
5+
// The declared default of `ListView.userActions.editInline` moved from `false`
6+
// to `true` (maintainer ruling 2026-10-10, the v18 line, verbatim:
7+
// 「乙 v18 把 spec 默认翻成 true,editInline: false 变成关法。」). A default move
8+
// reaches every silent document with no parse error and nothing in the
9+
// author's diff, so the upgrade path carries it as a TODO: only the deployment
10+
// can say which list that never declared the key was relying on read-only
11+
// cells. The same class as `view-pagination-page-size-default-50` and
12+
// protocol 12's `rest-requireauth-default-flip`: no key is removed, so there
13+
// is no tombstone and no RETIRED_KEYS_BY_MAJOR row, and no D2 conversion
14+
// exists — a mechanical pass writing `editInline: false` into every silent
15+
// view would preserve the old posture and defeat the ruling, and one writing
16+
// `true` would add nothing the default does not already do. The one-vocabulary
17+
// rule the printed `reason` names is objectui#5144 (a boolean view-level
18+
// `inlineEdit` folds into `editInline`; an explicit `editInline` wins); the
19+
// citation lives here, not in the printed guidance, which carries no tracker
20+
// number.
21+
export const entry: SemanticMigration = {
22+
id: 'list-view-edit-inline-default-on',
23+
// No backticks in `surface` — build-upgrade-guide.ts renders it inside a code
24+
// span AND a table cell.
25+
surface: 'ui.UserActionsConfig.editInline — an OMITTED editInline inside a list view\'s '
26+
+ 'userActions block, or a page interfaceConfig.userActions block',
27+
replacement: 'nothing, to take the platform default: the list is editable in place by a user who '
28+
+ 'may update the object, under the permission gate that already exists. To keep a list '
29+
+ 'read-only in place — a log, an audit trail, a history, a report roll-up — write it: '
30+
+ '`userActions: { editInline: false }`',
31+
reason:
32+
'A RULED behaviour change on a default, so there is nothing to rewrite and nothing to '
33+
+ 'refuse: the maintainer\'s ruling of 2026-10-10 made a list view editable in place by '
34+
+ 'default on the v18 line, and the declared default of `UserActionsConfigSchema.editInline` '
35+
+ 'moved from `false` to `true`. A `userActions` block that omits `editInline` now parses to '
36+
+ '`true` — the renderer offers the inline-edit toggle, and a user who may `update` the object '
37+
+ 'edits a cell in place with the field\'s type-aware widget; a user without `update` sees no '
38+
+ 'toggle, because the permission gate is untouched. A view with no `userActions` block at all '
39+
+ 'parses with none on either side; the renderer reads an absent key as the spec default. The '
40+
+ 'one-vocabulary rule objectui ruled for the fold stands: a boolean view-level `inlineEdit` folds into '
41+
+ '`editInline` (`true` reads on and opens the grid in edit mode), and an explicit `editInline` '
42+
+ 'wins. Not losslessly convertible because the question is intent, not text: only the '
43+
+ 'deployment knows which silent lists were relying on read-only cells. The accept set is '
44+
+ 'unchanged — a boolean — and every authored `editInline` parses exactly as before. A '
45+
+ 'document that serialised an earlier parse (an `os compile` artifact) carries a written '
46+
+ '`false` and stays read-only in place; recompile it, or delete the key.',
47+
acceptanceCriteria:
48+
'An empty `userActions` block parses to `editInline: true`, and a list view carrying '
49+
+ '`userActions: {}` parses to `userActions.editInline` true; an authored '
50+
+ '`userActions: { editInline: false }` still parses to `false`; a view-level `inlineEdit: true` '
51+
+ 'still parses as before, with no `userActions` block materialised. In the console, a grid view '
52+
+ 'that declares neither key offers the inline-edit toggle to a user with `update` on the object '
53+
+ 'and not to a user without it; `userActions: { editInline: false }` removes it; '
54+
+ '`inlineEdit: true` still opens the grid in edit mode. Every list that must stay read-only in '
55+
+ 'place declares `userActions: { editInline: false }` and shows no toggle.',
56+
};

0 commit comments

Comments
 (0)