Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .changeset/29037-view-personal-declarations-retired.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
'@objectstack/spec': minor
'@object-ui/plugin-list': minor
'@object-ui/core': minor
'@object-ui/i18n': minor
---

feat(spec)!: a view no longer declares itself personal — `scope: 'personal'` and `sharing.type` are retired, because nothing ever restricted a view to one user

Clause-②: no (narrowing)

Metadata-protocol: narrowed

Skills-docs: none — no file under `skills/**` teaches either declaration (a `git grep` of `skills/` finds no `scope: 'personal'` and no list-view `sharing.type`; its one `sharing` block is the public form link, which this does not touch); `content/docs/ui/audience-based-interfaces.mdx` now says data mode offers per-user list preferences, not personal views.

<!-- adr-0087: registered view-personal-declarations-removed, ui-view-personal-declarations-retired -->

**BREAKING** accept-set narrowing on the view surface (`@objectstack/spec`), ADR-0049 enforce-or-remove.

A view item saved with `scope: 'personal'`, or a list view saved with `sharing: { type: 'personal' }`, was accepted and served, and every user who could read the object still saw it: both switcher read paths list every view bound to the object, and nothing read either declaration as a visibility rule. The console list toolbar, the one reader of `sharing.type`, then labelled the view "Personal view". Personal named views are ruled to be a user-owned record, not view metadata, and they do not reuse either key.

