refactor(spec)!: retire global_nav from ACTION_LOCATIONS — a location the product never rendered, and the designer previewed anyway (#6888) - #7322
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Resolved examples/app-showcase/src/ui/actions/index.ts: main's #6739 fix (showcase_new_task type modal->form, target showcase_task.edit) is kept intact; this branch's #6888 change applies to `locations` only, which becomes [] now that global_nav is retired. Docblock carries both narratives. Regenerated spec-changes.json + protocol-upgrade-guide.md from the merged registries after a real build (never hand-merged - both are generated).
📓 Docs Drift CheckThis PR changes 2 package(s): 106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also reference the affected code. These are read-only:
|
|
Rebased onto current
Nothing from #6739 was reverted: its maintainer-ruled reasoning (a Worth noting for review: the merge makes the "flagged for review" note in the PR body less sharp than when written. It flagged a headless Generated artifacts ( Re-verified on the merged tree, all green:
One transient worth recording so the next reader does not re-diagnose it: Generated by Claude Code |
The textual merge kept this branch's side of `spec-changes.json` and `docs/protocol-upgrade-guide.md`, which predate PR #7322's `action-global-nav-location-removed` conversion. Regenerated from the merged registry so both that entry and this branch's `sharing-execution-context-retired` semantic entry project into the artifacts. Generated files, never hand-merged. Claude-Session: https://claude.ai/code/session_01ArkxxyvoyPaDhHiX5KhQBi Co-authored-by: os-zhuang <steve@objectstack.ai> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Closes #6888
Retires
global_navfromACTION_LOCATIONS, executing the maintainer's2026-08-09 direction-2 ruling. In-repo half only — the Studio designer surfaces
(
ActionPreview.tsxmock frame, inspector entry, palette dropdown option) aresub-issue #7100 in
objectui, contract-first behind this card; #7100 unlockswhen this closes.
Maintainer ruling (quoted verbatim)
Premise re-verified on
origin/main@3e8e669c0All three legs still held before the change (the issue's line numbers had drifted;
the facts had not):
global_navstill inACTION_LOCATIONSpackages/spec/src/ui/action.zod.ts:536(doc line:527— issue said:463/:454, drifted)examples/app-showcase/src/ui/actions/index.ts:184,:363global_navvariant still presentdocs/qa/platform-checklist/areas/records-forms.json:1385and the dispatch stepIndependently re-confirmed the "nothing renders it" half against the vendored
objectui, since the retirement rests on it:
actionRendersAt(
packages/types/src/ui-action.ts:86-99) is the single placement predicate sinceobjectui#3142 and is a pure
declared.includes(location)— nothing mapsglobal_navonto a surface, andpackages/app-shell/src/chrome/CommandPalette.tsxreads no action metadata at all.
Route: this is an enum-VALUE retirement, not a key removal
Per the repo's spec-property-retirement playbook, the three routes are
retiredKey()tombstone / strict-removal + guidance map / neither — and none ofthem applies to a def that survives while losing one value. The house mechanism
for that case is the enum's own
errormap dispatched onissue.input, set bycrypto.hashonHookBodyCapability(#4391) andarray_agg/string_aggonAggregationFunction(#6188). Consequences, all deliberate:RETIRED_KEYS_BY_MAJOR— that table is keyed${defKey}:${name}for authorable KEYS, and no key changed. Adding a row wouldbe a registration nothing consumed (gate b2).
api-surface/,authorable-surface/,json-schema.manifest/,export-origins/record export-and def-existence, not a def's value set. Confirmed zero diff; see the gate
table below. (The playbook is explicit that reading this zero as "the agent
forgot to regenerate" is the wrong verdict for this route, and that reading it
as normal for a whole-def removal is the opposite error.)
the full story and closes with the [finding][spec] Tombstones over STRIP-only conversions say
os migrate meta --from 16will "rewrite it" — the corpus is split between two verbs for one behaviour #6856 route-D house sentence.Per-surface changes
packages/spec/src/ui/action.zod.ts'global_nav'removed fromACTION_LOCATIONS(7 to 6) and from the semantics doc list; house tombstone note added to the docblock;GLOBAL_NAV_RETIREDprescription +errormap onActionLocationSchema, keyed onissue.inputpackages/spec/src/conversions/registry.tsaction-global-nav-location-removed(toMajor: 17,retiredFromLoadPath: true), wired intoCONVERSIONS_BY_MAJOR[17]packages/spec/src/migrations/registry.tsconversionIdsentry + rationale paragraphpackages/spec/liveness/action.jsonlocationsrow keepsstatus: live(the KEY is live and enforced) with a freshverifiedAt+evidencerecording the per-member auditexamples/app-showcase/src/ui/actions/index.tslocations: []; docblocks rewrittendocs/qa/platform-checklist/areas/records-forms.jsonglobal_navvariant deleted; fixture fleet, nav step, dispatch step, exclusivity clause and evidence count updated;enumSource.expect7 to 6; revision 2 to 3 with a history entrycontent/docs/protocol/objectui/actions.mdx,content/docs/ui/actions.mdxskills/objectstack-ui/SKILL.mdpackages/lint/src/validate-action-locations.tsglobal_navdropped from theaction-no-placementhint's list of places to put the actionpackages/spec/src/studio/plugin.zod.ts,packages/spec/src/studio/action-location-retirement.test.tsglobal_navprobes re-pointed atrecord_section, so that test keeps pinning the rename rather than accidentally pinning this retirementcontent/docs/references/ui/action.mdx,docs/protocol-upgrade-guide.md,packages/spec/spec-changes.json.changeset/action-global-nav-location-retired.md@objectstack/spec: majorNot touched:
content/docs/releases/.The empty-array edge: strip the value, KEEP the key
locations: ['global_nav']converts tolocations: []— neverdelete locations.On this surface the two are different declarations, not two spellings of one:
locations: []is the documented headless shape (content/docs/ui/actions.mdx,"Headless actions: declare it, then hide it") — the action stays callable over
REST/MCP/AI and keeps its capability gate, param contract and audit trail.
locationsmeans the author never placed the action, which isexactly what
packages/lint'saction-no-placementrule warns about. That rulestates the distinction in its own words: "an author who said 'nowhere,
deliberately' (
[]) and one who never said anything at all (key absent)".So dropping the key would convert a deliberate placement into a lint finding and
discard the author's statement of intent. This follows
hook-body-crypto-hash-removed("thecapabilitieskey itself stays — an emptygrant set is legal"), not
dataset-measure-array-string-agg-removed, which dropsits item for a validity reason that does not apply here: a measure with neither
aggregatenorderivedfails the dataset's own refinement, whereas an actionwith
locations: []parses and is documented to.Registry counts, before to after
CONVERSIONS_BY_MAJOR[17]MIGRATIONS_BY_MAJOR[17].conversionIdsRETIRED_KEYS_BY_MAJOR[17]MIGRATIONS_BY_MAJOR[17].semanticACTION_LOCATIONSmembersReverse verification — direction predicted before running
Predicted in writing first, then measured by reverting only
packages/spec/src/ui/action.zod.tstoorigin/main(viagit checkout origin/main --,never
git stash) and re-running the same suite:is no longer a member of the vocabularyexpected [ 'list_toolbar', 'list_item', …(5) ] to not include 'global_nav'refuses the value WITH the prescription — bare enum and whole action alikeexpected [Function] to throw an error(the value parsed clean, so no prescription existed to assert)tells ONLY the retired spelling that it "was removed"accepts the migrated shape … locations: []Plus one collateral red, predicted and not counted as evidence: the pre-existing
exposes the full set of supported locationspin, whose expectation this PRrewrites from 7 members to 6.
The two invariants are deliberately green on both sides and say so here rather
than being dressed up as before/after evidence: they pin the error map's
issue.inputkeying (a typo must not be told its value "was removed") and themigration target, neither of which the removal alone would prove.
A bare
.toThrow()would have been useless here and the pins avoid it: with thefix reverted the value parses green, so an assertion carrying one bit could
not tell "refused with no prescription" from "not refused at all" — which are the
two things this retirement has to distinguish.
Gates
pnpm --filter @objectstack/spec buildpnpm --filter @objectstack/spec check:generatedspec-changes.json,docs/protocol-upgrade-guide.md,content/docs/references/**. Ran with--fix(never the whole set)check:api-surface,check:authorable-surface,check:export-originsand thejson-schema.manifest/projection: green with zero regeneration. This is the CORRECT reading for an enum-VALUE retirement, and is stated here because the zero is also what a forgotten regen looks like on a whole-def removalcheck:generateddeliberately does not runcheck:livenessPASS ·check:empty-statePASS ·check:skill-examplesPASS ·check:variant-docsPASS ·check:exported-anyPASS ·check:dual-source-exportsPASS ·check:scripts-typecheckPASS (each exit code captured before the next ran, never through a pipe)node scripts/check-adr-0087-registration.mjs --base origin/main1 declared-breaking changeset(s) … [major+bang] registered action-global-nav-location-removed (new here: action-global-nav-location-removed)pnpm --filter @objectstack/spec testTest Files 359 passed (359)/Tests 9387 passed (9387)pnpm --filter @objectstack/spec typecheckpnpm --filter @objectstack/lint testTest Files 68 passed (68)/Tests 1771 passed, 4 skipped (1775)pnpm --filter @objectstack/lint typechecknode scripts/check-nul-bytes.mjsgrep -naPself-scan over every changed file@objectstack/lintfirst reported 18 failed files and a typecheck exit 2. That wasthe fresh-worktree trap, not this change: unbuilt siblings (
Cannot find module '@objectstack/formula','@objectstack/sdui-parser'), with no failure namingvalidate-action-locationsorglobal_nav. Runpnpm --filter '@objectstack/lint^...' buildfirst and both go green — recorded sothe next reader does not re-diagnose it.
docs/adr/**touched: NOnode scripts/check-adr-0087-registration.mjsis satisfied by registry codeplus the changeset's
adr-0087: registered action-global-nav-location-removedmarker; it demanded no
docs/adr/**edit. Per the ruling's note and PD #14, nonewas made —
git diff --name-only origin/main -- docs/adris empty.Flagged for review, not silently decided
showcase_new_taskhadglobal_navas its ONLY location, so it becomeslocations: []— a headlesstype: 'modal'action. No replacement placement wasinvented for it, because choosing one is a product decision about the showcase
rather than a consequence of this retirement. It is not left dead: the showcase's
live "Create Task" path is the Overview page's
element:buttonCTA carrying aninline action of the same name (
examples/app-showcase/src/ui/pages/index.ts:95),which is what a user actually clicks.
showcase_portfolio_snapshotneeds no suchnote — it is object-less by design and its docblock already said
global_navwaschosen "for the same reason" it has no
objectName, so[]is the declaration italways meant.
Changeset level:
major, matching #7067 (manifest.loading) and both enum-valueprecedents (#6188, #6815). The ADR-0087 gate reads it as
[major+bang], which iswhat it requires to accept the D2 registration.
Generated by Claude Code