Skip to content

Commit 3cb84d0

Browse files
feat(spec)!: retire the view item's owner and hidden keys (ADR-0049) (#20227)
Fixes #20085 Clause-②: no ## What this does Retires the view item's `owner` and `hidden` keys under ADR-0049 enforce-or-remove, following the triage direction on the card (comment 5826969296, verbatim: 「retire both keys」) and the `spec-property-retirement` playbook. Both keys sat on the view-item identity layer (`viewItemBaseShape()` in `packages/spec/src/ui/view.zod.ts`). They were accepted by the strict authoring door and by the wire member that the `PUT /api/v1/meta/view` door validates, and `saveMetaItem` stored them verbatim. Nothing read or wrote either one. An author (often an AI) who wrote `hidden: true` or `owner: 'u1'` got a clean save and no effect, and a view marked as one user's was listed for everyone. After this PR, every door that carries a ViewItem record refuses both keys with a prescription. ## Premise, re-measured first (execution note 1) Each reading is taken against the named tree, with a lit control on the same ref. The premise held, so the retirement proceeds. | where | ref | `owner` / `hidden` on a view item | lit control | |---|---|---|---| | objectstack `packages/**`, `examples/**` | `e7f69dbb` | no reader, no writer. Both switcher read paths filter on `viewKind` + `object` and sort on `order`: `GET /meta/view?object=` (`rest-server.ts` ~6594-6607) and `getViewsByObject` (`metadata-manager.ts:1727`). Examples author zero ViewItem records (`viewKind:` count 0); the 5 `owner:` hits in example view files are field-label translations. | `order` is read by both paths; `label:` authored 109 times in the same example files | | objectui at the pin `.objectui-sha` | `f8a9d0fb` | view-receiver `.owner` reads: 0. The 13 view-receiver-shaped `.hidden` hits are all fields, menu items or grid columns (read one by one). All 19 view write calls carry no `hidden` / `owner` patch; the only hidden-like patch key is ListView `hiddenFields`. Zero `.createView(` callers. Zero `sys_view_definition` references. | `isDefault` view reads: 10; `isPinned` 56; `sys_metadata` 124 | | objectui `main` | `25c7d584` | same readings (0 / 13 non-view / 18 write calls, none carrying the keys / 0 / 0) | `isDefault` 10; `isPinned` 56; `sys_metadata` 129 | | cloud `main` | `48d70663` | no code reader or writer. One test double pins a lean `{hidden:true}` personalization PUT as accepted; that is the flattened-overlay door (below), which this PR leaves alone. | `sys_metadata` 160 | Zone 2 assumption 3 (no personal-view feature reads `owner` through another path): confirmed. `sys_view_definition` has `owner` / `hidden` columns and a docblock claiming the switcher merges its rows client-side. objectui references the table 0 times at both refs, and no framework code reads its rows. ADR-0131 D13 (2026-09-04) already records that table and its runbook as inert and retired, with execution paused, and records per-user view scope as a parked v18 direction. The table and the runbook are left to that ADR's execution. Zone 2 assumption 2 (stored data): no producer was measured, but the `PUT /meta/view` door accepted and persisted both keys until now, so a stored row can hold them. The D2 conversion below is therefore owed. An upgrading deployment's stored row carrying either key is stripped at rehydration (`database-loader.ts:825` replays the chain over every stored row as `{ views: [row] }`), then parses clean. Behaviour does not change, because neither key ever had an effect. ## Scope boundary, measured as the order asked - The `owner` / `hidden` at `view.zod.ts:5186-5187` (`flattenedViewOverlayFields()`) are **not** the same key on the same door. They are the flattened-overlay members' own declarations: a lean personalization PUT with no `config`, a different door. They are not retired here. A bound `{ object, viewKind, hidden: true }` overlay still saves, and that is pinned as a boundary. The conversion leaves overlays alone for the same reason. - Not the column `hidden` (`:1060`), not `Hidden override` (`:3193`), not `scope`, not the flattened list-overlay region (`:5433` onward, PR #20183). ## The route, and why (the open choices settled here, on the four axes) **1. `retiredKey()` on the SHARED shape, not strict deletion plus a `guidance` entry.** `viewItemBaseShape()` feeds two doors. The strict authoring door (`ViewItemSchema`) would refuse a deletion, but the wire member (`ViewItemWireSchema`, `.strip()`) is member 1 of the union that `saveMetaItem`, the `view` registry binding and the assembled-manifest `viewItems` channel all run, and a bare deletion there would be a silent strip (ADR-0104). - Business need: the wire door is where a real PUT lands. - Long-term soundness: one declaration serves both doors (derive-by-reference, the rule `viewItemArmShape()` exists for), with no second copy to drift. - AI-error prevention: `tsc` types the key `never` on `defineViewItem`'s input, and every parse carries the prescription instead of a strip. - Startup-stage restraint: no new mechanism; `retiredKey()` inside `strictObject` shapes is established precedent (`TursoConfig.timeout`, `ListViewSchema.pageName` in this same file). **2. The D2 conversion reaches `viewItems` as well as `views`.** Package export and environment artifacts carry standalone ViewItem records in the assembled-manifest `viewItems` channel. `applyArtifactForwardConversions` replays the chain over that channel, and then the registration loop parses each entry against `AssembledViewArtifactSchema`, which now refuses the keys. Walking `views` alone would have left an artifact assembled before this release failing registration with a 422 over two keys that never did anything. This is the minimal reach that keeps the new refusal from breaking an upgrade; it adds no new surface. **3. The flattened-overlay copies stay.** The order asked that a separate door be retired only if it is the same key on the same door. It is not, so those copies are reported under Acceptance notes rather than removed. ## Contract changes, quoted verbatim `view.owner` prescription (a `retiredKey()`, issue code `invalid_type`, path `["owner"]`): > `view.owner` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — it named the user a `personal` view item belonged to, and nothing ever read it: the view switcher (`GET /meta/view?object=`) serves every item bound to the object without looking at `owner`, so a view marked as one user's was listed for every user who can read the object. Delete the key. Nothing restricts a view item to one user today — per-user view scoping is a parked direction (ADR-0017), not a shipped mechanism — so a view item is visible to everyone who can read its object. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. `view.hidden` prescription (issue code `invalid_type`, path `["hidden"]`): > `view.hidden` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — it promised to hide a view item from the switcher, and nothing ever read it: `GET /meta/view?object=` and the console's view switcher list every item bound to the object, `hidden: true` included. Delete the key; to take a view out of the switcher, delete the view item itself (or stop shipping it from source). Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. Removed `.describe` texts (the keys now describe themselves as `[REMOVED]` plus the prescription above in the generated reference): > Owner user id — set when `scope` is `personal`. > Hidden from the switcher (per-user / per-org declutter). The `ViewScopeSchema` TSDoc no longer calls the package layer "hideable from the switcher" or says `personal` is "scoped to `owner`". It now states that per-user scoping is parked and that nothing restricts a `personal` item to one user. ## The retirement kit (the playbook's surface list) | surface | this PR | |---|---| | schema | `retiredKey()` ×2 on `viewItemBaseShape()`, with an in-schema comment on what was removed and why | | D2 conversion | `view-item-owner-hidden-removed` (`toMajor: 18`, `retiredFromLoadPath: true`), record spelling only, `views` + `viewItems`, lossless `stripKeys`, fixture with 3 notices (two on a `views` record, one on a `viewItems` record), a flattened-overlay neighbour kept | | D3 chain | id added to `MIGRATIONS_BY_MAJOR[18].conversionIds`, and the step rationale extended | | `RETIRED_KEYS_BY_MAJOR[18]` | `ui/ViewItem:owner`, `ui/ViewItem:hidden`, `ui/ViewItemWire:owner`, `ui/ViewItemWire:hidden` (four entry files plus `gen:migration-registry`) | | liveness ledger | no row, because a row would be an ORPHAN: the walk stops at the `view` union's container arm (Acceptance notes) | | generated artifacts | `check:generated --fix` proved `content/docs/references/**` stale, and nothing else. `authorable-surface/`, `json-schema.manifest/`, `api-surface/` and the signatures are **byte-identical, as expected on this route**: all four read a def's top-level `properties` or exports, and `ViewItem` / `ViewItemWire` are discriminated unions with no top-level `properties`. `spec-changes.json` and the upgrade guide are unchanged too, because they project up to the current protocol major (17), so no major-18 sibling appears in them either (measured: `object-tenancy-organization-field-removed` 0 hits, `action-inert-keys-removed` 2) | | forms / i18n | no form offers either key (`view.form.ts:54` is the column `hidden`) | | CLI advisory lint | ledger-driven; no row, so nothing changes | | examples / skills / hand-written docs | zero authorings (measured); `tsc` and the tree-scoped pin below hold that | | reconciliation ledger | the `metadata-form-zod-reconciliation.test.ts` comment block now records both keys as retired | | changeset | `@objectstack/spec: minor`, `**BREAKING**`, FROM → TO, the one-line fix, and the ADR-0087 disposition `registered view-item-owner-hidden-removed` | ## Pins (`packages/spec/src/ui/view-item-owner-hidden-retirement.test.ts`) - Every door that carries a record refuses both keys: - the strict authoring door and the `.strip()` wire member: issue code `invalid_type`, path, and prescription; - the `view` registry binding, which is what `saveMetaItem` runs: `invalid_union` whose message is the prescription, and whose viewItem-branch issue locates the key; - the assembled channel. - `defineViewItem` throws, and an `@ts-expect-error` proves the input type is `never` (the file is compiled by `check:test-typecheck`). - CONTROL: the same record without the keys passes all four doors, with `scope` / `isDefault` / `order` intact and no `owner` / `hidden` grown. - BOUNDARY: a flattened overlay's own keys still parse. - Conversion: a stored row rehydrates clean through `applyConversionsToStoredItem` and then parses at the door; `viewItems` is reached and overlays and containers are left alone; the second replay produces 0 notices and returns the same reference; on the load path, a live author is refused rather than rewritten. - Registration: the four `RETIRED_KEYS_BY_MAJOR[18]` rows and the chain id. - **Tree-scoped structural absence** over the declared `@objectstack/spec` radius (`packages`, `examples` non-code, `skills`, `content`, `scripts`, all already in `scripts/cross-package-test-inputs.mjs`). - `owner` / `hidden` are among the commonest key names in this tree, so the matcher is structural rather than textual. It flags one object literal, or one YAML mapping, whose own keys include `viewKind`, `config` and a retired key. - Anti-vacuity cases cover each syntax the walk reads, plus the neighbours that must not match: an overlay, a nested column `hidden`, the `sys_view_definition` row shape, prose, a quoted string, and a sibling YAML item. - Excluded, with stated reasons: the conversion fixture, the gitignored `json-schema/` output, release notes, the pin itself, and `view.zod.ts`. `view.zod.ts` was the one measured hit, and it was the overlay door's own shape (a `config: z.undefined()` guard beside that door's keys). ## Verification (final head `b0b5b381`, after merging `origin/main` at `ce70876e`, which carried #20183) Heavy runs went through `scripts/pm/os-verify-lock.sh`; every exit code was written to disk before its log was read. The box was shared (lock queue waits of 9–20 min), so wall-clock figures are contended readings. | run | head | reading | |---|---|---| | metadata-protocol dependency closure build (includes `@objectstack/spec` build + `gen:schema`) | `b0b5b381` | exit 0 | | `pnpm --filter @objectstack/spec check:generated` | `b0b5b381` | exit 0, all 15 artifacts current. Earlier, `--fix` regenerated only `content/docs/references/**`, the one artifact it proved stale | | spec `--project local`, full | `b0b5b381` | 544 files / 16035 tests passed | | spec `--project repo`, full (this pin now lives here) | `b0b5b381` | 33 files / 604 tests passed (roster length 33, including this pin) | | spec `typecheck` (`tsc` + `check:scripts-typecheck` + `check:test-typecheck`, so the `@ts-expect-error` is compiled) | `b0b5b381` | exit 0 | | consumer: metadata-protocol, the 22 files that spell `viewKind` (#20183's new tests included) | `b0b5b381` | 22 / 372 passed | | consumer: objectql, the 24 `viewKind` files (registration loop, assembled `viewItems`) | `60eec227` | 24 / 380 passed | | consumer: lint (vitest ran the whole suite) | `60eec227` | 109 / 4232 passed | | consumer typecheck: `@objectstack/lint`, `@objectstack/metadata-protocol` | `60eec227` | exit 0, exit 0 | | spec source audits `check:liveness`, `check:empty-state`, `check:exported-any`, `check:dual-source-exports`, `check:variant-docs` | `4cb0f252` | all exit 0 | **Reverse verification (cross-package type, one-shot, nothing left behind).** A temp `packages/lint/src/zz-issue20085-dts-probe.ts` called `defineViewItem({ …, hidden: true })` against the REBUILT spec `.d.ts`. `@objectstack/lint` `tsc --noEmit` exited 1 with `src/zz-issue20085-dts-probe.ts(9,3): error TS2322: Type 'true' is not assignable to type 'undefined'.` — the probe's `hidden: true` line. After the probe was removed (verified absent), `@objectstack/lint typecheck` exited 0. Predicted direction: red. Observed: red. **Door measurement (one-shot, not committed).** The existing `protocol.save-union-issues.test.ts` harness drives the real `saveMetaItem` write path over its stub engine. With a probe block appended, in a temp file, deleted after the run: | body saved | result | |---|---| | record with `owner` | `INVALID_METADATA` / `422` / 0 rows persisted, prescription in `issues` | | record with `hidden` | the same | | CONTROL: the same record without either key | saves, 1 row | | BOUNDARY: a bound lean `{ hidden: true, object, viewKind }` overlay | saves, 1 row | **Ablation of the new pin** (`scripts/ablation-replace.mjs`, on committed state). The mutation swapped `owner: retiredKey(VIEW_ITEM_OWNER_RETIRED),` for `owner: z.string().optional(),`: anchor count 1 → 0, blob `9d5445c7` → `fc09981a`. The pin then read 4 failed / 14 passed, exactly the four `owner` door pins (strict, wire, `view` door, assembled). The restore brought the blob back to HEAD `9d5445c7`, with `git diff HEAD` at 0 bytes and `git status --porcelain` at 0 lines. Predicted direction: red. Observed: red. **Gates.** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` was re-derived on the ACTUAL changed paths at `b0b5b381` (111 commands; the dispatch-time lead had 72). Every line was run with its exit code captured before any pipe, and the record was reconciled with `--ran`: **111 derived, 107 run, 4 NOT-MEASURED, 0 UNRUN**. All 107 measured commands exited 0: 40 `node scripts/…` and 67 `pnpm …`, including `check:adr-0087-registration` (`registered view-item-owner-hidden-removed (new here …)`), `check:changeset-no-major`, `check:empty-changeset`, `check:cross-package-test-inputs`, `check:engine-double-contract`, `check:nul-bytes` and the spec `check:*` family. NOT MEASURED — exit 3, `PREREQUISITE NOT MET`. Each reads built output of packages outside the closure built here (client-react; the whole workspace), and the verify lock was queued 9–20 min per attempt, so they were not built locally. This diff touches no package entry point, export, client SDK or tsconfig. CI's build lanes measure them: - `pnpm --filter @objectstack/spec run check:skill-examples` - `pnpm check:dual-build-cjs-loads` - `pnpm check:lean-entry-closure` - `pnpm check:type-check-debt` Also not measured locally, and owned by CI: the rest (closure of 26 packages) and client-react (35) consumer suites, `pnpm lint`, and the CI-only job and type-check lanes that `dispatch-gates` lists as having no local invocation. ## Acceptance notes (observed, not fixed here) 1. **The liveness walk cannot see a view-item key** (execution note 3 on the card). `check:liveness`'s view walk stops at the `view` union's container arm: its `shapeOf` takes the first object member, and the `viewItem` arm is a discriminated union. So no view-item key can hold a ledger row, and a row reads as ORPHAN. That is why this retirement has no `dead` row to keep. Not claimed here. Carrier: none; triage routed it to the `domain:spec` lane. 2. **Same family: the authorable-surface ratchet cannot see a view-item key either.** `build-schemas.ts` collects authorable keys from a def's top-level `properties` only. `ui/ViewItem` and `ui/ViewItemWire` are discriminated unions with none, so `authorable-surface/` has no `ui/ViewItem:*` line, and check (b) never sees these tombstones. The four `RETIRED_KEYS_BY_MAJOR[18]` rows are declared rather than judged; this PR's own pin holds them. Carrier: none; same family as note 1. 3. **The flattened-overlay door keeps its own `owner` / `hidden`, and they are just as inert.** `flattenedViewOverlayFields()` declares both for the lean personalization PUT. The premise table found no reader of either key on any door, and the one-shot door probe saved a bound `{ hidden: true, object, viewKind }` overlay (1 row). Same family as this card; left for the seat to judge, because the order scoped this card to the ViewItem door. Carrier: none. 4. **The assembled channel's refusal loses the branch diagnostics.** `AssembledViewArtifactSchema` is a plain `z.union`. A refused `viewItems:` entry therefore reaches the registration loop's 422 as "First issue: Invalid input", while the `view` door's union surfaces the prescription (measured on a record carrying `hidden`). Pre-existing. Carrier: none. 5. **The earlier view-family conversions do not reach `viewItems`.** `mapViewPayloads` walks `stack.views` only, so, for example, `view-page-mount-removed` never visits a ViewItem record's `config` inside an assembled artifact's `viewItems:` channel. This is read from `conversions/walk.ts`, not exercised end-to-end. This PR's own conversion walks both collections. Carrier: none. 6. **A bound flattened LIST overlay without `columns` parses through the FORM member.** On `b0b5b381`, `{ name, object, viewKind: 'list', hidden: true }` is claimed as `listOverlay` by `selectViewMetadataBranch`, yet the union accepts it through the form overlay (output stamped `type: 'simple'`); adding `columns` gives `type: 'grid'`. `saveMetaItem` stores the original body, so no stored effect was measured. It sits in #20051's region. Carrier: none. 7. **`sys_view_definition` and its migration runbook describe a switcher merge that does not exist** (objectui has 0 references at its pin and at `main`). ADR-0131 D13 already rules both inert and retired, with execution paused. Nothing to do here. ## ADR interplay ADR-0017 §2 describes `personal` views as "visible only to its `owner`". Its 2026-09-04 amendment (ADR-0131 D13) retired the §3.4 store as inert and parks per-user view scope as a v18 direction. This PR removes the unenforced item-level declaration of that parked direction and leaves the scope model itself alone (`scope` is untouched). If the direction is revived, `owner` returns together with its reader. No ADR text is edited here, which keeps this diff off the governed surfaces. --- _Generated by [Claude Code](https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 66e266c commit 3cb84d0

