Skip to content

Commit 7ec0ef4

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-20234-conversion-summaries-form-d
2 parents 31a2969 + 5757463 commit 7ec0ef4

34 files changed

Lines changed: 508 additions & 61 deletions
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.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/service-storage': patch
3+
---
4+
5+
Provenance comments in `service-storage` were re-anchored
6+
7+
Comment and docblock lines under `src/` that cited tracker numbers which no
8+
longer resolve on GitHub now cite the commit in this repository's history that
9+
decided the matter, and say in their own words what was decided. Comments
10+
only: no type, schema, export, log or refusal text, or runtime behaviour changes.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
'@objectstack/platform-objects': patch
3+
---
4+
5+
fix(platform-objects): the ja-JP, es-ES and zh-CN metadata-form descriptions, help texts and labels that contradicted their current English source are re-translated (#20666)
6+
7+
Clause-②: no
8+
9+
A translated metadata-form leaf that a translator wrote by hand is kept as
10+
written when its English source changes later, so some leaves went on saying
11+
what the old source said. On a ja-JP or es-ES console the permission-set form's
12+
Tab & Row-Level Security section still offered custom context variables, and
13+
the agent form's Capabilities section still offered tools; `en` dropped both
14+
when the keys were removed.
15+
16+
Twelve leaves whose meaning contradicted the current English now match it:
17+
18+
- all three locales: the agent form's Capabilities section (skills and
19+
knowledge sources, no tools), the permission-set form's Tab & Row-Level
20+
Security section (tab visibility and RLS policies: ja-JP and es-ES no longer
21+
offer custom context variables, and zh-CN no longer names the section after
22+
sharing rules), and the report form's `blocks` help (dataset-bound
23+
sub-reports, not a join of several objects);
24+
- ja-JP and es-ES: the email-template form's Identity section (the template is
25+
resolved by its `name` through `IEmailService.sendTemplate`, not by an `id`,
26+
and the section carries no content type);
27+
- zh-CN: the report form's Joined blocks section label, which named the section
28+
after related objects.
29+
30+
Leaves whose English source only gained detail or was reworded, without
31+
retracting what the translation says, are unchanged. Values only: no key is
32+
added or removed, and no provenance table changes.

‎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
},

0 commit comments

Comments
 (0)