Skip to content

Commit fa0a4b6

Browse files
fix(platform-objects,plugin-audit): nav entries for the console's Audit Log and Integrations & APIs pages (#20699)
Fixes #20142 Clause-②: no ## Summary The console retired its System Hub card wall and its Developer Hub (objectui#10520, PR objectui#10576). Those had been the only in-app links to three console pages, so the pages were reachable by typed URL only. The console now registers each page under a component-registry key. This PR adds two of the three navigation entries that name those keys: | Entry | App / group | `componentRef` | Contributed by | Gate | | --- | --- | --- | --- | --- | | `nav_audit_log_browser` ("Audit Log Browser") | Setup / `group_diagnostics`, directly under `nav_audit_logs` | `audit:log` | `@objectstack/plugin-audit` `navigationContributions` | none: it lives and dies with the plugin that owns `sys_audit_log` | | `nav_integrations` ("Integrations & APIs") | Studio / `group_developer`, after `nav_public_forms` | `developer:integrations` | `@objectstack/platform-objects` `STUDIO_APP` | none beyond Studio's `studio.access` | The third key, `ai:approvals` (the AI pending-action queue), is the AI capability's entry to contribute under ADR-0029 D7, so this repo ships none. The seat answered the dev's placement question C (5896257587) and handed the entry to cloud's `@objectstack/service-ai` (knock 5896271533 on #6026). `content/docs/ui/setup-app.mdx`'s "Group / Filled by" table now reads `Audit Logs · Audit Log Browser — plugin-audit` on its Diagnostics row (patch round 2, `d4744797b`, after contract review 5896816094). No other hand-written page enumerates these slots' entries. Labels are in `packages/platform-objects/src/apps/translations/{en,zh-CN,ja-JP,es-ES}.ts`, with source hashes recorded in the three hand-maintained `*.source-hashes.ts` tables. No `plugin-audit/src/translations/*` file is touched: Setup nav labels live in platform-objects' `apps.setup.navigation` subtree, the same as `nav_audit_logs`'s. ## Measured first: the three keys at the console pin Read-only from objectui over git, at the objectstack `.objectui-sha` pin: `git init`, then `git fetch --depth 1 https://github.com/objectstack-ai/objectui.git dd3f7e1be3561d63267d7162f3fc0ac52e72834d` (exit 0, `FETCH_HEAD` = that sha), then `git grep`. | Key | File | Symbol | Loaded by | | --- | --- | --- | --- | | `audit:log` | `apps/console/src/registerSystemComponents.tsx:80` | `registerAppComponent({ ref: 'audit:log', label: 'Audit Log', ... AuditLogPage })` | `apps/console/src/main.tsx:45` `import './registerSystemComponents'` | | `ai:approvals` | `apps/console/src/registerSystemComponents.tsx:91` | `registerAppComponent({ ref: 'ai:approvals', label: 'AI Approvals', ... AiPendingActionsPage })` | same import | | `developer:integrations` | `apps/console/src/registerDeveloperComponents.tsx:86` | `registerAppComponent({ ref: 'developer:integrations', label: 'Integrations & APIs', ... IntegrationsPage })` | `apps/console/src/main.tsx:33` `import './registerDeveloperComponents'` | | positive control: `approvals:inbox` (the ref `nav_account_approvals` names) | `apps/console/src/registerApprovalsComponents.tsx:59` | `registerAppComponent({ ref: 'approvals:inbox', ... })` | `apps/console/src/main.tsx:42` | `SystemHubPage.tsx` is absent from `apps/console/src/pages/system/` at the pin. ## Mechanism hypotheses: which held - **H1 held.** A nav item reaches a console page only through `type: 'component'` plus a registry key. `requiresService` is the item-level gate, stripped server-side by `filterAppForUserWithReason` / `filterNav` in `packages/rest/src/meta-item-read-gate.ts` (ADR-0057 D10). It is variant-agnostic, so it gates a component item. - **H2 held for Studio and for audit, but was falsified for `group_approvals`.** `platform-objects.test.ts` pins "does not contribute slots owned by capability plugins" over `['group_integrations', 'group_approvals']` (ADR-0029 K2.b, commit `4cc2ceddce`). Round 1 split that pin to place `ai:approvals` there. The seat answered C (5896257587) by ADR-0029 D7: no platform-objects entry in `group_approvals`. Patch round 1 (`085c4ddb1`) removed the entry, and `platform-objects.test.ts` is byte-identical to `main`. - **H3 held.** Nav labels are hand-authored leaves in `packages/platform-objects/src/apps/translations/*.ts`, not generated bundles. `check:i18n` reports all nine packages' generated bundles in sync, so a `--write` run had nothing to write. Translated values: zh-CN `审计日志浏览器` / `集成与 API`, ja-JP `監査ログブラウザー` / `連携と API`, es-ES `Explorador de registros de auditoría` / `Integraciones y API`. None is a byte copy of the English source. - **H4: both choices settled from measurement.** See below. ## The two open choices ### Keep both audit entries: kept The two surfaces were compared at the pin and on this tree, and neither is a superset of the other. - **The object view (`nav_audit_logs`, unchanged)** has six named list views on `sys_audit_log`: `recent`, `writes_only`, `auth_events`, `record_views`, `config_changes` and `all_events`. It is searchable (`enable.searchable: true`), and it renders `user_id` / `tenant_id` as lookups. Its record page shows `old_value` / `new_value` as `Field.textarea`, which is raw text. - **The page (`audit:log`, AuditLogPage.tsx)** is one table (50 per page) with an action / object / actor-id / date filter bar. Its drawer pretty-prints Before and After JSON (`tryPrettyJson`) and the metadata. The actor is shown as a raw user id. Its action filter (`ACTION_OPTIONS` in `auditLogActions.ts`) lists `create, update, delete, login, logout, config_change, import`, and has no `read`. So the `read` rows are listed only by the object view's `record_views` view. The page is added; it does not replace the object entry. The page's label, "Audit Log Browser", differs from "Audit Logs" so the two rows are distinguishable in the same group. ### FOLLOW-UPS.md row K2: re-read, not edited Row K2 is left as written. This repo ships no entry that links the AI Approvals page, so the row's "ungated" half is for the cloud-side entry (the knock on #6026) to close. The "error-blind inbox" half is untouched by this PR. ## Tests (at head `085c4ddb1`, after `turbo run build` over `./packages/*`) - `packages/platform-objects/src/apps/console-page-nav-entries.test.ts` (new): pins `nav_integrations`, its group and position, and checks its ref against the measured key list and the key shape. - `packages/plugins/plugin-audit/src/audit-nav-contribution.test.ts` (new): runs the real `AuditPlugin.init`, and asserts item order `['nav_audit_logs', 'nav_audit_log_browser']`, `componentRef: 'audit:log'`, and that no gate is set. - `packages/cli/test/console-page-nav-entries.pin.test.ts` (new): a wire pin over the real `STUDIO_APP` fold, `SchemaRegistry`, and `RestServer` `GET /api/v1/meta/:type/:name`. The Studio entry is served last in `group_developer`. - `packages/platform-objects/src/platform-objects.test.ts` and `packages/cli/test/connect-agent-both-halves-wire.pin.test.ts` are unchanged from `main`. The Setup tree count re-measures 34. - Runs: platform-objects `pnpm test` 59 files / 947 tests pass; plugin-audit `pnpm test` 26 / 366 pass; the two cli pin files, 4 tests pass. The full cli unit tier last ran at round 1's `1d5520a076` (235 files / 3358 tests). The cli integration tier is declared to CI. Typecheck exits 0 for platform-objects, plugin-audit and cli. - Round 1's ablation of the `ai` gate is moot: that gate no longer exists. ## Gates `node scripts/pm/dispatch-gates.mjs --commands` at `d4744797b` derived 93 commands: `085c4ddb1`'s 64 plus 29 docs, link and spec-docs families that the docs row brings in. All 93 exit 0, and `--ran` reads `93 derived, 93 run, 0 NOT-MEASURED, 0 UNRUN`. At `085c4ddb1` the 64 all exited 0. That includes `check:i18n` (9 packages in sync), `check:test-source-alias`, `check:type-check-debt`, `check:dual-build-cjs-loads`, `check:nul-bytes`, `check:doc-authoring` and `check:cross-package-test-inputs`. The `--ran` reconciliation reads `64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN`. `pnpm check:app-nav-i18n` reports OK with setup at 55 merged nav ids, every id labelled in 4 locales. `pnpm check:i18n-coverage` was run in round 1 only. One gate went red mid-branch and was answered by narrowing, not by widening a ledger. `check:test-source-alias` refused a first draft of the cli pin that imported `@objectstack/plugin-audit` and `@objectstack/plugin-approvals` (new unaliased `dist/` imports). The cli pin now folds only platform-objects' entries. The audit ref is pinned in plugin-audit's own test. Lint, as a proven narrowing: `eslint --no-inline-config --format json` over the branch's 12 changed `.ts` files reported 0 errors and 0 warnings at `085c4ddb1` (the 13th changed file is the `.md` changeset). ① Population: `eslint.config.mjs`'s `files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}']` minus `NEVER_LINTED` covers all 12. ② The count is read from the JSON. ③ Invariance: `eslint.config.mjs` states that it "never enables type-aware linting (no `parserOptions.project`, no typed `@typescript-eslint` rules) for ANY file". So this diff cannot move any untouched file's verdict. The repo-wide `pnpm lint` is CI's. ## Acceptance notes - objectui observation, not filed: the page's action filter offers no `read` option, although `sys_audit_log.action` declares it (`auditLogActions.ts` at the pin). Its docblock still points the object at `platform-objects/src/audit/sys-audit-log.object.ts`, which moved to plugin-audit. Carrier: none. - Icons (`file-diff`, `plug-zap`) are lucide names; no gate validates icon names. - Pre-existing, not made false here: `content/docs/ui/setup-app.mdx`'s Apps row names Packages only, while `setup-nav.contributions.ts` on `main` also contributes Packaged Automation (`nav_packaged_automation`) to `group_apps`. Carrier: none. --- _Generated by [Claude Code](https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent e4e5222 commit fa0a4b6

