Skip to content

Commit 2e0e451

Browse files
feat(platform-objects): User Preferences and Identity Links leave Setup's menu; the User page gains a Preferences tab (#23936)
Fixes #23803 Clause-②: no ## What this changes Item 3 of the card: "User Preferences" and "Identity Links" leave Setup's menu, and each stays reachable from the user its rows belong to. - `packages/platform-objects/src/apps/setup-nav.contributions.ts`: the `nav_accounts` (`sys_account`, view `all_links`) and `nav_user_preferences` (`sys_user_preference`, view `all_preferences`) entries are removed. A comment in their place records why, and where the rows are reached instead. - `packages/platform-objects/src/pages/sys-user.page.ts`: `SysUserDetailPage` gains a last tab, "Preferences" (en / zh-CN / ja-JP / es-ES, like its siblings). It is a `record:related_list` over `sys_user_preference` on `user_id`, with the columns `key`, `created_at`, `updated_at` (the `all_preferences` view's columns minus `user_id`, which is the page's own record), sorted by `updated_at` descending like that view, 25 rows per page with the list's own paging. It authors no `add` and no `actions`. The existing "Linked Accounts" tab over `sys_account` is unchanged and serves "Identity links" (measured below), so no second tab is added. - Translations: the two ids' labels leave `en.ts`, `zh-CN.ts`, `ja-JP.ts` and `es-ES.ts`, and their recorded digests leave the three `*.source-hashes.ts` tables (an orphan digest reds `source-hash.test.ts`). Both ids join `DEAD_SETUP_NAV_IDS` in `setup-nav-dead-key-tombstone.test.ts`, with their reason in its header. - Tests in `packages/platform-objects`: the new `setup-nav-per-user-stores.test.ts` pins (1) no Setup entry this package contributes targets either object, by `objectName` or by a URL; (2) the User page has one related list over each object, keyed on `user_id`, accepted by `RecordRelatedListProps`, and the page parses through `PageSchema`; (3) the Preferences list shows a subset of the `all_preferences` columns and authors no `add` or `actions`; (4) `unlink_account` is a `list_item` action. In `caller-scoped-first-list-view.test.ts`, the non-vacuity control drops `sys_user_preference`, because no nav entry names it now and the population is derived from nav; a new case holds pin (a) for that object by name, and goes red if an entry names it again. - `packages/cli/test/connect-agent-both-halves-wire.pin.test.ts` (claim amendment 2): the two Setup-tree length pins move from 34 to 32, the count comment says 23 contributed entries, and the first case asserts `nav_accounts` and `nav_user_preferences` are absent. 32 is measured from the built packages: 8 groups (`group_approvals` is empty and collapses), plus 23 `platform-objects` entries, plus the MCP entry. - `packages/qa/dogfood/test/platform-app-object-entry-views.test.ts` (claim amendment 3): the composed-boot copy of the same invariant gets the same change. `sys_user_preference` leaves the non-vacuity list, and a new case pins that no merged Setup or Account entry names it and that its first declared list view is still not caller-scoped (read from the composition, or else the identity barrel). - `content/docs/ui/setup-app.mdx` (claim amendment 3): the Advanced row of the group table lists "OAuth Applications" only. No other sentence on the page names the two entries. - `docs/qa/platform-checklist/areas/platform-core.json` (claim amendment 3): in `platform-core.builtin-apps-nav-render`, the Setup detail step no longer lists Identity Links and User Preferences among Setup's rendered destinations. It now has the runner open a user from Users and check the Preferences and Linked Accounts tabs. Revision 5 becomes 6, and no acceptance clause changes. The "45 Setup destinations" count is left as it is: it belongs to item 4, which whichever change lands last re-measures. - `docs/qa/platform-checklist/areas/identity-auth.json`: item `identity-auth.linked-accounts-social` cited `setup-nav.contributions.ts#nav_accounts`. It now cites `sys-user.page.ts#SysUserDetailPage`. Revision 3 becomes 4, as provenance only, with no clause change. Without the re-point, `pnpm check:platform-checklist` exits 1 (`ABSENT SYMBOL`, measured at `2abfd4b4c`). - `packages/platform-objects/src/identity/sys-account.object.ts` and `sys-user-preference.object.ts`: comments only. They no longer say a Setup entry names the object's unscoped view. - Changeset: `@objectstack/platform-objects` patch, worded for administrators. No pin was deleted, and no assertion was loosened. ## The card's other items - Item 1 (one audit entry) is carried by #23890. - Item 2 (Packages and Installed Apps) was ruled B on decision card #23892 (6216295104). It executes as objectstack-ai/objectui#12675 and #23933. - Item 4 (39 entries) is checked by whichever of this PR, #23890 and the #23892 work (objectstack-ai/objectui#12675, #23933) lands last. This PR pins only its own part: no top-level Setup entry targets `sys_user_preference` or `sys_account`. ## Measured before the change (`origin/main` at `497518c3f`) 1. **Producer.** `nav_accounts` and `nav_user_preferences` were at `setup-nav.contributions.ts` lines 186 and 187, in `SETUP_NAV_CONTRIBUTIONS`, group `group_advanced`. Every other `app: 'setup'` contributor (cloud-connection twice, mcp, plugin-approvals, plugin-audit, plugin-auth, plugin-security, plugin-sharing, plugin-webhooks, service-datasource, service-messaging) was grepped for `objectName:`, and none names either object. 2. **The User page.** `SysUserDetailPage` had "Linked Accounts" over `sys_account` (`relationshipField: 'user_id'`) and no tab over `sys_user_preference`. The new tab follows the Sessions and Linked Accounts shape. 3. **Every preference row belongs to a user.** `user_id` is `Field.lookup('sys_user', { required: true })`. The record validator refuses an insert without it, and refuses a PATCH that clears it (`required` and `required_cleared` in `record-validator.ts`). A user delete turns the default `set_null` on a required foreign key into `restrict` (`engine.ts`), so deleting a user cannot detach rows. The one producer, objectui's `packages/data-objectstack/src/userState.ts`, writes `user_id: userId` on every insert. Nothing in this repo writes the object server-side, and tenant-wide settings live on `sys_setting` (ADR-0007). No live database was read, so a row written below the engine (a raw driver write) is outside this reading. 4. **Row actions on the tab and on the old grid.** Read in objectui at the pin `daeb77fc84` (the files read are byte-identical at objectui `main` `63d00da`). The object grid gates New, row Edit and row Delete through `resolveAffordance` with `listNew`, `rowEdit` and `rowDelete`. The related list gates them through `relatedNew`, `relatedRowEdit` and `relatedRowDelete` (`packages/core/src/utils/affordanceGrants.ts`). Each pair reads the same CRUD bit (managed-object policy and effective API operations) and the same grant, so New, Edit and Delete get the same verdict on the tab as on the grid. The tab does not have the grid's Import, inline cell edit, bulk delete or `by_user` grouping view. Those stay on the object's own list. Its route is unchanged (the console resolves `/apps/setup/sys_user_preference` from all objects, not from the menu), and a preference record's breadcrumb opens it. `sys_account`'s one row action, `unlink_account`, has `locations: ['list_item', 'record_header']`, and the related-list host offers a child object's `list_item` actions on its rows. 5. **Pins naming the ids or objects.** `caller-scoped-first-list-view.test.ts` (control updated, pin (a) kept by name), `setup-nav-dead-key-tombstone.test.ts` (ledger extended) and `source-hash.test.ts` (orphan check, satisfied by removing the digests). `console-page-nav-entries.test.ts` names neither. ## Search "From the search" now holds through the user, not directly. Measured in objectui at the same pin: the command palette (`CommandPalette.tsx`) and the full search page (`SearchResultsPage.tsx`) take their object list and their record-search scope from the active app's menu entries. So after this change, typing "preferences" in Setup's palette no longer offers the "User Preferences" list. `sys_user` stays in scope (`nav_users`, and `enable.searchable: true`), so searching for a user's name opens their page, where both tabs are. Neither object's rows were ever searchable: both declare `enable.searchable: false`. A direct palette hit for an object no menu names would be an objectui change, and none is made here. ## Acceptance notes - `docs/qa/platform-checklist/areas/platform-core.json` still records "45 Setup destinations" in the Account-detail step. That count is item 4's, re-measured by whichever of this card's changes lands last. - `packages/plugins/plugin-audit/src/audit-writers.ts` (the comment above the per-user UI-state map) and `audit-writers.test.ts` (a case comment) describe an admin editing a row "from Setup's User Preferences grid". The behaviour they describe is unchanged, because an admin's edit from the Preferences tab is the same write. Only the place name is out of date. Not edited, because `plugin-audit` is outside this claim. No carrier. - `showViewAll` on `record:related_list` (spec `RecordRelatedListProps`, default `true`, described as 'Show "View All" link to see all related records') is not read by any objectui renderer. `RelatedList.tsx` renders its View all link only when a host passes `onViewAll`, and no host does (objectui pin `daeb77fc84`). The new tab sets `showViewAll: true` like its eight siblings on this page. That is the default, so it changes nothing. Filed by the seat as #23941. ## Verification, at head `770f1d617` (after merging `origin/main` at `6d1660dc3`) - `pnpm --filter @objectstack/platform-objects test`: 73 files and 1136 tests passed. - `pnpm --filter @objectstack/cli exec vitest run test/connect-agent-both-halves-wire.pin.test.ts`: 1 file and 2 tests passed. At `a6f35a120`, before the edit, CI's Test Core (2/6) showed it red: "expected [...] to have a length of 34 but got 32". - `pnpm --filter @objectstack/dogfood exec vitest run test/platform-app-object-entry-views.test.ts`: before the edit, 1 failed and 49 passed (exit 1), with "(a) no longer judges sys_user_preference". After the edit, 51 passed (exit 0). The 21 dogfood `*.test.ts` files that are not `*.dogfood.test.ts` also ran together: 329 passed and 6 skipped. That subset is chosen by file name, not by whether a file boots; some of the 21 boot a kernel in-process. The 244 `*.dogfood.test.ts` files boot example apps and run in CI's Dogfood Regression Gate. None of them reads Setup's menu, and the two that name `sys_user_preference` read it over the data API, which this change does not touch. - `pnpm --filter @objectstack/platform-objects typecheck`: exit 0 at `a6f35a120`. The three edited or new test files are in the `tsconfig.test.json` program (counted with `--listFiles`). - `pnpm check:i18n`, `pnpm check:platform-checklist` (822/832 anchors resolved, 10 on the named residual) and `pnpm check:doc-authoring`: each exits 0. - `node scripts/pm/dispatch-gates.mjs --commands` (no paths): 94 commands for the 20 changed paths. 93 exited 0: two needed their prerequisite built first (`check:i18n` needed the CLI, and `check:skill-examples` needed `client-react`), then exited 0. `check:dual-build-cjs-loads` exited 3 (PREREQUISITE NOT MET: it reads the whole workspace's `dist`), so it is NOT MEASURED locally; CI runs it in Build Core, which is green on this head. `--ran` reconciliation: 94 derived, 93 run, 1 NOT MEASURED, 0 UNRUN. - Ablation, one leg per platform-objects behaviour, each landed with `scripts/ablation-replace.mjs` (it proves the anchor hit and restores the blob to HEAD): - re-adding `nav_user_preferences` turns 3 tests red; - adding `value` to the Preferences columns turns 1 test red; - pointing the tab's list at another object turns 3 tests red. - After every leg, `git diff HEAD` is empty. - CI's families are not re-run here: read them on this PR's checks. --- _Generated by [Claude Code](https://claude.ai/code/session_01TQLy3Yq6vF4SDpTXdr3Uwi)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2054a08 commit 2e0e451

