Skip to content

Commit 14a8bd2

Browse files
feat(spec)!: marketplace:installed-list is retired — refused by name at the schema door; the separate Installed Apps view it drew was retired (#24255)
Fixes #24230 Clause-②: no (narrowing) Retires the page component type `marketplace:installed-list`, refused by name through `RETIRED_PAGE_COMPONENT_TYPES`. This executes ruling B on #23892 (record `6216295104`, item 2: "the separate Installed Apps view retires once the filtered view serves its actions"), within triage grade `6243387750` and release `6245014494`. objectstack-ai/objectui#12926 (merged `35244b6379`) deleted the type's one renderer. Its one producer, `@objectstack/cloud-connection`'s `marketplace_installed` page, had already left `MARKETPLACE_INSTALLED_UI_BUNDLE` (#23933). The shape follows the `record:json` retirement (PR #24170, `030a7b83e`) surface for surface, with the differences listed below. Dev session: `session_01SovhHbD8dAQGDyEGHRoSUx` (cloud dispatch from the `domain:spec#1` seat, claim `6245064045`). ## Accept set, before → after, at each door | door | before (`cbfa5e4335`) | after (this head) | | --- | --- | --- | | `PageComponentSchema.type` (node) | accepts `{ type: 'marketplace:installed-list' }` through the open string arm | refuses: `code: 'custom'`, `path: ['type']`, `params.retiredComponentType: 'marketplace:installed-list'`, message = the prescription. Bare, `{}`-bag and keyed alike | | `PageSchema` (what `os validate` and save parse) | accepts the node | refuses at `regions[i].components[j].type`, one issue per node | | `ComponentPropsMap['marketplace:installed-list']` (props gate, `check-yaml-examples`, vocabulary) | strict empty `emptyProps(...)`: `{}` passes, any key refused | row kept as `retiredComponentProps(...)` (`z.never`): every bag refused, `{}` included, `invalid_type` at `[]` with the prescription | | lint `component-type-unknown` | silent (open-arm plugin namespace) | `error` at `…type`; message = the prescription's head, hint = its "Delete the …" sentence (193 chars, under the one-line 200 cap) | | lint `component-props-*` | `component-props-unknown-key` on a keyed bag, silent on `{}` | `component-props-invalid` `error` at `…properties` for every bag | | typo suggester | candidate | no longer a candidate (out of `KNOWN_COMPONENT_TYPE_CANDIDATES`, derived from the map) | | print pages | `PRINT_REFUSED_PAGE_COMPONENT_TYPES` row | row removed: a retired type is refused at the parse before any print rule reads it (the map's own docblock rule) | | `PageComponentType` enum | never a member | unchanged: nothing to remove | | exports | none of its own (`emptyProps` mints no def) | unchanged | The open string arm stays open, the `marketplace:` namespace included (`marketplace:installed-list-v2` still parses; pinned). Everything else is byte-identical. The prescription is one string at all doors. Its fix sentence names the route the console uses: "Delete the `marketplace:installed-list` component node; to link to that view, add a `component` navigation item with `componentRef: 'developer:packages'` and `params: { source: 'marketplace' }`." That is the spelling `MARKETPLACE_INSTALLED_UI_BUNDLE`'s own "Installed Apps" entry uses, and it validates against `app.zod.ts`'s `component` navigation item. It carries no tracker number (`check:doc-authoring`). ## Where this differs from the `record:json` precedent | surface | `record:json` | here | | --- | --- | --- | | enum value | removed from `PageComponentType` | n/a, never a member. The map entry is therefore the only parse door. Without it, the retired name would fall back to the open arm and parse in silence (pinned) | | row | `retiredComponentProps` | same, and here the kept row is the only thing that keeps the name KNOWN (pinned in `component-type-vocabulary.test.ts`) | | props def / `retired-defs` entry | `RecordJsonProps` deleted, `retired-defs/18.ui__RecordJsonProps.ts` | none: `emptyProps` exports nothing, so no def leaves the published set (H4 measured: `check:generated` moved only `content/docs/references/ui/page.mdx`) | | D3 entry and step-18 fragment | `ui-record-json-retired` | `ui-marketplace-installed-list-retired`, inserted at its sorted id, `order: 102` (max 101 + 1) | | sibling D3 entries | none | `ui-cloud-connection-widgets-unknown-keys-refused` amended, `ui-mcp-connect-agent-unknown-keys-refused` one clause (decision below) | | whole-bag census | `component-record-related-list-parent-23134.test.ts` gained `record:json` | gains `marketplace:installed-list` | | lint pins | `validate-record-json-23745.test.ts` flipped | `validate-component-types.test.ts`: the accept row is removed (the map-driven `flags %s` case now covers it); `validate-component-props.test.ts`: the keyed-bag and `{}` cases flipped into one whole-bag refusal pin | | tree-scoped absence walk | new, in `vitest.repo-tests.json` | same, in the new pin file, registered in `vitest.repo-tests.json` | | `Clause-②` | `no (narrowing)` (shipped in one prerelease only) | `no (narrowing)` too: the type shipped in stable 17.3.0, which decides the ADR-0087 disposition and the FROM → TO text, not the value. The diff widens nothing, and `yes (narrowing)` would claim a widening (contract review `6245719006` ②) | ## The two `semantic/18.*` entries: amended, not superseded Measured: both entries are in the same major-18 step as the new retirement entry, so an upgrader replaying 17 → 18 reads all three. `ui-cloud-connection-widgets-unknown-keys-refused`'s `acceptanceCriteria` said "Every `cloud-connection:panel` / `marketplace:installed-list` node authors an empty bag and validates clean". After this PR that sentence is false for the second type, and it contradicts the retirement entry. Its id is referenced by shipped CHANGELOG text and by `packages/cli/test/migrate-meta-engine-guidance.test.ts`'s guidance census. So the entry is amended in place: id kept, `surface`, `replacement` and `acceptanceCriteria` narrowed to `cloud-connection:panel`, and `reason` names the retired sibling and the new entry. Superseding it (new id, old entry deleted or left stale) would either drop a referenced id or leave contradictory text in one step. `ui-mcp-connect-agent-unknown-keys-refused` only named the row historically, as the precedent for its own row. One clause now says that row was since retired by name. No guidance changes. ## H2 / H3 / stop conditions, measured - **H1 (premise):** true on `origin/main` `cbfa5e4335` (the dispatch read `af284b05e5`): the row (`component.zod.ts`), the docblocks, the print row (`page.zod.ts`) and the lint accept pins were all present. - **H2 (producers):** no stored-row producer. The `MARKETPLACE_INSTALLED_UI_BUNDLE` carries no `pages`. Outside the spec and tests, the string survives in CHANGELOGs, `content/docs/releases/v17/17-3.mdx` (release-owned, untouched), `packages/spec/liveness/page.json` (a historical read-point note at an older objectui sha, untouched), a doc comment in `packages/cloud-connection/src/canonical-expression-envelopes.test.ts` (hold-off, untouched) and the CLI fixture below. So no D2 conversion is registered. - **CLI fixture:** `packages/cli/test/platform-page-i18n-parity.test.ts`'s `SAMPLE_PAGE` does not parse the page (no `PageSchema` read in the file), so it did not fail. It did author a `{ type: 'marketplace:installed-list' }` node inside the new absence walk's radius, so its body block is now `element:divider`. The gate reads only the header copy. 27/27 pass. - **H3 (Console Pin Gate):** `git grep` at the objectui pin `5e15928b46`: the type appears only as a string registration (`InstalledListWidget.tsx`), a CLI known-types string list, tests, and the parity test's `UNJUDGED_SPEC_BLOCKS` entry. There is no typed read of `ComponentPropsMap['marketplace:installed-list']` in non-test source. The row's KEY is kept, so `keyof typeof ComponentPropsMap` and the parity partition's key set are unchanged. Removing the key cannot break the pinned build because the key is not removed. The parity entry's "EMPTY SPEC SHAPE" text becomes "RETIRED UPSTREAM" in objectui's own pin bump, per the card. ## Ablation (fix committed first, `scripts/ablation-replace.mjs`, restored by blob) The subject resolves by relative import (`./page.zod`, `./component.zod`) to `src`, so no `dist` rebuild is involved. - **Mutation:** the map key renamed to `marketplace:installed-list-ABLATED` (held), and the row turned back into `emptyProps('marketplace:installed-list')`. On-disk counts: 1 and 1. - **Result:** retirement pin 4 failed / 6 passed (the map, the row, `PageComponentSchema`, `PageSchema`); vocabulary pins 2 failed / 20 passed. - **Restore:** both files back to their `HEAD` blobs (`9146237b28f7`, `0365abe5e2cf`) by hash, and `git diff HEAD` is empty. ## Gates (local, head `fc6245ba2`) - spec `build` exit 0. `check:generated`: `✓ All 14 generated artifacts are up to date` (only `gen:docs` moved: `content/docs/references/ui/page.mdx`, 2 lines, the regenerated `.describe()`). - spec `test` (`vitest --project local`): 693 files, 692 passed and 1 failed: the whole-bag census in `component-record-related-list-parent-23134.test.ts`, which this PR then updated; that file re-run 17/17. 21001 tests passed, 1 todo. spec `test:repo` scoped to the new repo-project file: 10/10. spec `typecheck` exit 0, `check:test-typecheck: OK`. - lint: closure built, `test` 143 files, 6820 passed, 5 skipped; `typecheck` exit 0. - cli unit: `test/platform-page-i18n-parity.test.ts` 27/27. - `check-platform-checklist --changed-from origin/main`: OK, 0 anchors lost. - `dispatch-gates --commands`: 111 commands, each run with its exit code captured before any pipe. 107 exited 0; `check:skill-examples` exited 3 (PREREQUISITE NOT MET: `client-react` dist built without DTS by this run's own CLI closure build) and was re-run green after a DTS build (262 examples). NOT MEASURED, declared to CI: - `check:dts-closure` (exit 1, missing `.d.ts` in 58 packages that this run built with `OS_SKIP_DTS=1` for the CLI unit test; the diff touches no build config); - `check:dual-build-cjs-loads` (exit 3, PREREQUISITE NOT MET: needs a whole-repo build); - `check-plugin-teardown-shape.mjs --self-test` (exit 3: its fixture needs an unshallow clone; checker-health only). - Changeset gates, re-run on head `603c1a10c` after patch round 1 (report `6245768083`), exit codes captured before any pipe. `check-adr-0087-registration`, exit 0: "✓ check-adr-0087-registration: 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition." and "[BREAKING+bang+clause-②-narrowing] registered ui-marketplace-installed-list-retired (new here: ui-marketplace-installed-list-retired)". `check-changeset-no-major` with this body as the `--event` payload, exit 0: "✓ This diff introduces no `major` bump." and "✓ LEVEL AXIS: this PR declares clause-② `no (narrowing)`, and no package whose published source (`src/**`, a packed `bin` target, or a repo file its build copies in) it moves is graded `patch`." Both carriers, this body and the changeset, read `Clause-②: no (narrowing)` at line start (contract review `6245719006` ②). ## Acceptance notes - `packages/spec/liveness/page.json`'s `regions` note still cites the widget's read point at an older objectui sha. It is historical evidence and was not edited here (ledger edits carry their own `verifiedAt` discipline). - objectui's `registry-inputs-spec-parity.test.ts` entry and `packages/cli/src/utils/known-schema-types.ts` (objectui) list the string. Both are objectui's pin-bump follow-up, per the card. `.objectui-sha` is not moved. - `packages/cloud-connection` (hold-off) is untouched; its doc comment already records the page retirement. - `content/docs/releases/` and every `CHANGELOG.md` are untouched. --- _Generated by [Claude Code](https://claude.ai/code/session_01SovhHbD8dAQGDyEGHRoSUx)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent fb6fb24 commit 14a8bd2

17 files changed

Lines changed: 523 additions & 75 deletions
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec)!: `marketplace:installed-list` is retired — refused by name at the schema door; the separate Installed Apps view it drew was retired, and Setup → Packages filtered to Source = marketplace replaces it (#24230, ADR-0049)
6+
7+
Clause-②: no (narrowing)
8+
9+
<!-- adr-0087: registered ui-marketplace-installed-list-retired -->
10+
11+
**BREAKING** — an accept-set narrowing on a published authoring surface, shipped as `minor` under the repo's launch-window convention for accept-set narrowings (the `user:profile`, `element:filter`, `element:form`, `ai:chat_window` and `record:json` retirements shipped the same way).
12+
13+
`marketplace:installed-list` was the plugin console widget that drew the separate Installed Apps view. It was published in 17.3.0 with an empty `ComponentPropsMap` row and was never a `PageComponentType` member: it reached the page schema through the type union's open string arm. The separate view was then retired, because Setup → Packages carries the same packages and their actions behind a Source = marketplace filter. The widget's one page, `marketplace_installed`, had already left `@objectstack/cloud-connection`, whose "Installed Apps" navigation entry opens the filtered Packages view instead, and the console deleted the widget's one renderer. After that, an authored node validated clean and then drew "Unknown component type" in front of a user. A declared element nothing draws is a capability claim nothing keeps, so it is retired under ADR-0049 enforce-or-remove and refused by name, the `ai:chat_window` way.
14+
15+
**What is refused:** an authored `marketplace:installed-list` component node, at `PageComponentSchema.type`. That covers `definePage()`, `PageSchema`, and every door that parses pages: `os validate`, `os build`, `os lint` and the metadata save door. The issue is located at the node's own path, with `code: 'custom'` and `params.retiredComponentType`, and its message is the retirement prescription. `ComponentPropsMap['marketplace:installed-list']` stays as a row, because for a type outside the enum the row is the only thing that keeps the name known to the component-props gate, `check-yaml-examples` and the type vocabulary. The row now refuses every props bag, `{}` included, with the same prescription. One prescription string, `RETIRED_PAGE_COMPONENT_TYPES` in `@objectstack/spec/ui`, answers at every door. The authoring rule `component-type-unknown` reports the node by name, and the typo suggester no longer offers it.
16+
17+
**What else moves:** its print classification row (`PRINT_REFUSED_PAGE_COMPONENT_TYPES`) leaves with it, because a retired type is refused at the parse before any print rule reads it. The D3 entry `ui-cloud-connection-widgets-unknown-keys-refused`, which closed this type's props bag to `{}` in the same major, now speaks for `cloud-connection:panel` alone, so the upgrade guide no longer tells an upgrader to keep a node that is refused. No export is removed: the row was built by the `emptyProps` factory, which exports no def, so no JSON Schema leaves the published set.
18+
19+
**What stays accepted:** every other `ComponentPropsMap` row, byte-identically, including the sibling console widgets `cloud-connection:panel` and `mcp:connect-agent`. The open string arm also stays open: custom and plugin-registered types keep parsing, the `marketplace:` namespace included. The only string refused is the retired name itself.
20+
21+
## FROM → TO
22+
23+
| you wrote | write instead |
24+
|:--|:--|
25+
| a `{ type: 'marketplace:installed-list' }` component node in a page region, slot or container | nothing: delete the node. The Installed Apps list is Setup → Packages filtered to Source = marketplace |
26+
| a page whose only job was to show that list | a navigation item `{ type: 'component', componentRef: 'developer:packages', params: { source: 'marketplace' } }`, which is what `@objectstack/cloud-connection`'s own "Installed Apps" entry does |
27+
28+
The one-line fix: delete the `marketplace:installed-list` component node, and link to the filtered Packages view if users need to reach it. No ADR-0087 conversion is registered, because the only edit is deleting an authored page node, and a mechanical conversion does not delete page nodes: which region closes up is a layout decision. The D3 entry `ui-marketplace-installed-list-retired` carries that delegation, so `os migrate meta --from 17` lists it as a manual change for every stack that still names the type.
29+
30+
## Who is affected, measured
31+
32+
- **objectstack** at `cbfa5e4335`: zero authored `marketplace:installed-list` nodes in shipped metadata. `@objectstack/cloud-connection` serves no page for it, and its pin test asserts that. The other hits were the spec's own row, print row and docblocks, its tests, two lint pins, one synthetic test fixture in `packages/cli` (now a live block) and the release-owned changelogs. A tree-scoped absence pin now holds that zero.
33+
- **objectui** at the console pin `.objectui-sha` `5e15928b46`: the type is registered by string only, with no typed read of `ComponentPropsMap['marketplace:installed-list']`. The row's key is kept, so the console build at the pin is unaffected. objectui `main` has deleted the renderer.
34+
- **Deployed metadata** was not measured.
35+
36+
The retirement kit:
37+
38+
- the retired-type map entry and the print classification row removed (`packages/spec/src/ui/page.zod.ts`), the row turned into a whole-bag refusal (`packages/spec/src/ui/component.zod.ts`), and the vocabulary docblock updated (`component-type-vocabulary.ts`)
39+
- the D3 semantic entry `ui-marketplace-installed-list-retired` and its step-18 rationale fragment; the D3 entries `ui-cloud-connection-widgets-unknown-keys-refused` (narrowed to `cloud-connection:panel`) and `ui-mcp-connect-agent-unknown-keys-refused` (one clause) amended
40+
- pin tests: `component-marketplace-installed-list-retirement.test.ts` pins `code`, `path`, `params` and the first sentence at each door (the map, the row with empty and keyed bags, `PageComponentSchema`, `PageSchema` at the element path), the absence from the enum and the print rows, the D3 registration and the narrowed sibling entry, with `cloud-connection:panel` as the live control, plus a tree-scoped absence walk; `component-type-vocabulary.test.ts` pins the type known only through its row, out of the typo candidates, and `marketplace:` still unreserved; the lint accept pins in `validate-component-types.test.ts` and `validate-component-props.test.ts` are flipped into refusal pins
41+
- generated docs follow the schema: the reference page for `ui/page`, `spec-changes.json` and the upgrade guide

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ View filter rule
293293

294294
| Property | Type | Required | Description |
295295
| :--- | :--- | :--- | :--- |
296-
| **type** | `Enum<'page:header' \| 'page:footer' \| 'page:sidebar' \| 'page:tabs' \| 'page:accordion' \| 'page:card' \| 'page:section' \| 'record:details' \| 'record:highlights' \| 'record:related_list' \| 'record:activity' \| 'record:chatter' \| 'record:discussion' \| 'record:path' \| 'record:alert' \| 'record:quick_actions' \| 'record:reference_rail' \| 'record:history' \| 'record:approval_decision' \| 'record:approvals' \| 'record:attachments' \| 'app:launcher' \| 'nav:menu' \| 'nav:breadcrumb' \| 'global:search' \| 'global:notifications' \| 'ai:suggestion' \| 'element:text' \| 'element:number' \| 'element:image' \| 'element:divider' \| 'element:button' \| 'element:record_picker' \| 'element:text_input'> \| string` | ✅ | Component Type — a standard vocabulary member, or a custom/registered component type in its own namespace (e.g. `object-grid`, `mcp:connect-agent`). The spec's own type namespaces are a closed vocabulary at author time: inside them, a type the vocabulary does not declare is refused by `os validate` / `os build` / `os lint` (rule `component-type-unknown`); a type the vocabulary RETIRED by name (`user:profile` — shell chrome, not author-placeable; `ai:chat_window` — no renderer by design, the floating chat overlay is the AI chat entry point; `element:filter` and `element:form` — retired whole, no renderer for either ever shipped; `record:json` — the raw-JSON panel it was declared for was dropped) is refused at the parse itself, with the retirement prescription. |
296+
| **type** | `Enum<'page:header' \| 'page:footer' \| 'page:sidebar' \| 'page:tabs' \| 'page:accordion' \| 'page:card' \| 'page:section' \| 'record:details' \| 'record:highlights' \| 'record:related_list' \| 'record:activity' \| 'record:chatter' \| 'record:discussion' \| 'record:path' \| 'record:alert' \| 'record:quick_actions' \| 'record:reference_rail' \| 'record:history' \| 'record:approval_decision' \| 'record:approvals' \| 'record:attachments' \| 'app:launcher' \| 'nav:menu' \| 'nav:breadcrumb' \| 'global:search' \| 'global:notifications' \| 'ai:suggestion' \| 'element:text' \| 'element:number' \| 'element:image' \| 'element:divider' \| 'element:button' \| 'element:record_picker' \| 'element:text_input'> \| string` | ✅ | Component Type — a standard vocabulary member, or a custom/registered component type in its own namespace (e.g. `object-grid`, `mcp:connect-agent`). The spec's own type namespaces are a closed vocabulary at author time: inside them, a type the vocabulary does not declare is refused by `os validate` / `os build` / `os lint` (rule `component-type-unknown`); a type the vocabulary RETIRED by name (`user:profile` — shell chrome, not author-placeable; `ai:chat_window` — no renderer by design, the floating chat overlay is the AI chat entry point; `element:filter` and `element:form` — retired whole, no renderer for either ever shipped; `record:json` — the raw-JSON panel it was declared for was dropped; `marketplace:installed-list` — the separate Installed Apps view it drew was retired, Setup → Packages filtered to Source = marketplace replaces it) is refused at the parse itself, with the retirement prescription. |
297297
| **id** | `string` | optional | Unique instance ID |
298298
| **label** | `string \| Record<string, string>` | optional | Display label — the default-language string, or an inline locale map (`{ en, "zh-CN" }`) resolved at render time |
299299
| **properties** | `Record<string, any>` | optional (default: `{}`) | Component props passed to the widget. See component.zod.ts for schemas. |
@@ -432,7 +432,7 @@ Print declaration: paper size, orientation, margins, the running header and foot
432432

433433
| Property | Type | Required | Description |
434434
| :--- | :--- | :--- | :--- |
435-
| **type** | `Enum<'page:header' \| 'page:footer' \| 'page:sidebar' \| 'page:tabs' \| 'page:accordion' \| …> \| string` | ✅ | Component Type — a standard vocabulary member, or a custom/registered component type in its own namespace (e.g. `object-grid`, `mcp:connect-agent`). The spec's own type namespaces are a closed vocabulary at author time: inside them, a type the vocabulary does not declare is refused by `os validate` / `os build` / `os lint` (rule `component-type-unknown`); a type the vocabulary RETIRED by name (`user:profile` — shell chrome, not author-placeable; `ai:chat_window` — no renderer by design, the floating chat overlay is the AI chat entry point; `element:filter` and `element:form` — retired whole, no renderer for either ever shipped; `record:json` — the raw-JSON panel it was declared for was dropped) is refused at the parse itself, with the retirement prescription. |
435+
| **type** | `Enum<'page:header' \| 'page:footer' \| 'page:sidebar' \| 'page:tabs' \| 'page:accordion' \| …> \| string` | ✅ | Component Type — a standard vocabulary member, or a custom/registered component type in its own namespace (e.g. `object-grid`, `mcp:connect-agent`). The spec's own type namespaces are a closed vocabulary at author time: inside them, a type the vocabulary does not declare is refused by `os validate` / `os build` / `os lint` (rule `component-type-unknown`); a type the vocabulary RETIRED by name (`user:profile` — shell chrome, not author-placeable; `ai:chat_window` — no renderer by design, the floating chat overlay is the AI chat entry point; `element:filter` and `element:form` — retired whole, no renderer for either ever shipped; `record:json` — the raw-JSON panel it was declared for was dropped; `marketplace:installed-list` — the separate Installed Apps view it drew was retired, Setup → Packages filtered to Source = marketplace replaces it) is refused at the parse itself, with the retirement prescription. |
436436
| **id** | `string` | optional | Unique instance ID |
437437
| **label** | `string \| Record<string, string>` | optional | Display label — the default-language string, or an inline locale map (`{ en, "zh-CN" }`) resolved at render time |
438438
| **properties** | `Record<string, any>` | optional (default: `{}`) | Component props passed to the widget. See component.zod.ts for schemas. |

‎packages/cli/test/platform-page-i18n-parity.test.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,9 @@ describe('`pages.*` default-locale parity (#8764, moved off the nav roster by #1
10861086
// verdict moved without them is a verdict nobody has seen fail. The
10871087
// sample is a synthetic page shaped like the retired `marketplace_installed`
10881088
// page (#23933); it is a fixture, and no shipped population carries it.
1089+
// Its header copy is all the gate reads, so its body is a live block: the
1090+
// retired page's own `marketplace:installed-list` widget is refused by
1091+
// name since #24230.
10891092
const SAMPLE_PAGE = {
10901093
name: 'marketplace_installed',
10911094
label: 'Installed Apps',
@@ -1102,7 +1105,7 @@ describe('`pages.*` default-locale parity (#8764, moved off the nav roster by #1
11021105
},
11031106
],
11041107
},
1105-
{ name: 'main', components: [{ type: 'marketplace:installed-list', properties: {} }] },
1108+
{ name: 'main', components: [{ type: 'element:divider', properties: {} }] },
11061109
],
11071110
};
11081111
const SAMPLE_SOURCES = new Map([[SAMPLE_PAGE.name, pageSourceCopy(SAMPLE_PAGE)]]);