14 files changed

Lines changed: 411 additions & 6 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
'@objectstack/platform-objects': patch
3+
'@objectstack/plugin-audit': patch
4+
---
5+
6+
fix(platform-objects,plugin-audit): Setup and Studio navigation entries for the console's Audit Log and Integrations & APIs pages (#20142)
7+
8+
The console retired its System Hub card wall and its Developer Hub, which had been the only in-app links to several pages, and registered each page under a component-registry key instead. Framework navigation reaches a console page only through a `type: 'component'` item that names such a key, and no item named them, so each page was reachable only by a typed URL. Two entries now name the pages whose capability ships in the open framework:
9+
10+
| Entry | App / group | `componentRef` | Contributed by | Gate |
11+
| --- | --- | --- | --- | --- |
12+
| `nav_audit_log_browser` ("Audit Log Browser") | Setup / Diagnostics, directly under Audit Logs | `audit:log` | `@objectstack/plugin-audit` | none: it lives and dies with the plugin that owns `sys_audit_log` |
13+
| `nav_integrations` ("Integrations & APIs") | Studio / Developer, after Public Forms | `developer:integrations` | `@objectstack/platform-objects` | none beyond Studio's own `studio.access` |
14+
15+
**Two audit entries, on purpose.** The existing Audit Logs entry (the `sys_audit_log` object view) stays. It carries the named list views, search, and the actor and tenant rendered as resolved lookups. The new page adds one filterable table whose detail drawer pretty-prints a change's before and after JSON, where the record page shows `old_value` / `new_value` as raw text. Neither surface replaces the other.
16+
17+
**No entry for the console's AI Approvals page (`ai:approvals`) here.** Under ADR-0029 D7, each capability plugin contributes its own navigation entries into a Setup slot, and the Setup shell does not enumerate capability objects. The AI pending-action queue belongs to the AI capability, whose provider (`@objectstack/service-ai`) ships in Cloud/Enterprise, not in the open framework. Its entry is therefore that capability's to contribute.
18+
19+
The keys are the ones the console registers at the objectui commit this release's console is built from. Labels ship in all four locales (en, zh-CN, ja-JP, es-ES), with their source hashes recorded. Nothing is removed or renamed, and there is nothing to migrate.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ anchors are:
5353
| **Access Control** (`group_access_control`) | Positions / Permission Sets — `plugin-security`; Sharing Rules / Record Shares — `plugin-sharing`; API Keys — `platform-objects` |
5454
| **Approvals** (`group_approvals`) | Approvals Inbox (the `approvals:inbox` component) · Requests · Action History · Delegations (OOO) — `plugin-approvals` |
5555
| **Configuration** (`group_configuration`) | All Settings · Localization · Company · Branding · Authentication · Email · File Storage · AI & Embedder · Knowledge · Feature Flags — `platform-objects` |
56-
| **Diagnostics** (`group_diagnostics`) | Sessions · Notification Events — `platform-objects`; Audit Logs — `plugin-audit` |
56+
| **Diagnostics** (`group_diagnostics`) | Sessions · Notification Events — `platform-objects`; Audit Logs · Audit Log Browser — `plugin-audit` |
5757
| **Integrations** (`group_integrations`) | `plugin-webhooks` |
5858
| **Advanced** (`group_advanced`) | OAuth Applications · Identity Links · User Preferences — `platform-objects` |
5959

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
//
3+
// #20142 — the Integrations & APIs page, one of the console pages that lost
4+
// their only in-app link when objectui#10520 retired the Developer Hub, read
5+
// back OVER THE WIRE from the composed Studio app.
6+
//
7+
// ---------------------------------------------------------------------------
8+
// What this pins
9+
// ---------------------------------------------------------------------------
10+
// The card's acceptance: "each entry resolves to its registered page (a
11+
// `componentRef` that is registered)". For the entry
12+
// `@objectstack/platform-objects` declares — `nav_integrations` →
13+
// `developer:integrations`, last in Studio's `group_developer` — this file
14+
// reads it from the served body of `GET /api/v1/meta/:type/:name`, after the
15+
// real `SchemaRegistry` registration and the real per-request filter, so a
16+
// regression anywhere between the declaration and the wire turns it red.
17+
//
18+
// The other two pages are pinned where they belong:
19+
// - `nav_audit_log_browser` → `audit:log` is contributed by
20+
// `@objectstack/plugin-audit`; its ref is pinned beside it in
21+
// `packages/plugins/plugin-audit/src/audit-nav-contribution.test.ts`.
22+
// Importing that plugin from this package's tests would resolve to its
23+
// `dist/`, which `check:test-source-alias` refuses for a new import.
24+
// - `ai:approvals` is not this repository's entry: ADR-0029 D7 has each
25+
// capability plugin contribute its own navigation, and the `ai`
26+
// capability's owner is `@objectstack/service-ai` in Cloud/Enterprise.
27+
//
28+
// "Registered" is judged against the keys MEASURED at the commit
29+
// objectstack's `.objectui-sha` pins (dd3f7e1be3561d63267d7162f3fc0ac52e72834d):
30+
// `registerDeveloperComponents.tsx` registers `developer:integrations` and
31+
// `registerSystemComponents.tsx` registers `audit:log`. objectui pins its
32+
// registration half in
33+
// `apps/console/src/__tests__/orphanedPageComponentRefs-10520.test.tsx`.
34+
//
35+
// It lives in `packages/cli/test/` for the reason its sibling
36+
// `connect-agent-both-halves-wire.pin.test.ts` states: `cli` is the one
37+
// workspace package that depends on every piece at once — the app shells
38+
// (`@objectstack/platform-objects`), the registry (`@objectstack/objectql`) and
39+
// the per-request filter (`@objectstack/rest`).
40+
41+
import { describe, expect, it, vi } from 'vitest';
42+
import { SchemaRegistry } from '@objectstack/objectql';
43+
import { STUDIO_APP } from '@objectstack/platform-objects/apps';
44+
import { RestServer } from '@objectstack/rest';
45+
46+
type AnyRec = Record<string, any>;
47+
48+
/** The registry keys the pinned console registers for the pages this repo links. */
49+
const MEASURED_CONSOLE_KEYS = ['audit:log', 'developer:integrations'];
50+
51+
/**
52+
* The real registration of the Studio shell. `structuredClone` because
53+
* `registerItem` writes the `_lock` envelope onto what it is handed
54+
* (ADR-0010 §3.7).
55+
*/
56+
function composedRegistry(): SchemaRegistry {
57+
const registry = new SchemaRegistry({ multiTenant: false, collisionPolicy: 'error' });
58+
(registry as AnyRec).logLevel = 'silent';
59+
registry.registerApp(structuredClone(STUDIO_APP), '@objectstack/platform-objects');
60+
return registry;
61+
}
62+
63+
function createMockServer() {
64+
return {
65+
get: vi.fn(), post: vi.fn(), put: vi.fn(), delete: vi.fn(), patch: vi.fn(), use: vi.fn(),
66+
listen: vi.fn().mockResolvedValue(undefined), close: vi.fn().mockResolvedValue(undefined),
67+
};
68+
}
69+
70+
function makeRes() {
71+
const res: AnyRec = { statusCode: 200, body: undefined };
72+
res.status = vi.fn((c: number) => { res.statusCode = c; return res; });
73+
res.json = vi.fn((b: unknown) => { res.body = b; return res; });
74+
res.header = vi.fn(); res.setHeader = vi.fn(); res.write = vi.fn(); res.end = vi.fn();
75+
return res;
76+
}
77+
78+
/** A `RestServer` serving the composed Studio app to a caller holding `studio.access`. */
79+
function serve() {
80+
const registry = composedRegistry();
81+
const protocol: AnyRec = {
82+
getDiscovery: vi.fn().mockResolvedValue({
83+
version: 'v0', routes: { data: '', metadata: '', ui: '', auth: '/auth' },
84+
}),
85+
getMetaTypes: vi.fn().mockResolvedValue([]),
86+
getMetaItems: vi.fn(async ({ type }: AnyRec) => {
87+
const t = String(type ?? '');
88+
return t === 'app' || t === 'apps' ? registry.getAllApps() : [];
89+
}),
90+
getMetaItem: vi.fn(async ({ name }: AnyRec) => {
91+
const item = registry.getApp(String(name));
92+
return item ? { type: 'app', name, item } : undefined;
93+
}),
94+
findData: vi.fn().mockResolvedValue([]),
95+
};
96+
const rest: AnyRec = new RestServer(
97+
createMockServer() as any, protocol as any, { api: { requireAuth: false } } as any,
98+
);
99+
rest.resolveExecCtx = async () => ({ userId: 'u1', systemPermissions: ['studio.access'] });
100+
rest.registerRoutes();
101+
return rest;
102+
}
103+
104+
/** `GET /api/v1/meta/apps/:name`, answered as `{ statusCode, body }`. */
105+
async function getApp(rest: AnyRec, name: string) {
106+
const route = rest.getRoutes().find(
107+
(r: AnyRec) => r.method === 'GET' && r.path === '/api/v1/meta/:type/:name',
108+
);
109+
if (!route) throw new Error('meta/:type/:name route not registered');
110+
const res = makeRes();
111+
await route.handler(
112+
{ method: 'GET', params: { type: 'apps', name }, query: {}, body: {}, headers: {} }, res,
113+
);
114+
return res;
115+
}
116+
117+
/** The served children of one nav group, or `undefined` when the group is not served. */
118+
function group(app: AnyRec | undefined, groupId: string): AnyRec[] | undefined {
119+
const found = ((app?.navigation ?? []) as AnyRec[]).find((g) => g?.id === groupId);
120+
return found ? ((found.children ?? []) as AnyRec[]) : undefined;
121+
}
122+
123+
const ids = (items: AnyRec[] | undefined) => (items ?? []).map((i) => String(i?.id));
124+
125+
describe('#20142 — the Integrations & APIs nav entry, served from the composed Studio app', () => {
126+
it('developer:integrations is served last in Studio\'s Developer group', async () => {
127+
const studio = await getApp(serve(), 'studio');
128+
expect(studio.statusCode).toBe(200);
129+
const developer = group(studio.body?.item, 'group_developer');
130+
expect(ids(developer)).toEqual([
131+
'nav_api_console', 'nav_flow_runs', 'nav_public_forms', 'nav_integrations',
132+
]);
133+
expect(developer?.at(-1)).toMatchObject({ type: 'component', componentRef: 'developer:integrations' });
134+
});
135+
136+
it('the served ref is a key the pinned console registers', async () => {
137+
const studio = await getApp(serve(), 'studio');
138+
const served = ((studio.body?.item?.navigation ?? []) as AnyRec[])
139+
.flatMap((g) => (g?.children ?? []) as AnyRec[])
140+
.filter((i) => i?.id === 'nav_integrations');
141+
expect(served).toHaveLength(1);
142+
expect(MEASURED_CONSOLE_KEYS).toContain(served[0].componentRef);
143+
});
144+
});
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
//
3+
// #20142 — the framework half of the console pages' navigation contract, for
4+
// the entry this package declares.
5+
//
6+
// objectui#10520 (PR objectui#10576) retired the console's System Hub card wall
7+
// and its Developer Hub, which had been the only in-app links to three pages,
8+
// and registered each page under a component-registry key instead. Framework
9+
// navigation reaches a console page only through a `type: 'component'` item
10+
// whose `componentRef` is such a key, so until an entry names the key the page
11+
// is reachable by typed URL alone. Of the three:
12+
// - `developer:integrations` is Studio's, declared here and pinned below;
13+
// - `audit:log` is contributed by `@objectstack/plugin-audit` (it lives and
14+
// dies with that plugin) and is pinned beside it in
15+
// `packages/plugins/plugin-audit/src/audit-nav-contribution.test.ts`;
16+
// - `ai:approvals` is NOT this repository's to contribute: ADR-0029 D7 has
17+
// each capability plugin contribute its own entries, and the `ai`
18+
// capability's owner is `@objectstack/service-ai` in Cloud/Enterprise.
19+
//
20+
// Why a pin rather than a code comment: `componentRef` is a free string, and
21+
// `validate-nav-target-refs` deliberately does not resolve component refs (the
22+
// registry is not visible to the linter). A misspelt key parses, merges and
23+
// ships, and the console renders "Component not registered" in its place.
24+
// Each repo pins its own half of the seam: objectui pins the registration in
25+
// `apps/console/src/__tests__/orphanedPageComponentRefs-10520.test.tsx`, this
26+
// file pins the ref the entry names.
27+
//
28+
// The keys below were MEASURED, not recalled: read from objectui at the commit
29+
// objectstack's `.objectui-sha` pins (dd3f7e1be3561d63267d7162f3fc0ac52e72834d),
30+
// where `registerSystemComponents.tsx` registers `audit:log` and
31+
// `registerDeveloperComponents.tsx` registers `developer:integrations`, both
32+
// imported for their side effect by `apps/console/src/main.tsx`. A pin bump
33+
// that renames one of them must change this list in the same PR.
34+
35+
import { describe, it, expect } from 'vitest';
36+
import { AppSchema } from '@objectstack/spec/ui';
37+
38+
import { STUDIO_APP } from './studio.app.js';
39+
40+
/** The registry keys the pinned console registers for the pages this repo links. */
41+
const MEASURED_CONSOLE_KEYS = ['audit:log', 'developer:integrations'] as const;
42+
43+
type NavItem = {
44+
id?: string;
45+
type?: string;
46+
label?: string;
47+
componentRef?: string;
48+
requiresService?: string;
49+
requiredPermissions?: string[];
50+
children?: NavItem[];
51+
};
52+
53+
/** The children of one Studio group, by id. */
54+
const studioGroup = (id: string): NavItem[] => {
55+
const group = ((STUDIO_APP.navigation ?? []) as NavItem[]).find((g) => g.id === id);
56+
expect(group, `Studio lost its ${id} group`).toBeDefined();
57+
return group?.children ?? [];
58+
};
59+
60+
describe('the Integrations & APIs entry targets the console page (#20142)', () => {
61+
const entry = (): NavItem => {
62+
const found = studioGroup('group_developer').find((i) => i.id === 'nav_integrations');
63+
expect(found, 'Studio lost its nav_integrations entry').toBeDefined();
64+
return found!;
65+
};
66+
67+
it('routes to the `developer:integrations` registry key, last in group_developer', () => {
68+
expect(entry()).toMatchObject({
69+
type: 'component',
70+
componentRef: 'developer:integrations',
71+
label: 'Integrations & APIs',
72+
});
73+
expect(studioGroup('group_developer').map((i) => i.id)).toEqual([
74+
'nav_api_console',
75+
'nav_flow_runs',
76+
'nav_public_forms',
77+
'nav_integrations',
78+
]);
79+
});
80+
81+
it('carries no gate beyond Studio\'s own, like its neighbours', () => {
82+
expect(entry().requiresService).toBeUndefined();
83+
expect(entry().requiredPermissions).toBeUndefined();
84+
});
85+
86+
it('the Studio app still satisfies AppSchema', () => {
87+
expect(() => AppSchema.parse(STUDIO_APP)).not.toThrow();
88+
});
89+
});
90+
91+
describe('each ref names a key the pinned console registers (#20142)', () => {
92+
it('the ref this package declares is among the measured keys', () => {
93+
const ref = studioGroup('group_developer').find((i) => i.id === 'nav_integrations')?.componentRef;
94+
expect(MEASURED_CONSOLE_KEYS as readonly (string | undefined)[]).toContain(ref);
95+
});
96+
97+
it('every measured key is a registry KEY, never a console path', () => {
98+
// objectui#2763's boundary: navigation names a key and the console owns the
99+
// URL it resolves to (`developer:integrations` →
100+
// `/apps/APP/component/developer/integrations`).
101+
for (const key of MEASURED_CONSOLE_KEYS) {
102+
expect(key).toMatch(/^[a-z0-9_-]+:[a-z0-9_-]+$/);
103+
expect(key).not.toContain('/');
104+
}
105+
});
106+
});