20 files changed

Lines changed: 296 additions & 40 deletions
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/platform-objects': patch
3+
---
4+
5+
feat(platform-objects): Setup's menu no longer lists "User Preferences" or "Identity Links". You reach both from the user they belong to: open a user in Setup › Users, and the user's page has a "Preferences" tab and its existing "Linked Accounts" tab.
6+
7+
Clause-②: no
8+
9+
Both lists held rows that belong to one user each: identity links are written when a user signs in with a provider, and preferences are what the console remembers as a user works (recent items, favourites, list settings). A menu entry listing them for every user at once was not a page an administrator works from.
10+
11+
- **Where they are now.** On a user's page, "Preferences" lists that user's preference rows (key, created, updated) and "Linked Accounts" lists their identity links, with the same Unlink action. New, Edit and Delete on the Preferences tab follow the same rules as the old list did. Search finds the user, and the tabs are on the user's page.
12+
- **What did not change.** The two objects, their list views and their addresses are unchanged, so a bookmarked list still opens, and a preference row's breadcrumb still leads to the full list.
13+
- **Nothing to migrate.** No data moves and no setting changes.

‎content/docs/ui/setup-app.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ anchors are:
5555
| **Configuration** (`group_configuration`) | All Settings · Localization · Company · Branding · Authentication · Email · File Storage · AI & Embedder · Knowledge · Feature Flags — `platform-objects` |
5656
| **Diagnostics** (`group_diagnostics`) | Sessions · Notification Events — `platform-objects`; Audit Logs · Audit Log Browser — `plugin-audit` |
5757
| **Integrations** (`group_integrations`) | `plugin-webhooks` |
58-
| **Advanced** (`group_advanced`) | OAuth Applications · Identity Links · User Preferences — `platform-objects` |
58+
| **Advanced** (`group_advanced`) | OAuth Applications — `platform-objects` |
5959

