Skip to content

Commit ce12ad2

Browse files
feat(platform-objects): Setup drops its unfiltered Packages entry; the full list lives in Studio (#29547)
<!-- claude-projects-attribution: {"github_login":"os-warren"} --> _Requested by **Warren** · [project thread](https://claude.ai/code/project/chan_01U5RdnAAmkSQmZbqh6FtqZ8?thread=cmsg_01U5RdnAAmkSQmZbqh6FtqZ8MFX42FUfma4vepCroC88iy)_ Fixes #25098 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:系统设置(Setup)的「应用」组去掉未过滤的「软件包」入口;完整软件包列表只留在 Studio → 总览 → 软件包。 - 为什么改:维护者裁决「二、软件包列表挪到 Studio ;甲」:完整列表是开发面,Setup 只保留应用市场过滤后的「已安装应用」入口。 - 风险与回滚:只删一个导航项及其四语种标签;页面本身没动,书签照常可开。回滚即还原本 PR。 - 先看:`setup-nav.contributions.ts`(删掉的那一行)、新 pin `setup-packages-door.test.ts`、changeset。 - 两分钟验证:`pnpm --filter @objectstack/platform-objects exec vitest run src/apps/setup-packages-door.test.ts`,预期 3 个用例全绿。 - 需要你做的:无,席位落地。 ## What changes - `packages/platform-objects/src/apps/setup-nav.contributions.ts`: Setup's `group_apps` contribution drops `nav_packages` (`developer:packages`, unfiltered). The group comment now says where the full list lives (Studio) and which Setup door remains: `nav_marketplace_installed`, the marketplace-filtered "Installed Apps" entry that `@objectstack/cloud-connection` contributes wherever the marketplace capability is present. Studio's `nav_packages` (`studio.app.ts`, Overview → Packages) is untouched. - Translations: `apps.setup.navigation.nav_packages` is removed from `en`, `zh-CN`, `ja-JP` and `es-ES`, and its recorded digest from the three `*.source-hashes.ts` tables. `apps.studio.navigation.nav_packages` stays in all four catalogs and all three hash tables. - `setup-packaged-actions-nav.test.ts`: restated, not loosened. Its retirement control used Setup's `nav_packages` as proof the walk reads the real array; it now uses `nav_packaged_actions`, which the same file already requires to exist. Its comment and one test title no longer say "beside Packages". - New pin `setup-packages-door.test.ts`: Setup's shell plus `SETUP_NAV_CONTRIBUTIONS` carry no `developer:packages` entry without a `source` filter (and no `nav_packages` id), and Studio's Overview still carries exactly one unfiltered `developer:packages` entry. The predicate has its own positive and negative controls. - Changeset: `patch` for `@objectstack/platform-objects`. ## Why Maintainer ruling 甲 on #25098 (amends #23892 ruling B, item 1): the full package list is a development surface, so its unfiltered entry lives in Studio only, and Setup keeps the marketplace-filtered Installed Apps door. The blocker objectstack-ai/cloud#3138 is cleared: cloud PR #3488 registers that door in hosted tenant environments, so a hosted Setup keeps a way to installed packages. ## Tests - `pnpm --filter @objectstack/platform-objects test` (under the verify lock, after building the dependency closure): Test Files 110 passed (110), Tests 1510 passed (1510). - `pnpm --filter @objectstack/platform-objects typecheck`: exit 0. - Ablation, one leg plus restore, at commit `510f2898` via `scripts/ablation-replace.mjs`: re-inserting Setup's `nav_packages` line into `setup-nav.contributions.ts` turns `Setup ships no unfiltered developer:packages entry` red (`expected [ { id: 'nav_packages', …(4) } ] to deeply equal []`; 1 failed, 2 passed). Restore blob equals the HEAD blob (`e8992d3f9d5b`) and `git diff HEAD` is empty. The test imports the source file directly, so no `dist/` rebuild sits in the path. ## Acceptance notes - `packages/cli/scripts/check-app-nav-i18n.mjs` names `nav_packages` only in two comments that record #5750's history (the id was untranslated at the time; the Studio copy did not answer for Setup). Both stay true as history, so they are unchanged. The gate judges the merged Setup app, which no longer carries the id, so it has one id fewer to check. - `packages/cloud-connection/src/marketplace-installed-ui-entry.pin.test.ts` describes `developer:packages` as "the component Setup's own Packages entry opens" in a doc comment. That clause is now stale; it is outside this card's file surface and left for the seat. - Setup's `group_apps` translations still list `nav_cloud_connection`; its move to Integrations belongs to #25097, not this card. ## Verification All readings at head `510f2898`. <details><summary>Derived gates: 59 derived, 57 exit 0, 2 NOT MEASURED; plus pnpm lint and check:app-nav-i18n</summary> - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 59 commands from the 11 changed paths (merge base `399df5bb`). - 57 exit 0. `pnpm check:i18n` and `pnpm check:lean-entry-closure` first exited 3 (prerequisite not built); after `turbo run build` of the closure the i18n gate names, plus `@objectstack/objectql`, both exit 0: `check-i18n-bundles: OK (10 package(s) — all bundles in sync, no undeclared authoring keys)`. - NOT MEASURED, reason: prerequisite not met (exit 3). `pnpm check:dual-build-cjs-loads` needs a full `pnpm build` of about 83 packages; CI's Build Core runs it. `node scripts/check-plugin-teardown-shape.mjs --self-test` is checker-health only and needs an unshallow clone. - `pnpm check:app-nav-i18n` (outside the derivation, run because the card names it): `OK (13 contributor(s), 4 locale(s), 2 app(s) — setup: 56 merged nav id(s), account: 12 merged nav id(s) — every id labelled in every locale)`. - `pnpm lint` under the verify lock: `VERDICT command-exit 0`. - `dispatch-gates --ran`: `59 derived famil(ies) accounted for — 57 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)`. - CI families (type-check lanes, Build Core, Test Core) are read from this PR's check-runs, not re-run locally. </details> ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (not touched) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes. - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. (not touched) - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English. (The maintainer quick-read section is in Chinese by the os-dev order.) --- _Generated by [Claude Code](https://claude.ai/code/session_01JQwxbZp2WazS7kV2qRc7RV)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 66ba23e commit ce12ad2

12 files changed

Lines changed: 116 additions & 36 deletions
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/platform-objects': patch
3+
---
4+
5+
feat(platform-objects): Setup no longer carries the unfiltered Packages entry; the full package list lives in Studio
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The full `developer:packages` list is a development surface, so its one unfiltered navigation entry is now Studio's Overview → Packages, which is unchanged. Setup's `group_apps` drops its `nav_packages` entry. Setup keeps the marketplace-filtered "Installed Apps" door (`nav_marketplace_installed`, contributed by `@objectstack/cloud-connection` wherever the marketplace capability is present), so its Apps group reads Browse Marketplace, Installed Apps and Packaged Actions.
11+
12+
The Setup copies of the entry's label (`apps.setup.navigation.nav_packages` in `en`, `zh-CN`, `ja-JP` and `es-ES`) and their recorded source hashes are removed with it; Studio's copies stay. A bookmark to the Packages page keeps working, since the page itself did not move.

‎packages/cli/test/connect-agent-both-halves-wire.pin.test.ts‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -254,14 +254,15 @@ describe('#17647 — Connect-an-Agent visibility: BOTH halves, over the wire', (
254254
.toContain('nav_connect_agent');
255255
// The Setup tree the platform admin is served BY THIS COMPOSITION: 9
256256
// authored groups less `group_approvals`, which nothing contributes
257-
// into and the filter collapses, plus the 25 entries
258-
// `SETUP_NAV_CONTRIBUTIONS` carries, plus the MCP one.
257+
// into and the filter collapses, plus the 24 entries
258+
// `SETUP_NAV_CONTRIBUTIONS` carries, plus the MCP one. (25 until
259+
// #25098 moved the unfiltered Packages entry to Studio.)
259260
// `nav_settings_branding` is not among them, and not because of a
260261
// tenancy wall (this harness boots none): since #24766 it is
261262
// contributed by `@objectstack/service-settings` together with the
262263
// `branding` namespace, offered only where no organization wall stands,
263264
// and this harness composes platform-objects and the MCP bundle alone.
264-
expect(allowedIds).toHaveLength(34);
265+
expect(allowedIds).toHaveLength(33);
265266
expect(allowedIds).not.toContain('nav_settings_branding');
266267
// The two per-user stores are reached from the User record page, not
267268
// from Setup's menu (#23803).
@@ -284,8 +285,8 @@ describe('#17647 — Connect-an-Agent visibility: BOTH halves, over the wire', (
284285
expect(withAccountHalf.statusCode).toBe(200);
285286
expect(withoutAccountHalf.statusCode).toBe(200);
286287
expect(navIds(withAccountHalf.body?.item)).toEqual(navIds(withoutAccountHalf.body?.item));
287-
// 34: the composition the count in the test above states.
288-
expect(navIds(withAccountHalf.body?.item)).toHaveLength(34);
288+
// 33: the composition the count in the test above states.
289+
expect(navIds(withAccountHalf.body?.item)).toHaveLength(33);
289290

290291
// The mechanism behind the invariance, asserted rather than assumed:
291292
// the bundle aims exactly one contribution at `setup` and one at

‎packages/platform-objects/src/apps/setup-nav.contributions.ts‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,19 @@ export const SETUP_NAV_CONTRIBUTIONS: NavigationContribution[] = [
3939
],
4040
},
4141
{
42-
// Package ADMINISTRATION is a platform/operator concern (ADR-0084:
43-
// packages are Operate, out of the builder) — so its home is Setup, not
44-
// the application builder. The console binds `developer:packages` to the
45-
// existing package-management page. Building/creating apps is a separate
46-
// journey (Home cover → /studio); this entry is for install/inspect/admin.
42+
// No unfiltered Packages entry here (#25098, ruling 甲 amending #23892 B):
43+
// the full `developer:packages` list is a development surface, so its one
44+
// unfiltered door is Studio's Overview → Packages (`studio.app.ts`). Setup
45+
// reaches installed packages through the marketplace-filtered "Installed
46+
// Apps" door (`nav_marketplace_installed`, `developer:packages` with
47+
// `params: { source: 'marketplace' }`), which
48+
// `@objectstack/cloud-connection` contributes to this group wherever the
49+
// marketplace capability is present. `setup-packages-door.test.ts` pins
50+
// both halves.
4751
app: 'setup',
4852
group: 'group_apps',
4953
priority: BASE_PRIORITY,
5054
items: [
51-
{ id: 'nav_packages', type: 'component', label: 'Packages', componentRef: 'developer:packages', icon: 'package' },
5255
// Packaged actions are OPERATIONAL state — on/off per packaged action
5356
// (ADR-0126 §7.4: "Studio keeps the editing; Setup gets the operational
5457
// state"). ADR-0146 D4 keeps the action switch here while the flow

‎packages/platform-objects/src/apps/setup-packaged-actions-nav.test.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
// `sys_metadata_activation` ledger this package registers (#12419) and work
2424
// on compositions with NO automation service; gating the entry on that
2525
// service would hide a page that still works there.
26-
// - no `requiredPermissions` — matches `nav_packages` beside it: the Setup
26+
// - no `requiredPermissions` — like the rest of `group_apps`: the Setup
2727
// app's own `setup.access` gates the app, and the activation WRITE doors
2828
// enforce `manage_metadata` / the ADR-0126 §5 operator gate server-side.
2929
import { describe, it, expect } from 'vitest';
@@ -72,7 +72,7 @@ describe('the Setup Packaged Actions entry targets the console component (ADR-01
7272
});
7373
});
7474

75-
it('lives in group_apps beside Packages — package administration is Operate (ADR-0084)', () => {
75+
it('lives in group_apps — operational package state is Operate (ADR-0084)', () => {
7676
expect(entry().group).toBe('group_apps');
7777
});
7878

@@ -93,8 +93,10 @@ describe('the Setup Packaged Actions entry targets the console component (ADR-01
9393
describe('Setup › Packaged automation stays retired (ADR-0146 D11)', () => {
9494
it('no entry names the retired id or the retired `automation:packaged` ref', () => {
9595
const items = allItems().map(({ item }) => item);
96-
// Control: the walk reads the real array.
97-
expect(items.some((i) => i.id === 'nav_packages')).toBe(true);
96+
// Control: the walk reads the real array. (It used to name Setup's
97+
// `nav_packages`, which moved to Studio in #25098;
98+
// `setup-packages-door.test.ts` pins that move.)
99+
expect(items.some((i) => i.id === 'nav_packaged_actions')).toBe(true);
98100
expect(items.filter((i) => i.id === 'nav_packaged_automation')).toEqual([]);
99101
expect(items.filter((i) => i.componentRef === 'automation:packaged')).toEqual([]);
100102
});
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
//
3+
// The full package list lives in Studio; Setup carries no unfiltered door to it
4+
// (#25098, ruling 甲 amending #23892 ruling B).
5+
//
6+
// The console's `developer:packages` component is the one list of what is
7+
// installed. Opened with no `source` filter it is a development surface, so its
8+
// unfiltered entry lives in Studio only (Overview → Packages). Setup reaches
9+
// installed packages through the marketplace-filtered "Installed Apps" door —
10+
// `developer:packages` with `params: { source: 'marketplace' }` — which
11+
// `@objectstack/cloud-connection` contributes to Setup's `group_apps` wherever
12+
// the marketplace capability is present, and pins on its side
13+
// (`marketplace-installed-ui-entry.pin.test.ts`).
14+
//
15+
// This file is the platform-objects half: what THIS package ships for each app.
16+
// Both directions are pinned, because either regression is silent at runtime —
17+
// an unfiltered Setup entry simply renders, and a Studio entry that goes
18+
// missing leaves the full list reachable only by a hand-typed URL.
19+
import { describe, it, expect } from 'vitest';
20+
21+
import { SETUP_APP } from './setup.app.js';
22+
import { SETUP_NAV_CONTRIBUTIONS } from './setup-nav.contributions.js';
23+
import { STUDIO_APP } from './studio.app.js';
24+
25+
type NavItem = {
26+
id?: string;
27+
type?: string;
28+
componentRef?: string;
29+
params?: Record<string, unknown>;
30+
children?: NavItem[];
31+
};
32+
33+
const PACKAGES_REF = 'developer:packages';
34+
35+
/** Every nav item in a tree, depth-first. */
36+
const walk = (items: unknown[] | undefined, out: NavItem[] = []): NavItem[] => {
37+
for (const raw of items ?? []) {
38+
const item = raw as NavItem | undefined;
39+
if (!item) continue;
40+
out.push(item);
41+
if (Array.isArray(item.children)) walk(item.children, out);
42+
}
43+
return out;
44+
};
45+
46+
/** A `developer:packages` entry with no `source` filter: the full list. */
47+
const isUnfilteredPackagesDoor = (item: NavItem): boolean =>
48+
item.componentRef === PACKAGES_REF && item.params?.source === undefined;
49+
50+
const setupItems = (): NavItem[] => [
51+
...walk(SETUP_APP.navigation as unknown[]),
52+
...SETUP_NAV_CONTRIBUTIONS.flatMap((c) => walk(c.items as unknown[])),
53+
];
54+
55+
describe('the unfiltered package list is a Studio surface, not a Setup one (#25098)', () => {
56+
it('the predicate tells the full list from the marketplace-filtered door', () => {
57+
// Controls on the predicate itself, so the absence below cannot pass
58+
// because the predicate matches nothing.
59+
expect(isUnfilteredPackagesDoor({ type: 'component', componentRef: PACKAGES_REF })).toBe(true);
60+
expect(
61+
isUnfilteredPackagesDoor({ type: 'component', componentRef: PACKAGES_REF, params: { source: 'marketplace' } }),
62+
).toBe(false);
63+
});
64+
65+
it('Setup ships no unfiltered `developer:packages` entry', () => {
66+
const items = setupItems();
67+
// Control: the walk reads the real Setup shell and its contributions.
68+
expect(items.some((i) => i.id === 'group_apps')).toBe(true);
69+
expect(items.some((i) => i.id === 'nav_packaged_actions')).toBe(true);
70+
71+
expect(items.filter(isUnfilteredPackagesDoor)).toEqual([]);
72+
expect(items.filter((i) => i.id === 'nav_packages')).toEqual([]);
73+
});
74+
75+
it('Studio still ships the unfiltered entry, under Overview', () => {
76+
const overview = walk(STUDIO_APP.navigation as unknown[]).find((i) => i.id === 'group_overview');
77+
expect(overview, 'Studio lost its group_overview').toBeDefined();
78+
79+
const doors = walk(overview!.children).filter(isUnfilteredPackagesDoor);
80+
expect(doors).toHaveLength(1);
81+
expect(doors[0]).toMatchObject({ id: 'nav_packages', type: 'component', componentRef: PACKAGES_REF });
82+
});
83+
});

‎packages/platform-objects/src/apps/translations/en.ts‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,6 @@ export const en: TranslationData = {
7070
nav_system_overview: { label: 'System Overview' },
7171

7272
// Apps / Marketplace
73-
// `nav_packages` is a SETUP entry (package administration is an
74-
// operator concern — ADR-0084), distinct from the same-id entry under
75-
// `apps.studio.navigation`. Two apps carrying one nav id is normal;
76-
// each app resolves its own subtree, so the studio copy never answers
77-
// for this one. Its absence here was #5750's first cause.
78-
nav_packages: { label: 'Packages' },
7973
nav_packaged_actions: { label: 'Packaged Actions' },
8074
nav_marketplace_browse: { label: 'Browse Marketplace' },
8175
nav_marketplace_installed: { label: 'Installed Apps' },

‎packages/platform-objects/src/apps/translations/es-ES.source-hashes.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ export const esESSourceHashes: Readonly<Record<string, string>> = {
7171
'apps.setup.description': 'deacdfc679232a4a',
7272
'apps.setup.navigation.group_overview.label': 'eebdbe1cc7b59cdf',
7373
'apps.setup.navigation.group_apps.label': '699b367291abcbe6',
74-
'apps.setup.navigation.nav_packages.label': '8cd8f29658025f73',
7574
'apps.setup.navigation.nav_packaged_actions.label': '0d86b11df972dff3',
7675
'apps.setup.navigation.nav_marketplace_browse.label': '2afbe021f5419fdd',
7776
'apps.setup.navigation.nav_marketplace_installed.label': '93bd5d458e05db60',

‎packages/platform-objects/src/apps/translations/es-ES.ts‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ export const esES: TranslationData = {
3636
navigation: {
3737
group_overview: { label: 'Resumen' },
3838
group_apps: { label: 'Aplicaciones' },
39-
// Setup's own `nav_packages` — distinct from the same id under
40-
// `apps.studio.navigation`, which does not answer for this app (#5750).
41-
nav_packages: { label: 'Paquetes' },
4239
nav_packaged_actions: { label: 'Acciones empaquetadas' },
4340
nav_marketplace_browse: { label: 'Explorar Marketplace' },
4441
nav_marketplace_installed: { label: 'Aplicaciones instaladas' },

‎packages/platform-objects/src/apps/translations/ja-JP.source-hashes.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ export const jaJPSourceHashes: Readonly<Record<string, string>> = {
7171
'apps.setup.description': 'deacdfc679232a4a',
7272
'apps.setup.navigation.group_overview.label': 'eebdbe1cc7b59cdf',
7373
'apps.setup.navigation.group_apps.label': '699b367291abcbe6',
74-
'apps.setup.navigation.nav_packages.label': '8cd8f29658025f73',
7574
'apps.setup.navigation.nav_packaged_actions.label': '0d86b11df972dff3',
7675
'apps.setup.navigation.nav_marketplace_browse.label': '2afbe021f5419fdd',
7776
'apps.setup.navigation.nav_marketplace_installed.label': '93bd5d458e05db60',

‎packages/platform-objects/src/apps/translations/ja-JP.ts‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ export const jaJP: TranslationData = {
3636
navigation: {
3737
group_overview: { label: '概要' },
3838
group_apps: { label: 'アプリ' },
39-
// Setup's own `nav_packages` — distinct from the same id under
40-
// `apps.studio.navigation`, which does not answer for this app (#5750).
41-
nav_packages: { label: 'パッケージ' },
4239
nav_packaged_actions: { label: 'パッケージアクション' },
4340
nav_marketplace_browse: { label: 'マーケットプレイスを閲覧' },
4441
nav_marketplace_installed: { label: 'インストール済みアプリ' },

0 commit comments

Comments
 (0)