‎packages/platform-objects/src/apps/studio.app.ts‎

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -298,9 +298,9 @@ export const STUDIO_APP: App = {
298298
},
299299
{
300300
// Developer — first-party developer tooling surfaces hosted by the
301-
// console (API console, flow run inspector, public forms registry).
302-
// Registered as built-in components in the console's
303-
// ComponentRegistry under the `developer:*` namespace.
301+
// console (API console, flow run inspector, public forms registry,
302+
// integrations & APIs). Registered as built-in components in the
303+
// console's ComponentRegistry under the `developer:*` namespace.
304304
id: 'group_developer',
305305
type: 'group',
306306
label: 'Developer',
@@ -327,6 +327,20 @@ export const STUDIO_APP: App = {
327327
componentRef: 'developer:public-forms',
328328
icon: 'file-text',
329329
},
330+
{
331+
// #20142 — the console's Integrations & APIs page: the environment's
332+
// REST base URL, per-object endpoints and an `x-api-key` cURL
333+
// sample. Its only in-app link was a card on the console's Developer
334+
// Hub, which objectui#10520 retired once all four of the hub's
335+
// destinations were registry keys; the console registers this one
336+
// in `registerDeveloperComponents.tsx`. No gate beyond Studio's own
337+
// `studio.access`, like its neighbours.
338+
id: 'nav_integrations',
339+
type: 'component',
340+
label: 'Integrations & APIs',
341+
componentRef: 'developer:integrations',
342+
icon: 'plug-zap',
343+
},
330344
],
331345
},
332346
{

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ export const en: TranslationData = {
135135
// Diagnostics
136136
nav_sessions: { label: 'Sessions' },
137137
nav_audit_logs: { label: 'Audit Logs' },
138+
// The console's Audit Log page, beside the object view (#20142).
139+
nav_audit_log_browser: { label: 'Audit Log Browser' },
138140
nav_notifications: { label: 'Notifications' },
139141

140142
// Integrations — every entry here is contributed at RUNTIME by the
@@ -191,6 +193,7 @@ export const en: TranslationData = {
191193
nav_api_console: { label: 'API Console' },
192194
nav_flow_runs: { label: 'Flow Runs' },
193195
nav_public_forms: { label: 'Public Forms' },
196+
nav_integrations: { label: 'Integrations & APIs' },
194197
group_integration: { label: 'Integration' },
195198
nav_email_templates: { label: 'Email Templates' },
196199
},

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ export const esESSourceHashes: Readonly<Record<string, string>> = {
117117
'apps.setup.navigation.nav_notification_templates.label': '9e270c87ef2578df',
118118
'apps.setup.navigation.nav_sessions.label': '673bde6e0c8a3ef9',
119119
'apps.setup.navigation.nav_audit_logs.label': '700dd03639b82a6a',
120+
'apps.setup.navigation.nav_audit_log_browser.label': 'f6932710da7ec071',
120121
'apps.setup.navigation.nav_notifications.label': 'e0fdb90b1d31078d',
121122
'apps.setup.navigation.nav_webhooks.label': '8fdd81bc76459a6a',
122123
'apps.setup.navigation.nav_http_deliveries.label': '340f81dc6ea64987',
@@ -152,6 +153,7 @@ export const esESSourceHashes: Readonly<Record<string, string>> = {
152153
'apps.studio.navigation.nav_api_console.label': 'd90e508f1921e8d3',
153154
'apps.studio.navigation.nav_flow_runs.label': '0bdee1504c505862',
154155
'apps.studio.navigation.nav_public_forms.label': '4592e97959994e86',
156+
'apps.studio.navigation.nav_integrations.label': 'edbd97d7f401a5b8',
155157
'apps.studio.navigation.group_integration.label': 'a1f2ddfb5c00c83a',
156158
'apps.studio.navigation.nav_email_templates.label': '3463bce831c833d5',
157159
'dashboards.system_overview.label': '574b2a4b7fb7f979',

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export const esES: TranslationData = {
9494

9595
nav_sessions: { label: 'Sesiones' },
9696
nav_audit_logs: { label: 'Registros de Auditoría' },
97+
nav_audit_log_browser: { label: 'Explorador de registros de auditoría' },
9798
nav_notifications: { label: 'Notificaciones' },
9899

99100
// Integrations — contributed at RUNTIME by the owning capability
@@ -137,6 +138,7 @@ export const esES: TranslationData = {
137138
nav_api_console: { label: 'Consola de API' },
138139
nav_flow_runs: { label: 'Ejecuciones de flujo' },
139140
nav_public_forms: { label: 'Formularios públicos' },
141+
nav_integrations: { label: 'Integraciones y API' },
140142
group_integration: { label: 'Integración' },
141143
nav_email_templates: { label: 'Plantillas de correo' },
142144
},

0 commit comments

Comments
 (0)