6060
The exact rendered menu depends on which capability plugins are loaded.
6161
A few notable entries:

‎docs/qa/platform-checklist/areas/identity-auth.json‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,7 +1116,7 @@
11161116
"title": "Linked accounts: link a social/OIDC identity through the redirect round-trip → a sys_account row appears in mine-view; unlink removes it; provider-less boot degrades honestly",
11171117
"since": "v16",
11181118
"status": "active",
1119-
"revision": 3,
1119+
"revision": 4,
11201120
"priority": "P2",
11211121
"surface": "mixed",
11221122
"personas": ["a signed-in user linking a second identity", "the same user after unlinking"],
@@ -1182,14 +1182,15 @@
11821182
"packages/platform-objects/src/identity/sys-account.object.ts#user_id (unlink_account → /api/v1/auth/unlink-account accountId=row id; mine view user_id={current_user_id} vs all_links; apiMethods ['get','list'])",
11831183
"packages/plugins/plugin-auth/src/auth-route-ledger.ts#AUTH_ROUTE_LEDGER (POST link-social=auth.accounts.linkSocial, GET list-accounts=auth.accounts.list, POST unlink-account=auth.accounts.unlink — re-pointed #18104: linkSocial is a CLIENT METHOD name, carried in this file only inside a dotted string value)",
11841184
"packages/platform-objects/src/apps/account.app.ts#nav_account_linked (nav_account_linked → 'Linked Accounts' in the Account app, objectName sys_account — the user's surface this item drives)",
1185-
"packages/platform-objects/src/apps/setup-nav.contributions.ts#nav_accounts (nav_accounts → 'Identity Links', objectName sys_account — the Setup app's admin entry over the same object, not the user's surface)",
1185+
"packages/platform-objects/src/pages/sys-user.page.ts#SysUserDetailPage (its Linked Accounts tab — a record:related_list over sys_account on user_id — is the admin's surface over the same object since #23803 removed Setup's nav_accounts 'Identity Links' entry; not the user's surface)",
11861186
"packages/plugins/plugin-auth/src/auth-manager.ts#applyConfigPatch (the public config patch the local-provider fixture registers through; oidcProviders)",
11871187
"#21845 (follow-up run: link / mine-view / unlink / read-only clauses passed against a local OIDC provider through the authenticated link door)"
11881188
],
11891189
"history": [
11901190
{ "revision": 1, "date": "2026-08-08", "change": "new item: social/OIDC account linking round-trip → sys_account mine-view row, unlink removal, provider-less honest degradation; blocked(fixture) pending a configured IdP (PENDING-GAPS §C)", "ref": "claude/platform-test-checklist-ocwugl" },
11911191
{ "revision": 2, "date": "2026-09-23", "change": "the 'issuer stamped' requirement is withdrawn from both the step and the first acceptance clause's verify: better-auth 1.7.3 rolled the issuer-scoped account identity back and sys_account.issuer retired with it, so nothing stamps it and the old verify failed a healthy system. The reason is kept — the step now says (provider_id, account_id) is the whole identity and names the retirement's migration entry in packages/spec — and the verify states that an issuer-less row is the expected shape", "ref": "#19217" },
1192-
{ "revision": 3, "date": "2026-10-05", "change": "blocked lifted: run #21845 passed the link, mine-view, unlink and read-only clauses against a local authorization-code + PKCE OIDC provider registered through the auth manager's applyConfigPatch from a scratch app plugin ('auth:configure' fires too early); the provider recipe is written into fixtures.requires, not committed as code, and the missing-IdP gap is kept as a CLOSED-for-runs knownGap. Step 2 now links through the authenticated POST /api/v1/auth/link-social and navigates to the URL it returns — the old step drove the sign-in door, which signs in rather than links. The step, the provider-less clause and its negative no longer name a specific sys_account action: they describe the link affordance, so the clause indices stay stable while the action itself is handled on its own card. The user's nav label is 'Linked Accounts' in the Account app (nav_account_linked); 'Identity Links' is the Setup app's admin entry (card #21851 entry 3)", "ref": "#21845" }
1192+
{ "revision": 3, "date": "2026-10-05", "change": "blocked lifted: run #21845 passed the link, mine-view, unlink and read-only clauses against a local authorization-code + PKCE OIDC provider registered through the auth manager's applyConfigPatch from a scratch app plugin ('auth:configure' fires too early); the provider recipe is written into fixtures.requires, not committed as code, and the missing-IdP gap is kept as a CLOSED-for-runs knownGap. Step 2 now links through the authenticated POST /api/v1/auth/link-social and navigates to the URL it returns — the old step drove the sign-in door, which signs in rather than links. The step, the provider-less clause and its negative no longer name a specific sys_account action: they describe the link affordance, so the clause indices stay stable while the action itself is handled on its own card. The user's nav label is 'Linked Accounts' in the Account app (nav_account_linked); 'Identity Links' is the Setup app's admin entry (card #21851 entry 3)", "ref": "#21845" },
1193+
{ "revision": 4, "date": "2026-10-16", "change": "PROVENANCE, no clause change: #23803 removed Setup's nav_accounts ('Identity Links') entry, so the source anchor that cited it no longer resolved. Re-pointed it at SysUserDetailPage, whose Linked Accounts tab (a related list over sys_account keyed on user_id) is where an administrator reaches a user's identity links now. Steps, acceptance clauses, oracles and negatives are unchanged: they drive the user's Account-app surface, which did not move", "ref": "#23803" }
11931194
]
11941195
},
11951196
{

‎docs/qa/platform-checklist/areas/platform-core.json‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@
509509
"title": "The built-in apps a stock boot REGISTERS (Setup / Account) render every merged-nav destination; app-level and entry-level gates are enforced, not errored — and Studio's deliberate absence is asserted, never hunted",
510510
"since": "v15",
511511
"status": "active",
512-
"revision": 5,
512+
"revision": 6,
513513
"priority": "P0",
514514
"surface": "browser",
515515
"personas": [
@@ -532,7 +532,7 @@
532532
"as admin, GET /api/v1/meta/app?id=setup and extract every merged nav destination — Setup is a SHELL of empty group anchors (packages/platform-objects/src/apps/setup.app.ts: group_overview/apps/people_org/access_control/approvals/configuration/diagnostics/integrations/advanced) filled by SETUP_NAV_CONTRIBUTIONS (setup-nav.contributions.ts) plus capability-plugin contributions (plugin-security Positions/Permission Sets, plugin-sharing Sharing Rules/Record Shares, plugin-approvals, plugin-webhooks)",
533533
"GET /api/v1/meta/app with NO query first and record the names actually served — a stock boot answers exactly showcase_app, setup, account. Then GET /api/v1/meta/app?id=account (account.app.ts — Profile + Inbox/Security/Developer groups). ⚠️ GET /api/v1/meta/app?id=studio answers 200 {\"items\":[]} on every stock boot and that is the expected result (knownGap): record the absence as an assertion and move on — do NOT install @objectstack/studio to make the step runnable",
534534
"as admin, hand-walk each destination in each app: navigate, wait for render, SCREENSHOT FIRST, then read the DOM — assert no pageerror, no 'failed to load', no blank <main>, no 'no actions configured' placeholder",
535-
"Setup detail: confirm each settings URL entry (nav_settings_* → /apps/setup/system/settings/<namespace>) opens the settings namespace form (localization/company/branding/auth/mail/storage/ai/knowledge/feature_flags), and Users / Organization / Business Units / Teams / Sessions / OAuth Applications / Identity Links / User Preferences render",
535+
"Setup detail: confirm each settings URL entry (nav_settings_* → /apps/setup/system/settings/<namespace>) opens the settings namespace form (localization/company/branding/auth/mail/storage/ai/knowledge/feature_flags), and Users / Organization / Business Units / Teams / Sessions / OAuth Applications render. User preferences and identity links are not Setup destinations: open a user from Users and confirm the user page's Preferences and Linked Accounts tabs render that user's rows",
536536
"Studio detail is NOT walked from here — the app is not registered on a stock boot (knownGap), so there is no merged nav to render. Its metadata:resource lists and component surfaces (App Builder, All Metadata Types, Packages, API Console, Flow Runs, Public Forms) are reached through the console surface at /_console/studio/<pkg>/<pillar> and are covered by area studio-authoring. What THIS item asserts about Studio is the absence itself, per clause 6",
537537
"Account detail: confirm Profile (account:profile_card), Notifications (sys_inbox_message/mine), Approvals (sys_approval_request/my_pending), Linked Accounts (sys_account), Active Sessions (sys_session/mine), API Keys (sys_api_key/mine), OAuth Applications (sys_oauth_application/mine) and Connect an Agent (nav_connect_agent) each render — 8 Account destinations and 45 Setup destinations for the admin, re-measured 2026-10-01 (GET /api/v1/meta/app leaves; #9453 recorded 43 and 7). Recount from the payload each run",
538538
"confirm the gated entries resolve to ABSENT-not-erroring for the admin: nav_organizations (requiresService org-scoping) is absent in single-org mode; nav_jwks Signing Keys is ABSENT for EVERY persona, admin included — the entry was removed in #7544 because sys_jwks declares apiEnabled:false, so its list answers OBJECT_API_DISABLED (404) for all callers and no permission gate could prune it; SSO Providers is absent unless OS_SSO_ENABLED (knownGap)",
@@ -618,6 +618,12 @@
618618
"date": "2026-10-06",
619619
"change": "partial-coverage gap of QA run #21782 parked on #22017 (row 10). The marketplace catalog content was blocked(environment): egress to the catalog was denied. Disposition: recorded knownGap. The existing knownGap now names the environment the leg needs (a boot that reaches DEFAULT_CLOUD_URL or a reachable OS_CLOUD_URL) and that no CI job drives it. No clause changed",
620620
"ref": "#22017"
621+
},
622+
{
623+
"revision": 6,
624+
"date": "2026-10-16",
625+
"change": "the Setup detail step listed Identity Links and User Preferences among Setup's rendered destinations; #23803 removed both entries from Setup (nav_accounts, nav_user_preferences), and an administrator reaches those rows from the User record page's Linked Accounts and Preferences tabs. The step now drives those two tabs from a user's page instead. The destination count in the Account-detail step is left as it is: it is #23803 item 4's, re-measured by whichever of that card's changes lands last. No acceptance clause changed",
626+
"ref": "#23803"
621627
}
622628
]
623629
},

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,12 @@ 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: 9 authored groups less
256256
// `group_approvals`, which nothing contributes into and the filter
257-
// collapses, plus 25 contributed entries plus the MCP one.
258-
expect(allowedIds).toHaveLength(34);
257+
// collapses, plus 23 contributed entries plus the MCP one.
258+
expect(allowedIds).toHaveLength(32);
259+
// The two per-user stores are reached from the User record page, not
260+
// from Setup's menu (#23803).
261+
expect(allowedIds).not.toContain('nav_accounts');
262+
expect(allowedIds).not.toContain('nav_user_preferences');
259263
});
260264

