Skip to content

Commit 4974a6e

Browse files
committed
chore(spec): regenerate the registry, defaults ledger and reference pages for the editInline default; add the changeset
gen:migration-registry concatenates the D3 entry into registry.ts; the spec build moved ui/UserActionsConfig:editInline to true in authorable-defaults/ui.json under the declared chain; gen:docs rewrote the four editInline rows in references/ui/view.mdx and page.mdx. spec-changes.json and docs/protocol-upgrade-guide.md are not regenerated here: the PR-stage check projects the registry in memory and compares no committed copy, and the committed copies on origin/main already carry sibling drift this diff does not own. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
1 parent e11ef3a commit 4974a6e

5 files changed

Lines changed: 91 additions & 5 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-②: yes (widening)
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/src/migrations/registry.ts‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15614,6 +15614,54 @@ const step18: MigrationStep = {
1561415614
+ 'PluginStartupResult interface — a different type, carrying startTime rather than any '
1561515615
+ 'duration key — which is not a reader of this schema and is unchanged.',
1561615616
},
15617+
// The declared default of `ListView.userActions.editInline` moved from `false`
15618+
// to `true` (maintainer ruling 2026-10-10, the v18 line, verbatim:
15619+
// 「乙 v18 把 spec 默认翻成 true,editInline: false 变成关法。」). A default move
15620+
// reaches every silent document with no parse error and nothing in the
15621+
// author's diff, so the upgrade path carries it as a TODO: only the deployment
15622+
// can say which list that never declared the key was relying on read-only
15623+
// cells. The same class as `view-pagination-page-size-default-50` and
15624+
// protocol 12's `rest-requireauth-default-flip`: no key is removed, so there
15625+
// is no tombstone and no RETIRED_KEYS_BY_MAJOR row, and no D2 conversion
15626+
// exists — a mechanical pass writing `editInline: false` into every silent
15627+
// view would preserve the old posture and defeat the ruling, and one writing
15628+
// `true` would add nothing the default does not already do.
15629+
{
15630+
id: 'list-view-edit-inline-default-on',
15631+
// No backticks in `surface` — build-upgrade-guide.ts renders it inside a code
15632+
// span AND a table cell.
15633+
surface: 'ui.UserActionsConfig.editInline — an OMITTED editInline inside a list view\'s '
15634+
+ 'userActions block, or a page interfaceConfig.userActions block',
15635+
replacement: 'nothing, to take the platform default: the list is editable in place by a user who '
15636+
+ 'may update the object, under the permission gate that already exists. To keep a list '
15637+
+ 'read-only in place — a log, an audit trail, a history, a report roll-up — write it: '
15638+
+ '`userActions: { editInline: false }`',
15639+
reason:
15640+
'A RULED behaviour change on a default, so there is nothing to rewrite and nothing to '
15641+
+ 'refuse: the maintainer\'s ruling of 2026-10-10 made a list view editable in place by '
15642+
+ 'default on the v18 line, and the declared default of `UserActionsConfigSchema.editInline` '
15643+
+ 'moved from `false` to `true`. A `userActions` block that omits `editInline` now parses to '
15644+
+ '`true` — the renderer offers the inline-edit toggle, and a user who may `update` the object '
15645+
+ 'edits a cell in place with the field\'s type-aware widget; a user without `update` sees no '
15646+
+ 'toggle, because the permission gate is untouched. A view with no `userActions` block at all '
15647+
+ 'parses with none on either side; the renderer reads an absent key as the spec default. The '
15648+
+ 'one-vocabulary rule of objectui#5144 stands: a boolean view-level `inlineEdit` folds into '
15649+
+ '`editInline` (`true` reads on and opens the grid in edit mode), and an explicit `editInline` '
15650+
+ 'wins. Not losslessly convertible because the question is intent, not text: only the '
15651+
+ 'deployment knows which silent lists were relying on read-only cells. The accept set is '
15652+
+ 'unchanged — a boolean — and every authored `editInline` parses exactly as before. A '
15653+
+ 'document that serialised an earlier parse (an `os compile` artifact) carries a written '
15654+
+ '`false` and stays read-only in place; recompile it, or delete the key.',
15655+
acceptanceCriteria:
15656+
'An empty `userActions` block parses to `editInline: true`, and a list view carrying '
15657+
+ '`userActions: {}` parses to `userActions.editInline` true; an authored '
15658+
+ '`userActions: { editInline: false }` still parses to `false`; a view-level `inlineEdit: true` '
15659+
+ 'still parses as before, with no `userActions` block materialised. In the console, a grid view '
15660+
+ 'that declares neither key offers the inline-edit toggle to a user with `update` on the object '
15661+
+ 'and not to a user without it; `userActions: { editInline: false }` removes it; '
15662+
+ '`inlineEdit: true` still opens the grid in edit mode. Every list that must stay read-only in '
15663+
+ 'place declares `userActions: { editInline: false }` and shows no toggle.',
15664+
},
1561715665
{
1561815666
id: 'list-view-navigation-view-retired',
1561915667
surface: 'view.list.navigation.view',

0 commit comments

Comments
 (0)