|
| 1 | +--- |
| 2 | +"@objectstack/spec": major |
| 3 | +--- |
| 4 | + |
| 5 | +refactor(spec)!: retire `global_nav` from `ACTION_LOCATIONS` — a location the product never rendered, and the designer previewed anyway (#6888, ADR-0049) |
| 6 | + |
| 7 | +`ACTION_LOCATIONS` is the canonical vocabulary for where an action surfaces in a |
| 8 | +running app, and `global_nav` — "global navigation/command-palette level |
| 9 | +actions" — has been in it since the vocabulary was written. **No running-app |
| 10 | +surface ever rendered it.** The console's ⌘K palette |
| 11 | +(`app-shell/src/chrome/CommandPalette.tsx`) builds its groups from nav items, |
| 12 | +objects, dashboards, pages, reports, recent items, record search and theme; its |
| 13 | +`actions` group is hard-coded chrome; and the file references neither |
| 14 | +`global_nav` nor any action-metadata source at all. Of the five references to |
| 15 | +the value in the whole UI repo at the vendored SHA, four were the Studio |
| 16 | +designer and the fifth a doc comment. |
| 17 | + |
| 18 | +What lifts this above ordinary inert-declaration cleanup is the direction of the |
| 19 | +lie. `metadata-admin/previews/ActionPreview.tsx` drew the author a mock |
| 20 | +`⌘K · Command palette` frame, so the **authoring tool promised a surface the |
| 21 | +product does not have**. An author declares the location, watches it "render" in |
| 22 | +the designer, ships it, and it reaches no user — the ADR-0078 |
| 23 | +declares/renders/does-nothing shape, arriving through the location vocabulary |
| 24 | +rather than through a missing key. For an AI author reading the corpus (ADR-0033) |
| 25 | +that preview is evidence the capability exists, which is exactly how dead |
| 26 | +metadata multiplies. |
| 27 | + |
| 28 | +Retired rather than implemented (maintainer ruling, 2026-08-09): no user has |
| 29 | +asked for command-palette actions, and the only two declarers were our own |
| 30 | +showcase corpus. Wiring the palette would have been capability expansion with no |
| 31 | +pull. If real appetite appears it re-enters through the front door, |
| 32 | +implementation first. |
| 33 | + |
| 34 | +FROM → TO: |
| 35 | + |
| 36 | +| Was | Now | |
| 37 | +|:--|:--| |
| 38 | +| `locations: ['global_nav', 'record_header']` | `locations: ['record_header']` — drop the value, keep the served locations | |
| 39 | +| `locations: ['global_nav']` on an action with a UI home | place it where a renderer serves it: `list_toolbar`, `list_item`, `record_header`, `record_more`, `record_related`, `record_section` | |
| 40 | +| `locations: ['global_nav']` on an action with no UI home (e.g. object-less, invoked over REST/MCP/AI) | `locations: []` — the documented **headless** declaration, which keeps the capability gate, param contract and audit trail | |
| 41 | + |
| 42 | +The retirement kit: |
| 43 | + |
| 44 | +- This is an enum **VALUE** retirement, so there is no `retiredKey()` tombstone: |
| 45 | + the enum's own error map carries the prescription, keyed on the received value |
| 46 | + so only the spelling that used to be legal is told it "was removed" (the |
| 47 | + `crypto.hash` / `HookBodyCapability` precedent, #4391, and `array_agg` / |
| 48 | + `AggregationFunction`, #6188). A mis-spelling still gets zod's list of the |
| 49 | + legal locations. For the same reason nothing lands in `RETIRED_KEYS_BY_MAJOR` |
| 50 | + and the four surface ratchets are byte-identical — no def and no authorable |
| 51 | + key changed. |
| 52 | +- **ADR-0087 D2 conversion + D3 chain step** |
| 53 | + (`action-global-nav-location-removed`): `os migrate meta --from 16` strips the |
| 54 | + value from `action.locations`, one notice per rewritten action. |
| 55 | +- **The key is kept when the array empties** — `locations: []`, never |
| 56 | + `delete locations`. On this surface the two are different declarations, not |
| 57 | + two spellings of one: the empty array is the documented headless shape |
| 58 | + ("Headless actions: declare it, then hide it"), while an absent key means |
| 59 | + nobody placed the action — which is what `packages/lint`'s |
| 60 | + `action-no-placement` warns about, in those words ("an author who said |
| 61 | + 'nowhere, deliberately' (`[]`) and one who never said anything at all"). |
| 62 | + Dropping the key would convert a deliberate placement into a lint finding and |
| 63 | + discard the author's own statement of intent. This is the |
| 64 | + `hook-body-crypto-hash-removed` shape ("the `capabilities` key itself stays — |
| 65 | + an empty grant set is legal"), not the |
| 66 | + `dataset-measure-array-string-agg-removed` shape, which drops its item only |
| 67 | + because the stripped remainder would fail the dataset's own refinement. |
| 68 | +- The QA platform checklist's `records-forms.action-location-matrix` loses its |
| 69 | + `global_nav` variant. That variant was **unrunnable**, not merely obsolete: |
| 70 | + its step "new_task from the palette" could never pass. `enumSource.expect` |
| 71 | + moves 7 → 6, which is the ratchet that would otherwise have caught this drift. |
| 72 | +- The two showcase declarers become headless. `showcase_portfolio_snapshot` is |
| 73 | + object-less by design and its docblock already said `global_nav` was chosen |
| 74 | + "for the same reason" it has no `objectName` — so `[]` is the declaration it |
| 75 | + always meant. |
| 76 | + |
| 77 | +**Behaviour that changes:** none at runtime. An action declaring `global_nav` |
| 78 | +rendered nowhere before this change and renders nowhere after it; what changes is |
| 79 | +that the declaration is now refused at parse, with the reason, instead of being |
| 80 | +accepted and silently ignored. |
| 81 | + |
| 82 | +<!-- adr-0087: registered action-global-nav-location-removed --> |
0 commit comments