12 files changed

Lines changed: 929 additions & 25 deletions
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec)!: retire the view item's `owner` and `hidden` keys — declared, stored, and read by nothing (#20085)
6+
7+
**BREAKING** — `owner` and `hidden` are removed from the view item
8+
(`defineViewItem`, `ViewItemSchema`, and the `view` metadata door's ViewItem
9+
record `{ name, object, viewKind, config }`). ADR-0049 enforce-or-remove; triage
10+
direction, verbatim: 「retire both keys」.
11+
12+
Both keys were accepted by the strict authoring door and by the wire member the
13+
`PUT /api/v1/meta/view` door validates, and `saveMetaItem` stored them verbatim —
14+
but nothing ever read or wrote either. Measured before removal, each against a
15+
lit control: no reader or writer of the view-item keys in the framework, in
16+
objectui at its pinned commit and at `main`, or in cloud. Both view-switcher read
17+
paths (`GET /meta/view?object=` and `getViewsByObject`) filter on `viewKind` +
18+
`object` and sort on `order`. So `hidden: true` hid nothing, and a view with
19+
`owner` set was listed for every user who can read the object. The `owner` half
20+
was a visibility claim nothing enforced, which is the security shape ADR-0049 is
21+
about. Per-user view scoping is a parked direction (ADR-0017, amended
22+
2026-09-04), not a shipped mechanism.
23+
24+
### FROM → TO
25+
26+
| removed | what to write instead |
27+
| --- | --- |
28+
| view item `owner` | delete the key. Nothing restricts a view item to one user today; a view item is visible to everyone who can read its object. |
29+
| view item `hidden` | delete the key. To take a view out of the switcher, delete the view item (or stop shipping it from source). |
30+
31+
**The one-line fix: delete `owner:` and `hidden:` from every view item.**
32+
`os migrate meta --from 17` lists the mechanical edits for existing sources.
33+
34+
⚠️ Runtime behaviour is deliberately **unchanged**. Neither key ever changed what
35+
a view showed or to whom, so removing one removes no behaviour. What changes is
36+
the answer an author gets: a view item carrying either key is now refused at
37+
parse, with a prescription, instead of being saved with no effect.
38+
39+
### The retirement kit
40+
41+
- **Tombstones, on the shared shape.** Both keys are `retiredKey()` tombstones on
42+
the view-item base shape. That shape feeds two doors: the strict authoring door
43+
(`ViewItemSchema`) and the `.strip()` wire member (`ViewItemWireSchema`, which
44+
the `view` write door and the assembled-manifest `viewItems` channel both run).
45+
A bare deletion there would have been a silent strip (ADR-0104), so one
46+
tombstone serves both: `tsc` types the key `never` on `defineViewItem`'s input,
47+
and every parse raises the prescription.
48+
- **D2 conversion `view-item-owner-hidden-removed`** (step 18, retired from the
49+
load path). It strips both keys from the view item **record** spelling as a
50+
lossless delete, in both collections a record travels in: `views` (stack
51+
sources, and stored `sys_metadata` rows, which the rehydration seam replays as
52+
`{ views: [row] }`) and the assembled-manifest `viewItems` channel (package
53+
export and environment artifacts). Without the second, an artifact assembled
54+
before this release would fail its registration parse.
55+
- **`RETIRED_KEYS_BY_MAJOR[18]`**: `ui/ViewItem:owner`, `ui/ViewItem:hidden`,
56+
`ui/ViewItemWire:owner`, `ui/ViewItemWire:hidden`.
57+
- **No liveness row, and no authorable-surface line.** Both instruments read a
58+
def's top-level `properties`, and `ViewItem` / `ViewItemWire` are
59+
discriminated unions that have none. So the four surface ratchets stay
60+
byte-identical on this retirement, and that reading is expected on this route.
61+
- **No deprecation window**, per the project's startup-stage posture.
62+
63+
⛔ **Untouched: the flattened-overlay members' own `owner` / `hidden`.** Those
64+
two members of the `view` door (a lean personalization PUT carrying no `config`)
65+
declare the same names on a different door, which this change does not touch. A
66+
`{ object, viewKind, hidden: true }` overlay still parses. The conversion leaves
67+
overlays alone for the same reason.
68+
69+
⚠️ **The out-of-repo consumer population is NOT MEASURED.** `@objectstack/spec`
70+
is published, so this is breaking for consumers no telemetry was consulted for.
71+
72+
Clause-②: no
73+
74+
<!-- adr-0087: registered view-item-owner-hidden-removed -->

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2104,8 +2104,8 @@ This schema accepts one of the following structures:
21042104
| **isDefault** | `boolean` | optional | Whether this is the object's default view in the switcher. |
21052105
| **order** | `integer` | optional | Sort order within the object's view switcher / left rail. |
21062106
| **scope** | `Enum<'package' \| 'shared' \| 'personal'>` | optional | Identity layer (defaults to `package` for source-loaded views). |
2107-
| **owner** | `string` | optional | Owner user id — set when `scope` is `personal`. |
2108-
| **hidden** | `boolean` | optional | Hidden from the switcher (per-user / per-org declutter). |
2107+
| **owner** | `never` | optional | [REMOVED] `view.owner` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — it named the user a `personal` view item belonged to, and nothing ever read it: the view switcher (`GET /meta/view?object=`) serves every item bound to the object without looking at `owner`, so a view marked as one user's was listed for every user who can read the object. Delete the key. Nothing restricts a view item to one user today — per-user view scoping is a parked direction (ADR-0017), not a shipped mechanism — so a view item is visible to everyone who can read its object. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. |
2108+
| **hidden** | `never` | optional | [REMOVED] `view.hidden` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — it promised to hide a view item from the switcher, and nothing ever read it: `GET /meta/view?object=` and the console's view switcher list every item bound to the object, `hidden: true` included. Delete the key; to take a view out of the switcher, delete the view item itself (or stop shipping it from source). Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. |
21092109
| **protection** | `{ lock: Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>; reason: string; docsUrl?: string }` | optional | Package author protection block — lock policy for this view. |
21102110
| **_lock** | `Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>` | optional | Item-level lock — controls overlay & delete (ADR-0010). |
21112111
| **_lockReason** | `string` | optional | Human-readable reason shown when a write is refused by _lock. |
@@ -2194,8 +2194,8 @@ This schema accepts one of the following structures:
21942194
| **isDefault** | `boolean` | optional | Whether this is the object's default view in the switcher. |
21952195
| **order** | `integer` | optional | Sort order within the object's view switcher / left rail. |
21962196
| **scope** | `Enum<'package' \| 'shared' \| 'personal'>` | optional | Identity layer (defaults to `package` for source-loaded views). |
2197-
| **owner** | `string` | optional | Owner user id — set when `scope` is `personal`. |
2198-
| **hidden** | `boolean` | optional | Hidden from the switcher (per-user / per-org declutter). |
2197+
| **owner** | `never` | optional | [REMOVED] `view.owner` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — it named the user a `personal` view item belonged to, and nothing ever read it: the view switcher (`GET /meta/view?object=`) serves every item bound to the object without looking at `owner`, so a view marked as one user's was listed for every user who can read the object. Delete the key. Nothing restricts a view item to one user today — per-user view scoping is a parked direction (ADR-0017), not a shipped mechanism — so a view item is visible to everyone who can read its object. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. |
2198+
| **hidden** | `never` | optional | [REMOVED] `view.hidden` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — it promised to hide a view item from the switcher, and nothing ever read it: `GET /meta/view?object=` and the console's view switcher list every item bound to the object, `hidden: true` included. Delete the key; to take a view out of the switcher, delete the view item itself (or stop shipping it from source). Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. |
21992199
| **protection** | `{ lock: Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>; reason: string; docsUrl?: string }` | optional | Package author protection block — lock policy for this view. |
22002200
| **_lock** | `Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>` | optional | Item-level lock — controls overlay & delete (ADR-0010). |
22012201
| **_lockReason** | `string` | optional | Human-readable reason shown when a write is refused by _lock. |
@@ -2277,8 +2277,8 @@ This schema accepts one of the following structures:
22772277
| **isDefault** | `boolean` | optional | Whether this is the object's default view in the switcher. |
22782278
| **order** | `integer` | optional | Sort order within the object's view switcher / left rail. |
22792279
| **scope** | `Enum<'package' \| 'shared' \| 'personal'>` | optional | Identity layer (defaults to `package` for source-loaded views). |
2280-
| **owner** | `string` | optional | Owner user id — set when `scope` is `personal`. |
2281-
| **hidden** | `boolean` | optional | Hidden from the switcher (per-user / per-org declutter). |
2280+
| **owner** | `never` | optional | [REMOVED] `view.owner` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — it named the user a `personal` view item belonged to, and nothing ever read it: the view switcher (`GET /meta/view?object=`) serves every item bound to the object without looking at `owner`, so a view marked as one user's was listed for every user who can read the object. Delete the key. Nothing restricts a view item to one user today — per-user view scoping is a parked direction (ADR-0017), not a shipped mechanism — so a view item is visible to everyone who can read its object. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. |
2281+
| **hidden** | `never` | optional | [REMOVED] `view.hidden` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — it promised to hide a view item from the switcher, and nothing ever read it: `GET /meta/view?object=` and the console's view switcher list every item bound to the object, `hidden: true` included. Delete the key; to take a view out of the switcher, delete the view item itself (or stop shipping it from source). Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. |
22822282
| **protection** | `{ lock: Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>; reason: string; docsUrl?: string }` | optional | Package author protection block — lock policy for this view. |
22832283
| **_lock** | `Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>` | optional | Item-level lock — controls overlay & delete (ADR-0010). |
22842284
| **_lockReason** | `string` | optional | Human-readable reason shown when a write is refused by _lock. |
@@ -2377,8 +2377,8 @@ This schema accepts one of the following structures:
23772377
| **isDefault** | `boolean` | optional | Whether this is the object's default view in the switcher. |
23782378
| **order** | `integer` | optional | Sort order within the object's view switcher / left rail. |
23792379
| **scope** | `Enum<'package' \| 'shared' \| 'personal'>` | optional | Identity layer (defaults to `package` for source-loaded views). |
2380-
| **owner** | `string` | optional | Owner user id — set when `scope` is `personal`. |
2381-
| **hidden** | `boolean` | optional | Hidden from the switcher (per-user / per-org declutter). |
2380+
| **owner** | `never` | optional | [REMOVED] `view.owner` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — it named the user a `personal` view item belonged to, and nothing ever read it: the view switcher (`GET /meta/view?object=`) serves every item bound to the object without looking at `owner`, so a view marked as one user's was listed for every user who can read the object. Delete the key. Nothing restricts a view item to one user today — per-user view scoping is a parked direction (ADR-0017), not a shipped mechanism — so a view item is visible to everyone who can read its object. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. |
2381+
| **hidden** | `never` | optional | [REMOVED] `view.hidden` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — it promised to hide a view item from the switcher, and nothing ever read it: `GET /meta/view?object=` and the console's view switcher list every item bound to the object, `hidden: true` included. Delete the key; to take a view out of the switcher, delete the view item itself (or stop shipping it from source). Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. |
23822382
| **protection** | `{ lock: Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>; reason: string; docsUrl?: string }` | optional | Package author protection block — lock policy for this view. |
23832383
| **_lock** | `Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>` | optional | Item-level lock — controls overlay & delete (ADR-0010). |
23842384
| **_lockReason** | `string` | optional | Human-readable reason shown when a write is refused by _lock. |

0 commit comments

Comments
 (0)