261265
it('the Account half widened NOTHING on Setup — the admin tree is unchanged', async () => {
@@ -273,7 +277,7 @@ describe('#17647 — Connect-an-Agent visibility: BOTH halves, over the wire', (
273277
expect(withAccountHalf.statusCode).toBe(200);
274278
expect(withoutAccountHalf.statusCode).toBe(200);
275279
expect(navIds(withAccountHalf.body?.item)).toEqual(navIds(withoutAccountHalf.body?.item));
276-
expect(navIds(withAccountHalf.body?.item)).toHaveLength(34);
280+
expect(navIds(withAccountHalf.body?.item)).toHaveLength(32);
277281

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

‎packages/platform-objects/src/apps/caller-scoped-first-list-view.test.ts‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,32 @@ describe('the population is derived from Setup and Account navigation (#21972)',
145145
// Non-vacuity control, not the population: the objects this card reordered
146146
// must still be judged by both pins, or a green run says nothing about them.
147147
it('judges every object whose caller-scoped first view this card moved', () => {
148-
for (const name of ['sys_user', 'sys_api_key', 'sys_session', 'sys_oauth_application', 'sys_account', 'sys_user_preference']) {
148+
for (const name of ['sys_user', 'sys_api_key', 'sys_session', 'sys_oauth_application', 'sys_account']) {
149149
expect(JUDGED_BY_A, `(a) no longer judges ${name}`).toContain(name);
150150
expect(JUDGED_BY_B.map((e) => e.objectName), `(b) no longer judges an entry naming ${name}`).toContain(name);
151151
}
152152
});
153153

154+
// The sixth object #21972 reordered left this population with Setup's "User
155+
// Preferences" entry (#23803): no nav entry of either app names it now, so
156+
// neither pin above reaches it, and (b) has nothing to judge. (a)'s concern
157+
// does not leave with the entry — a preference row opened from the User
158+
// record page's Preferences tab still has an object breadcrumb, a door that
159+
// names no view — so (a) is held for it here, by name. Should an entry name
160+
// it again, the first assertion goes red: put it back in the list above.
161+
it('holds (a) for sys_user_preference, which no nav entry names any more', () => {
162+
expect(NAMED_OBJECTS, 'an entry names sys_user_preference again — judge it in the loop above').not.toContain(
163+
'sys_user_preference',
164+
);
165+
expect(CATALOGUE.has('sys_user_preference'), 'control: the object is read from this package').toBe(true);
166+
const [firstName, firstView] = Object.entries(listViewsOf('sys_user_preference'))[0] ?? [];
167+
expect(firstName).toBeTypeOf('string');
168+
expect(
169+
isCallerScoped(firstView),
170+
`sys_user_preference declares the caller-scoped list view "${firstName}" first`,
171+
).toBe(false);
172+
});
173+
154174
it('cannot read exactly these named objects from this package', () => {
155175
// `sys_inbox_message` is `@objectstack/service-messaging`'s; the Account
156176
// app's Notifications entry names it, and names `mine`.

0 commit comments

Comments
 (0)