‎packages/lint/src/validate-component-props.test.ts‎

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,11 +1183,17 @@ describe('validateComponentProps — record:alert / record:quick_actions / recor
11831183
* open-string-arm types — never in `PageComponentType` — so nothing else
11841184
* judged them either), and the walker's unregistered-type skip swallowed the
11851185
* whole props bag: any authored key produced ZERO findings from
1186-
* validate/build. Their rows are strict and EMPTY — measured from the
1187-
* renderers' read points at the `.objectui-sha` pin, where both registrations
1188-
* discard the schema node (`() => <Widget />`) — so EVERY authored key is a
1189-
* finding. Remove either map row and its loud test here goes back to that
1190-
* silence.
1186+
* validate/build. `cloud-connection:panel`'s row is strict and EMPTY —
1187+
* measured from the renderer's read points at the `.objectui-sha` pin, where
1188+
* the registration discards the schema node (`() => <Widget />`) — so EVERY
1189+
* authored key is a finding. Remove the map row and its loud test here goes
1190+
* back to that silence.
1191+
*
1192+
* #24230 — `marketplace:installed-list` is RETIRED by name (its one renderer
1193+
* was deleted with the separate Installed Apps view). Its row is kept as a
1194+
* whole-bag refusal, so the same dispatch now refuses EVERY bag, `{}` included,
1195+
* as `component-props-invalid`; the type rule reports the node too (the
1196+
* map-driven `flags %s` case in `validate-component-types.test.ts`).
11911197
*/
11921198
describe('validateComponentProps — cloud-connection:panel / marketplace:installed-list are dispatched (#11575)', () => {
11931199
it('reports any key authored on `cloud-connection:panel`, naming the zero-prop surface', () => {
@@ -1204,25 +1210,19 @@ describe('validateComponentProps — cloud-connection:panel / marketplace:instal
12041210
expect(findings[0].message).toContain('cloud-connection:panel');
12051211
});
12061212

1207-
it('reports any key authored on `marketplace:installed-list` the same way', () => {
1208-
const findings = validateComponentProps(
1209-
stackWith([{
1210-
type: 'marketplace:installed-list',
1211-
properties: { filter: 'installed' },
1212-
}]),
1213-
);
1214-
expect(findings).toHaveLength(1);
1215-
expect(findings[0].rule).toBe(COMPONENT_PROPS_UNKNOWN_KEY);
1216-
expect(findings[0].where).toBe('page "probe_page" · marketplace:installed-list');
1217-
expect(findings[0].message).toContain('`filter`');
1213+
it('refuses the retired `marketplace:installed-list` bag WHOLE, `{}` included, as an error', () => {
1214+
for (const properties of [{}, { filter: 'installed' }]) {
1215+
const findings = validateComponentProps(
1216+
stackWith([{ type: 'marketplace:installed-list', properties }]),
1217+
);
1218+
expect(findings.map((f) => [f.rule, f.severity])).toEqual([[COMPONENT_PROPS_INVALID, 'error']]);
1219+
expect(findings[0].where).toBe('page "probe_page" · marketplace:installed-list');
1220+
}
12181221
});
12191222

1220-
it('stays silent on the empty bag both plugin-shipped pages author', () => {
1223+
it('stays silent on the empty bag the cloud-connection page authors', () => {
12211224
const findings = validateComponentProps(
1222-
stackWith([
1223-
{ type: 'cloud-connection:panel', properties: {} },
1224-
{ type: 'marketplace:installed-list', properties: {} },
1225-
]),
1225+
stackWith([{ type: 'cloud-connection:panel', properties: {} }]),
12261226
);
12271227
expect(findings).toEqual([]);
12281228
});

‎packages/lint/src/validate-component-types.test.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,11 @@ describe('leaves the declared vocabulary and the open arm alone', () => {
111111
// ⛔ The RETIRED types are deliberately NOT here — their kept
112112
// `ComponentPropsMap` row makes them `isKnownComponentType`, and this rule
113113
// used to read that as "accepted". They now have their own describe below.
114-
// Plugin namespaces — the open arm's declared story.
114+
// Plugin namespaces — the open arm's declared story. (`marketplace:installed-list`
115+
// left this list when it was retired by name: an open-arm type, so the
116+
// EXACT-name describe below is what reports it.)
115117
'mcp:connect-agent',
116118
'cloud-connection:panel',
117-
'marketplace:installed-list',
118119
// Colon-free custom/SDUI shapes — ditto.
119120
'flex',
120121
'grid',

0 commit comments

Comments
 (0)