|
| 1 | +--- |
| 2 | +'@objectstack/spec': minor |
| 3 | +--- |
| 4 | + |
| 5 | +feat(spec)!: retire the flattened view overlay's `owner` and `hidden` keys — accepted at the save door, stored, and read by nothing (#20230) |
| 6 | + |
| 7 | +**BREAKING** — `owner` and `hidden` are removed from the flattened view overlay: |
| 8 | +the lean `view` body with no `config` that `PUT /api/v1/meta/view/:name` (the |
| 9 | +Studio and MCP save) accepts, members 3 and 4 of the `view` metadata door, and |
| 10 | +the same members in the assembled-manifest `viewItems:` channel. ADR-0049 |
| 11 | +enforce-or-remove; triage direction, verbatim: 「follow #20085's disposition for |
| 12 | +the same key pair」. This completes the family: the view item record's `owner` / |
| 13 | +`hidden` are retired in this same release by its own entry, with the same texts. |
| 14 | + |
| 15 | +⚠️ **This supersedes one sentence of the view item retirement's note in this same |
| 16 | +release.** That note says the flattened overlay's own `owner` / `hidden` are |
| 17 | +untouched and that a `{ object, viewKind, hidden: true }` overlay still parses. |
| 18 | +True of that change alone; after this one, such an overlay is refused too. Read the |
| 19 | +two notes together: after this release, neither door accepts either key. |
| 20 | + |
| 21 | +Clause-②: no (narrowing) |
| 22 | + |
| 23 | +The overlay door declared both keys separately from the view item's pair. A bound |
| 24 | +overlay such as `{ object, viewKind, hidden: true }` saved clean and one row was |
| 25 | +stored with the key, and nothing ever read it. Both view-switcher read paths |
| 26 | +(`GET /meta/view?object=` and `getViewsByObject`) filter on `viewKind` + `object` |
| 27 | +and sort on `order`, so `hidden: true` hid nothing, and a view with `owner` set |
| 28 | +was listed for every user who can read the object. |
| 29 | + |
| 30 | +Writer census, taken before removal: no writer of either overlay key in this |
| 31 | +framework or its examples, in objectui at its pinned commit and at `main` (the |
| 32 | +toolbar writes only `rowHeight`, `sort`, `hiddenFields`, `columnState` and |
| 33 | +`inlineEdit`; the switcher only `label`, `isPinned`, `isDefault` and `sortOrder`), |
| 34 | +or in the HotCRM app. The cloud repository was not reachable from the census. |
| 35 | + |
| 36 | +### FROM → TO |
| 37 | + |
| 38 | +| removed | what to write instead | |
| 39 | +| --- | --- | |
| 40 | +| flattened overlay `owner` | delete the key. Nothing restricts a view to one user today; a view is visible to everyone who can read its object. | |
| 41 | +| flattened overlay `hidden` | delete the key. To take a view out of the switcher, delete the view item (or stop shipping it from source). | |
| 42 | + |
| 43 | +**The one-line fix: delete `owner:` and `hidden:` from every view body you save.** |
| 44 | +`os migrate meta --from 17` lists the mechanical edits for existing sources. |
| 45 | + |
| 46 | +⚠️ Runtime behaviour is deliberately **unchanged**. Neither key ever changed what |
| 47 | +a view showed or to whom. What changes is the answer an author gets: a save that |
| 48 | +carries either key is refused `422 INVALID_METADATA`, with the prescription |
| 49 | +located at the key, instead of being stored with no effect. The prescriptions are |
| 50 | +the view item's own texts, so the family answers with one voice on both doors. |
| 51 | + |
| 52 | +### Stored rows |
| 53 | + |
| 54 | +Every read of a stored `view` row replays the conversion chain before the row is |
| 55 | +served or badged, and the D2 conversion strips both keys there. What that leaves |
| 56 | +depends on what else the row holds: |
| 57 | + |
| 58 | +- **A row with any other view key** (a column state, a sort, a default flag, an |
| 59 | + order): served and badged valid without the keys. A GET then a PUT of the whole |
| 60 | + row saves (if it was otherwise valid), so the console's next read-merge-write of |
| 61 | + it saves, and `os migrate meta --stored --apply` rewrites it. |
| 62 | +- **A hide-only row**, holding nothing but its identity (`name`, `object`, |
| 63 | + `viewKind`, `label`) and `owner` / `hidden`, such as |
| 64 | + `{ object, viewKind, hidden: true }`: the strip leaves identity only, which the |
| 65 | + `view` door refuses ("only identity fields"). The row is served badged invalid |
| 66 | + (it was badged valid before this release). A whole-row re-save, or one that adds |
| 67 | + only identity (a rename sets `label`), answers `422 INVALID_METADATA`. |
| 68 | + `--apply` reports it `failed` and leaves it as stored; every read strips it |
| 69 | + again. A write that adds a real view key, such as a toolbar toggle, saves. |
| 70 | + **Fix: delete the row** (it never changed what anyone saw), or add the |
| 71 | + personalization setting its author meant and save that. |
| 72 | + |
| 73 | +### The retirement kit |
| 74 | + |
| 75 | +- **Tombstones on both overlay members.** `retiredKey()` in |
| 76 | + `flattenedViewOverlayFields()`, with the view item's prescription texts. Both |
| 77 | + members `.strip()`, so a bare deletion would have dropped the key in silence |
| 78 | + (ADR-0104). |
| 79 | +- **D2 conversion `view-overlay-owner-hidden-removed`** (step 18, retired from the |
| 80 | + load path). A lossless delete from the flattened spelling (no `config`, no |
| 81 | + container slot) in `views` (stack sources and stored rows) and `viewItems` |
| 82 | + (assembled artifacts). It is disjoint from `view-item-owner-hidden-removed` by |
| 83 | + `config`, so no row is judged by both. |
| 84 | +- **D3 semantic entry `view-overlay-owner-hidden-retired`**: the family's one D3 |
| 85 | + record, naming its D2 conversion. The view item record's pair is a separate |
| 86 | + family with its own conversion and its own D3 entry; the two share the |
| 87 | + prescription texts. |
| 88 | +- **`RETIRED_KEYS_BY_MAJOR[18]`**: `ui/ViewMetadata:owner`, `ui/ViewMetadata:hidden`. |
| 89 | + `ui/ViewMetadata` is unemitted (its `z.undefined()` guards have no JSON Schema |
| 90 | + form), so no build gate judges these rows and the four surface ratchets are |
| 91 | + byte-identical on this retirement. The rows are pinned by the retirement test. |
| 92 | +- **No liveness row**: the `view` ledger walks the container keys only. |
| 93 | +- **No deprecation window**, per the project's startup-stage posture. |
| 94 | + |
| 95 | +⚠️ **The out-of-repo population is NOT MEASURED.** `@objectstack/spec` is published, |
| 96 | +and production `sys_metadata` rows are not reachable from the repository. Stored |
| 97 | +rows are stripped on read by the conversion above, and a hide-only row among them |
| 98 | +needs the fix above. A client that still sends either key is refused at its next |
| 99 | +save. |
| 100 | + |
| 101 | +<!-- adr-0087: registered view-overlay-owner-hidden-removed, view-overlay-owner-hidden-retired --> |
0 commit comments