- **FROM → TO.** `{ name, object, viewKind, scope: 'personal', … }` → `{ name, object, viewKind, … }` (omit `scope`, or write `shared` for a runtime-authored view). `list: { …, sharing: { type, lockedBy } }` (and `listViews.<key>`, an object's `listViews.<key>`, a list view item's `config`) → `list: { …, sharing: { lockedBy } }`.
- **The one-line fix.** Delete `scope: 'personal'` and delete `sharing.type`, whatever value it holds. `os migrate meta --from 17` lists the edits for existing sources, and `--write` applies them. A stored `sys_metadata` row and an assembled artifact are converted when they are read (the `view-personal-declarations-removed` conversion), so a view saved with either declaration keeps loading and nothing a reader sees changes.
- **What the parse says.** "`view.scope: 'personal'` was removed in @objectstack/spec 18.3.0 (ADR-0049 enforce-or-remove) — nothing ever restricted a `personal` view item to one user: …" at every door that reads a view item's `scope`, and "`view.list.sharing.type` was removed in @objectstack/spec 18.3.0 (ADR-0049 enforce-or-remove) — nothing enforced it: …" at every list-view door and at `defineStack` as `STACK_SCHEMA_INVALID` / 422. TypeScript rejects both at the authoring site.
- **Residue.** `type: 'collaborative'`, the default a released toolchain wrote into every parsed `sharing` block, is accepted and stripped, so a view built before this release parses without an edit; `personal` is refused.
- **Unchanged.** `scope: 'package'` and `scope: 'shared'`; `sharing.lockedBy`; a form view's public-link `sharing` block.
- **Console.** The list toolbar no longer labels any view personal or shared; a view with `sharing.lockedBy` still shows "Locked by …". `normalizeListViewSchema` drops the legacy `sharing: { visibility, enabled }` pair instead of folding it onto the retired `type`, and the `list.sharingPersonal` / `list.sharingCollaborative` strings are gone from every locale.
4 changes: 2 additions & 2 deletions content/docs/references/api/protocol.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1877,7 +1877,7 @@ The published metadata item body, opaque by ruling (1C). Shape is the item's own
| **tree** | `{ parentField?: string; labelField?: string; fields?: string[]; defaultExpandedDepth?: integer }` | optional | Tree/hierarchy configuration — applies when the view renders as a tree layout |
| **pageName** | `never` | optional | [REMOVED] `view.pageName` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — it named the page a `type: 'page'` view was to mount, and that mount was never built: no renderer read the key, so the named page was never reached and the view drew an empty grid. Delete the key; to put a published page in front of users, give the app a navigation item — `{ type: 'page', pageName: '<page_name>' }` under the app's `navigation` — which is a different key on a different surface and is the page mount that has always rendered. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
| **description** | `string \| Record<string, string>` | optional | View description for documentation/tooltips |
| **sharing** | `{ type?: Enum<'personal' \| 'collaborative'>; lockedBy?: string }` | optional | View sharing and access configuration |
| **sharing** | `{ lockedBy?: string }` | optional | View sharing configuration: who locked the view configuration |
| **requiredPermissions** | `string[]` | optional | [ADR-0066] Capabilities a user must ALL hold for this list view or dashboard to be served to them — names that permission sets grant through `systemPermissions`, the same list as `requiredPermissions` on an app, a navigation item or an action. Absent or empty: no audience gate. There is no any-of form: to serve one item to several audiences (legal OR admin), declare one capability and grant it to each of their permission sets. The server enforces it: a user who does not hold them all is not listed the item and is refused it by name; a view container's list views are pruned the same way, and so are an object's own list views for a user who may not edit the object. |
| **rowHeight** | `Enum<'compact' \| 'short' \| 'medium' \| 'tall' \| 'extra_tall'>` | optional | Row height / density setting |
| **grouping** | `{ fields: object[] }` | optional | Group records by one or more fields — server-side: the groups and their header numbers come from an aggregate query over the whole filtered set, rows within a group are paged (see GroupingConfigSchema) |
Expand Down Expand Up @@ -1956,7 +1956,7 @@ The published metadata item body, opaque by ruling (1C). Shape is the item's own
| **tree** | `{ parentField?: string; labelField?: string; fields?: string[]; defaultExpandedDepth?: integer }` | optional | Tree/hierarchy configuration — applies when the view renders as a tree layout |
| **pageName** | `never` | optional | [REMOVED] `view.pageName` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — it named the page a `type: 'page'` view was to mount, and that mount was never built: no renderer read the key, so the named page was never reached and the view drew an empty grid. Delete the key; to put a published page in front of users, give the app a navigation item — `{ type: 'page', pageName: '<page_name>' }` under the app's `navigation` — which is a different key on a different surface and is the page mount that has always rendered. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
| **description** | `string \| Record<string, string>` | optional | View description for documentation/tooltips |
| **sharing** | `{ type?: Enum<'personal' \| 'collaborative'>; lockedBy?: string }` | optional | View sharing and access configuration |
| **sharing** | `{ lockedBy?: string }` | optional | View sharing configuration: who locked the view configuration |
| **requiredPermissions** | `string[]` | optional | [ADR-0066] Capabilities a user must ALL hold for this list view or dashboard to be served to them — names that permission sets grant through `systemPermissions`, the same list as `requiredPermissions` on an app, a navigation item or an action. Absent or empty: no audience gate. There is no any-of form: to serve one item to several audiences (legal OR admin), declare one capability and grant it to each of their permission sets. The server enforces it: a user who does not hold them all is not listed the item and is refused it by name; a view container's list views are pruned the same way, and so are an object's own list views for a user who may not edit the object. |
| **rowHeight** | `Enum<'compact' \| 'short' \| 'medium' \| 'tall' \| 'extra_tall'>` | optional | Row height / density setting |
| **grouping** | `{ fields: object[] }` | optional | Group records by one or more fields — server-side: the groups and their header numbers come from an aggregate query over the whole filtered set, rows within a group are paged (see GroupingConfigSchema) |
Expand Down
2 changes: 1 addition & 1 deletion content/docs/references/data/object.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ const result = ApiMethod.parse(data);
| **tree** | `{ parentField?: string; labelField?: string; fields?: string[]; defaultExpandedDepth?: integer }` | optional | Tree/hierarchy configuration — applies when the view renders as a tree layout |
| **pageName** | `never` | optional | [REMOVED] `view.pageName` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — it named the page a `type: 'page'` view was to mount, and that mount was never built: no renderer read the key, so the named page was never reached and the view drew an empty grid. Delete the key; to put a published page in front of users, give the app a navigation item — `{ type: 'page', pageName: '<page_name>' }` under the app's `navigation` — which is a different key on a different surface and is the page mount that has always rendered. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
| **description** | `string \| Record<string, string>` | optional | View description for documentation/tooltips |
| **sharing** | `{ type?: Enum<'personal' \| 'collaborative'>; lockedBy?: string }` | optional | View sharing and access configuration |
| **sharing** | `{ lockedBy?: string }` | optional | View sharing configuration: who locked the view configuration |
| **requiredPermissions** | `string[]` | optional | [ADR-0066] Capabilities a user must ALL hold for this list view or dashboard to be served to them — names that permission sets grant through `systemPermissions`, the same list as `requiredPermissions` on an app, a navigation item or an action. Absent or empty: no audience gate. There is no any-of form: to serve one item to several audiences (legal OR admin), declare one capability and grant it to each of their permission sets. The server enforces it: a user who does not hold them all is not listed the item and is refused it by name; a view container's list views are pruned the same way, and so are an object's own list views for a user who may not edit the object. |
| **rowHeight** | `Enum<'compact' \| 'short' \| 'medium' \| 'tall' \| 'extra_tall'>` | optional | Row height / density setting |
| **grouping** | `{ fields: object[] }` | optional | Group records by one or more fields — server-side: the groups and their header numbers come from an aggregate query over the whole filtered set, rows within a group are paged (see GroupingConfigSchema) |
Expand Down
Loading
Loading