Repository navigation
feat(types,runner)!: retire the app node's actions array, AppAction and AppActionSchema (objectui#7469) - #10761
Conversation
…nd AppActionSchema Maintainer ruling C: app-level actions are navigation items of type 'action'. The app node's actions member becomes a named refusal on both faces (?: never + retirementTombstone, since BaseSchema's passthrough would otherwise keep an authored array), AppAction and AppActionSchema leave the published exports, and the standalone runner's header stops reading the array: no per-action buttons, no avatar menu, and the Bell is unconditional. Pins that read the retired surface are retired or re-pointed, the AppActionSchema pair leaves the mirror-parity ledger, and the docs teach navigation action items. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014fWVhLzhxR8qrFsJ5o8TYW
…a page The rewritten "Global Actions" section teaches a navigation item of type 'action'. That value is a NavigationItem kind, not a rendered node type, so check:doc-types declares it beside the page's existing `item` / `group` menu-kind exemptions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014fWVhLzhxR8qrFsJ5o8TYW
Brings in the queue's landings before the pull request opens, including the other zod-mirror-parity ledger edit (objectui#7759 group A). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014fWVhLzhxR8qrFsJ5o8TYW
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: ① Derived judgmentsContract face (
Runner. The Docs. The rewritten 「Global Actions」 teaches an Extra test edits. Each is re-pointed at the new truth, and nothing guarded shrinks except the retired thing itself:
Gate edit. Pins can fail. Types pin: 14 rows; on BASE the four Residue. At head, ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
…ding it hides (objectui#10746) (objectstack-ai#10762) Fixes objectstack-ai#10746 Clause-②: no — a Studio inspector's patch on a type switch; no declared surface moves ## What changed `packages/app-shell/src/views/metadata-admin/inspectors/ReportDefaultInspector.tsx`: the Report type picker's commit is now `commitType`. When the picked type is `joined`, the patch that commits `type: 'joined'` also carries `dataset`, `values`, `rows`, `columns`, `chart` and `order` as `undefined`-valued keys — only those the draft actually holds (`draft[key] !== undefined`). Any other type commits `{ type }` alone, as before. The list lives in `JOINED_CONTAINER_CLEARED_KEYS` with the reasoning beside it; `blocks`, `runtimeFilter` and `drilldown` are never named. `order` is a sixth key, beyond the card's five, under the role file's bounded in-place exemption (same defect class, mechanical, same file, same gate family, no other claim on the file): the spec's own `reportForm` hides `order` in the same "Dataset binding" section (`visibleWhen: "data.type != 'joined'"`), and the INSTALLED spec already refuses a container `order` on a joined report ("a `joined` report orders per block — move `order` onto `blocks[]`"), so the same invisible-key save refusal exists today for `order`. Evidence in H3 below. New pin file `ReportDefaultInspector.joinedClearsBinding-10746.test.tsx` (8 tests); changeset `.changeset/10746-joined-report-clears-binding.md` (`'@object-ui/app-shell': patch`). ## H1 — reproduction on `origin/main` (`1422a920ed`) The pin file run against the untouched component: `Tests 4 failed | 4 passed (8)`. The defect pin quotes the only `onPatch` argument the type picker commits today: `AssertionError: expected { type: 'joined' } to strictly equal { type: 'joined', …(6) }` — the commit is `{ type: 'joined' }` and nothing else. The 4 pins green on base are the boundary/control pins, which pin what must NOT change. ## H2 — the clear mechanism `onPatch` is a shallow patch in all three hosts: `ResourceEditPage` applies it as `handleDraftChange((d) => ({ ...d, ...patch }))`, `ReportConfigPanel.handlePatch` as `{ ...draftRef.current, ...patch }`, and `StudioDesignSurface.onPatch` as `setDraft((d) => ({ ...d, ...patch }))`; each saves through `client.save`, which is `JSON.stringify`. None offers a delete sentinel, so the one way to drop a key is an `undefined`-valued key — the spelling this inspector's own `commitChart` (`chart: next.type ? next : undefined`) and the siblings (`ActionDefaultInspector`, `ObjectDefaultInspector`, `DatasetDefaultInspector`'s "Clear all", pinned by its objectui#9372 suite) already use. Measured in the second pin: after the spread the key is an OWN property holding `undefined` (`Object.hasOwn` true, value `undefined` — not `null`, not an empty string), and `JSON.parse(JSON.stringify(committed))` — the shape `client.save` puts on the wire and the spec parses — has no such property. The spec's refinement itself skips `undefined` (`if (value === undefined || …) continue;` in the joined arm), so even the in-memory draft would not be refused. - Reach: The clear holds for the patch and for the save that follows it. The metadata-admin editor rebuilds its draft as the served draft spread over `layered.effective` (on load, after each save and after publish), and `effective` is the published layer, so a report whose PUBLISHED version was bound gets those keys back in the draft after the first draft save until it is published, and a report already saved `joined` with stale keys is not repaired on load. Both are the host's draft-over-baseline merge, objectui#10765. ## H3 — the spec check Installed `@objectstack/spec` is 17.4.0 (`packages/app-shell/node_modules/@objectstack/spec`). It PREDATES objectstack-ai/objectstack#20160: `grep 'selects per block'` over its `dist/` gives 0 hits; the control `grep 'orders per block'` gives 3. One-off probe with the installed `ReportSchema.safeParse` (not committed): - joined + one block: success - joined + stale `dataset` / `values` / `rows` / `columns` / `chart`: **success** — 17.4.0 accepts them - joined + stale `order`: refused, `custom` at `['order']`, "a `joined` report orders per block — move `order` onto `blocks[]`." - joined + `blocks: []`: refused at `['blocks']` - joined + `runtimeFilter` + `drilldown: false`: success - joined + `dataset: undefined` as an own key: success So the pins' parse leg measures the `order` half with the installed spec (before the fix: refused at `['order']`; after: parses), and for the four selection keys the pins assert ABSENCE and cite the rule read at objectstack `origin/main` `packages/spec/src/ui/report.zod.ts`: `JOINED_CONTAINER_SELECTION_KEYS = ['dataset', 'rows', 'columns', 'values']`, one `custom` issue per present key at `path: [key]`, message "a `joined` report selects per block — move `KEY` onto `blocks[]`, or delete it; on the container it selects nothing." (`KEY` stands for the key's own name.) No checkout on this box holds a built spec `dist` carrying that refusal, and building one was outside this card. ## H4 — edges - `joined` → non-joined: nothing is restored; the patch is `{ type }` alone and the author re-binds (pinned). - non-joined → non-joined: the binding is kept; the patch is `{ type }` alone (control, pinned). - `joined` with `blocks[]` present: untouched, same array reference, in both directions (pinned). - Undo: there is no undo stack behind the metadata draft. `ResourceEditPage`'s `Undo2` button is `doDiscardDraft` (ADR-0034: discard the whole pending draft), not a per-edit undo, and `handleDraftChange` keeps no history. One undo cannot restore the keys; switching the type back does not either. - `runtimeFilter` and `drilldown` survive the switch (pinned): the joined branch reads both. - Partially bound report: the patch names only the keys present (pinned), so `ReportConfigPanel.onFieldChange` sees no phantom clears, and an unbound report's switch stays `{ type: 'joined' }` (pinned). ## Pins and ablations Head `3f53c15776`, pin file plus the two existing `ReportDefaultInspector` suites: `Test Files 3 passed (3) · Tests 37 passed (37)`. Red on base (`1422a920ed`, component untouched, pin file present): `4 failed | 4 passed` — the defect pin, the own-key/serialised pin, the parse leg, the partially-bound pin. The other 4 are controls and boundaries, green on base by construction. Per-hunk ablations on the committed head, each through objectstack's `scripts/ablation-replace.mjs` (anchor must hit exactly once; the blob change is verified on disk; restore proven `blob == HEAD` `a9cb7e9c` and `git diff HEAD` empty), the prediction written before each run, observed direction = predicted: - M1, drop the `joined` guard (`if (nextType === 'joined')` becomes `if (typeof nextType === 'string')`): predicted CONTROL F red; observed `1 failed | 7 passed` (CONTROL: non-joined → non-joined keeps the binding). - M2, drop the presence filter (`if (draft[key] !== undefined) patch[key] = undefined;` becomes unconditional): predicted D and E red; observed `2 failed | 6 passed` (names only present keys; unbound one-key patch). The first attempt was a NO-OP the tool refused — the replacement text was a substring of the anchor, so its on-disk count could not rise (`1 to 1, a rise of 0`) — and it restored; the leg was re-run with a distinct replacement. - M3, add `blocks` to the cleared list: predicted C and G red; observed `2 failed | 6 passed` (parse leg: the joined report loses its blocks; CONTROL: blocks untouched). - M4, drop `order` from the list: predicted A, B and C red; observed `3 failed | 5 passed`. Assertion spelling: every patch is read with `toStrictEqual`, because `toHaveBeenCalledWith` / `toEqual` treat an `undefined`-valued key as absent and would read the defect and the fix alike. Round 2 (`d4945789f6`, the contract-review wording round: the changeset's reach sentence and the pin file's three-host comment; no source line changed — the diff's only non-changeset lines are the comment lines shown by `git diff -U0`, and `ReportDefaultInspector.tsx` is untouched): pin file re-run `Tests 8 passed (8)`. ## Gates (local, derived by hand from `package.json` and the workflows — objectui has no `dispatch-gates.mjs`) - ① closure build `turbo run build --filter='@object-ui/app-shell^...' --concurrency=2`: `Tasks: 28 successful, 28 total`, 2m12s, under the verify lock. - ② `pnpm --filter @object-ui/app-shell type-check` (`tsc --noEmit && tsc -p tsconfig.test.json`): exit 0; `tsc -p tsconfig.test.json --listFiles` lists the pin file (1 hit among 4673 files). - ② vitest, the three files above: 37 passed; round 2 pin file: 8 passed. - `node scripts/check-changeset-presence.mjs`: ✅ (round 2: 2 source files of 1 released package changed, 1 changeset declared). `node scripts/check-changeset-no-major.mjs`: ✅. `pnpm check:changeset-claims`: ✅. `pnpm check:pending-changeset-literals`: ✅. - `pnpm check:control-bytes`: ✅ OK (round 2: 8942 tracked text files). The role file's control-byte grep over the changed files: no match. - `pnpm check:new-line-citations`: `0 new citation(s)` (both rounds). - `pnpm check:test-path-roots`: ✅ OK. `pnpm check:vi-mock-specifiers`: ✅ OK. - eslint with the package config on the two touched `.tsx` files: exit 0. - NOT MEASURED locally, CI-owned: `pnpm lint` (repo-wide), the 8 test shards, `Build & E2E`, `check:i18n-*` (no `t()` key was added or changed). Round 1 CI on `3f53c157`: 40 success, 3 skipped, 0 failed per the contract review. ## Serial Round 2: `origin/main` at `25c7d584e4` (+5 commits over BASE `1422a920ed`: objectstack-ai#10752, objectstack-ai#10753, objectstack-ai#10708, objectstack-ai#10751, objectstack-ai#10761); none touches the three files; `git merge-tree --write-tree` exits 0 (clean, tree `738f2bf534`). No merge commit was needed. ## Acceptance notes - `chart` is cleared per the triage direction and objectstack-ai/objectstack#20161 (inert on a joined container); the installed spec does not refuse it, so its clearing is pinned by absence only. - Deviation: the worktree-creating `git fetch origin main` ran against the shared checkout (the role-file recipe) rather than inside the worktree as the order asked; `origin/main` did not move (`1422a920ed` before and after), only the shared checkout's `FETCH_HEAD` was touched. Every later fetch used a private ref inside the worktree. - Observation, not filed: `ReportConfigPanel` hosts this inspector too, merges with the same spread and emits `onFieldChange(key, undefined, next)` once per cleared key — the documented "field changed" signal, correct for a live preview. - The draft-over-baseline merge that re-introduces a published binding after the first draft save is objectui#10765 (filed by the seat), not addressed here. --- _Generated by [Claude Code](https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #7469
Clause-②: yes
This PR carries out the maintainer's ruling C on objectui#7469 (recorded in comment
5828532177). The maintainer chose 「C 退役 actions[] (Recommended)」: "one channel, one contract". App-level actions arenavigationitems oftype: 'action'. The free-formactionsarray on the app node,AppAction,AppActionSchemaand the runner's rendering of them are retired.The work was done by the
domain:spec#1dispatch under claim5852900400, sessionhttps://claude.ai/code/session_014fWVhLzhxR8qrFsJ5o8TYW.What changed
@object-ui/types(contract face).AppComponentSchema.actionsis nowactions?: neveron the TS face andretirementTombstone(APP_ACTIONS_REFUSAL)on the zod mirror. An authored array is refused with ONEinvalid_typeissue atactions. The message names the remedy in the spec's own spelling:navigation: [{ id, type: 'action', label, actionDef: { actionName } }].AppAction(TS) andAppActionSchema(zod) are deleted, together with their exports from.and./zod. The unexportedAppActionSchemaTypeis deleted too.AppMenuItem/MenuItemSchemastay, because the legacymenustill reads them (H3). The objectui#7719shortcutrefusal on them is unchanged in behaviour. Its guidance text and docblock namedAppAction.items/AppAction.shortcut, and this change would have made that text false, so it now names themenuitem instead.@object-ui/runner.LayoutRendererno longer reads the app'sactions. The'button'arm, the'user'avatar-menu arm with its items, and the Bell'sactionsgate are gone. The chrome now has one rule: the Bell is always drawn, which is what an app with no'button'action got before. The now-unused dropdown and avatar imports are removed.Docs.
content/docs/core/app-schema.mdx"Global Actions" is rewritten to teach anavigationitem oftype: 'action'. The example is spec-valid. The section says the console sidebar dispatches the item by action name and that the runner renders nonavigationitems.actions:examples on that page are removed, along with the "keyboard shortcuts" best practice (that key retired withAppAction).content/docs/guide/schema-overview.mdloses its twoactionsexamples and the user-menu bullet.Changeset.
.changeset/7469-app-actions-retired.mddeclaresminorfor@object-ui/typesand@object-ui/runner. The runner is published ("private": false) and is in the fixed group. There is nomajor.Mechanism hypotheses, measured
01700dcd7(the declarations and the three runner arms were where the dispatch said), and it was re-read before editing.AppComponentSchemaextends.passthrough()BaseSchema, so a deleted member would be KEPT in silence. The tombstone route follows from that. Block (d) of the new pin reads it:AppMirror.omit({ actions: true })parses the array green and keeps it.AppMenuItemhas another reader (AppComponentSchema.menu,menuItemToNavigationItem, the runner's sidebar), so it stays. No menu-item type was used only byAppAction.LayoutRenderer.appActionItems-6854.test.tsxwas retired with the arms. The new rule is pinned inLayoutRenderer.chrome-7469.test.tsx.origin/maine2c4e12, 0 of the 4 app-bearing files that carrynavigation:writeactions:. The control is that the sameactions: [matcher does hit one file underexamples/. In objectui, the onlyactions:on an app node were two docs pages, the runner pin and one types fixture. All are removed or re-pointed; the new retirement pin keeps one on purpose, under@ts-expect-error.origin/maine2c4e12AppSchemais astrictObjectwhose body has noactions(0 hits; the controlnavigationhas 26). On the installed spec 17.4.0,AppSchema.safeParseof an app withactionsgivesunrecognized_keys[actions], and the same app with anavigationaction item parses green. Pin block (c) covers this. There is no spec change.Verification (final head
52e5d79278, merge ofmainat704e05b09)pnpm exec vitest run --maxWorkers=2 packages/types/ packages/runner/passed: 250 files, 5351 tests, lockVERDICT command-exit 0.VERDICT command-exit 0, each against a freshly rebuilt dependency closure:pnpm --filter @object-ui/types type-check(three programs);pnpm --filter @object-ui/runner type-check;pnpm --filter @object-ui/app-shell type-check.@object-ui/app-shellwas type-checked green. Its one file namingAppComponentSchema(useNavigationSync.ts) passed its test (1 file, 12 tests). The nav-action dispatch and layout suites also passed (useNavActionDispatch.test.tsxpluslayout/__tests__/: 36 files, 250 tests).AppAction/AppActionSchemaoutsidepackages/types. The census test asserts this.actionson anAppComponentSchemaliteral failedtsc -p tsconfig.test.jsonwith TS2322 ("not assignable to type 'undefined'"). That proves the runner compiles against the rebuilt.d.ts. The file was removed afterwards and the tree was clean.git hash-object, restored fromHEAD, andgit diff HEADread empty after each leg):tsc -p tsconfig.test.jsonraised TS2578 twice (on theAppActionimport directive and theactionsdirective) and TS2344 on theEqualassertion.52e5d79278:check-changeset-presence,check-changeset-no-major,check-changeset-fixedandcheck-changeset-overwrite. The report-only gatescheck:changeset-claimsandcheck:pending-changeset-literalsran too.check:new-line-citations(0 new),check:control-bytes,check:spec-symbols,check:test-path-roots,check:handler-key-reads,check:readme-exports,check:phantom-deps,check:unreferenced-sources,check-type-check-coverageandcheck:prompt-keys.check:doc-types,check:doc-snippets(676 of 676 blocks),check:doc-examples,check:doc-fences,check:doc-example-ids,check-doc-expression-carriage,check-doc-linksandcheck:doc-example-readers.check-governed-queue-guard --testover the 18 diff paths: NOT GOVERNED.--no-inline-configon the 13 touched TS/TSX/MJS files (the file count is read from--format json): 4 errors and 9 warnings. The per-file, per-rule tally is identical to the same files at the merge base, so there are 0 differences. The 4 errors are pre-existing sites that carry inline disables. The config is not type-aware (noparserOptions.projectorprojectService), so this diff cannot change the verdict on any untouched file.mainand injected into the virtual store, which is CI's job on this PR. The one coordinate this diff moves on a spec-derived type isactions, and objectstackmaine2c4e12does not declare it (H6), so the gate compiles this change the same way againstmainas against the pin.Acceptance notes
app-action-onclick-refusal-6854.test.tsis deleted. It pinnedAppActionSchema.onClickonly.app-menu-item-shortcut-refusal-7719.test.tsis re-pointed. Its control onAppAction.shortcutis dropped, its message assertion now namesAppMenuItem, and its authoring-position block runs throughAppComponentSchema.menu.handler-keys-string-any-mirrors-7344.test.tsis updated:z.objectdeletion probe went with its only instance. The same strip is still measured onMenuItemSchemain the 7719 pin.component-docs-retired-handler-keys-7340.test.tsrecords the census move from 27 to 26, andapp.tsleaves it.zod-mirror-parity.test.tsloses only this pair's rows and imports.EXPECTED_MIRROR_PAIRSgoes from 160 to 159, per its own instruction.phase2-schemas.test.tsswaps itsactionsfixture for anavigationaction item.scripts/check-doc-component-types.mjsgets oneDOC_TYPE_EXEMPTIONSentry,actiononcore/app-schema.mdx, because it is a NavigationItem kind and not a node. The script's 18 suites passed: 689 tests, exit 0.AppActionas it was.check:changeset-claims(report-only) lists.changeset/7760-lazy-mirror-input-type-args.md, which names the deleted 6854 test file. The 6854, 7344, 7719 and 7721 entries also describeAppAction. They were not edited, becausecheck-changeset-overwriteguards other cards' changesets. This PR's changeset says in its own body that they predate the retirement.menuonly and renders nonavigationitem. After this change it has no way to show app-level actions. That is the ruling's stated cost (「独立运行器失去头部按钮和用户菜单」), and the docs say it in plain words.NavItemreads amenuitem'spath,label,iconandchildren. It does not read the declaredAppMenuItem.href("External Link"). app-shell honourshrefthroughmenuItemToNavigationItem. This comes from reading the source only; no producer or public entry point was measured. Carrier: none.claude/issue-7469-app-shell-global-actionswas not touched.Generated by Claude Code