@xemahq/ui-kernel 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/errors/http-error-status.d.ts +17 -0
- package/dist/errors/http-error-status.d.ts.map +1 -0
- package/dist/errors/http-error-status.js +59 -0
- package/dist/errors/http-error-status.js.map +1 -0
- package/dist/errors/index.d.ts +2 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +2 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/biome-host/errors.d.ts.map +1 -1
- package/dist/lib/biome-host/errors.js +1 -19
- package/dist/lib/biome-host/errors.js.map +1 -1
- package/dist/offline/index.d.ts.map +1 -1
- package/dist/offline/index.js.map +1 -1
- package/dist/offline/online-controller.d.ts.map +1 -1
- package/dist/offline/online-controller.js +6 -1
- package/dist/offline/online-controller.js.map +1 -1
- package/dist/offline/outbox.d.ts.map +1 -1
- package/dist/offline/outbox.js +2 -24
- package/dist/offline/outbox.js.map +1 -1
- package/package.json +1 -2
- package/src/index.ts +0 -17
- package/src/lib/biome-host/agent-validation.ts +0 -239
- package/src/lib/biome-host/biome-builders.ts +0 -112
- package/src/lib/biome-host/biome-mode.ts +0 -6
- package/src/lib/biome-host/biome-navigation.ts +0 -37
- package/src/lib/biome-host/biome-registry.ts +0 -299
- package/src/lib/biome-host/biome-scope-hooks.ts +0 -41
- package/src/lib/biome-host/biome-scope.tsx +0 -52
- package/src/lib/biome-host/biome-scoped-query.ts +0 -134
- package/src/lib/biome-host/create-biome-orval-config.ts +0 -82
- package/src/lib/biome-host/define-web-biome.spec.ts +0 -221
- package/src/lib/biome-host/define-web-biome.ts +0 -222
- package/src/lib/biome-host/errors.ts +0 -220
- package/src/lib/biome-host/frontend-biome.ts +0 -256
- package/src/lib/biome-host/host-bridge.ts +0 -281
- package/src/lib/biome-host/host-sources.ts +0 -54
- package/src/lib/biome-host/i18n-hooks.ts +0 -51
- package/src/lib/biome-host/i18n-port.test.ts +0 -50
- package/src/lib/biome-host/i18n-port.ts +0 -96
- package/src/lib/biome-host/index.ts +0 -35
- package/src/lib/biome-host/realtime-hooks.ts +0 -75
- package/src/lib/biome-host/realtime-port.ts +0 -115
- package/src/lib/biome-host/session-contributions.ts +0 -296
- package/src/lib/biome-host/use-mutation-with-error-toast.ts +0 -90
- package/src/lib/biome-host/use-page-state.ts +0 -248
- package/src/lib/capabilities/capability-context.ts +0 -25
- package/src/lib/capabilities/capability-provider.tsx +0 -73
- package/src/lib/capabilities/index.ts +0 -17
- package/src/lib/capabilities/types.ts +0 -69
- package/src/lib/capabilities/use-capability.ts +0 -79
- package/src/lib/contribution-registry/contribution-registry.spec.ts +0 -141
- package/src/lib/contribution-registry/contribution-registry.ts +0 -204
- package/src/lib/contribution-registry/index.ts +0 -9
- package/src/lib/contribution-registry/renderable-registry.ts +0 -142
- package/src/lib/system-bus/capability-invoke.ts +0 -92
- package/src/lib/system-bus/deeplink.ts +0 -210
- package/src/lib/system-bus/enums.ts +0 -86
- package/src/lib/system-bus/host-ports.ts +0 -93
- package/src/lib/system-bus/index.ts +0 -16
- package/src/lib/system-bus/intent-registry.ts +0 -109
- package/src/lib/system-bus/intents.ts +0 -113
- package/src/lib/system-bus/palette.ts +0 -78
- package/src/lib/system-bus/system-bus-builder.ts +0 -159
- package/src/lib/system-bus/system-bus.ts +0 -37
- package/src/lib/system-bus/windows.ts +0 -51
- package/src/offline/id.ts +0 -16
- package/src/offline/index.ts +0 -54
- package/src/offline/mutation.ts +0 -75
- package/src/offline/offline-status-badge.tsx +0 -51
- package/src/offline/online-controller.ts +0 -105
- package/src/offline/outbox.test.ts +0 -172
- package/src/offline/outbox.ts +0 -348
- package/src/offline/react-hooks.ts +0 -40
- package/src/offline/react.tsx +0 -32
- package/src/offline/storage.ts +0 -112
- package/src/offline/types.ts +0 -77
- package/src/org-db/components/DbResultTable.tsx +0 -176
- package/src/org-db/index.ts +0 -1
- package/src/pdf/index.ts +0 -11
- package/src/registry/index.ts +0 -9
- package/src/registry/lib/agent-validation-host.ts +0 -36
- package/src/registry/lib/biome-slot.tsx +0 -50
- package/src/registry/lib/biomes-enabled-context.ts +0 -20
- package/src/registry/lib/extension-points.ts +0 -134
- package/src/registry/lib/primitives/SessionEventCard.tsx +0 -164
- package/src/registry/lib/primitives/SessionEventTimeline.tsx +0 -95
- package/src/registry/lib/primitives/SessionMutationBar.tsx +0 -76
- package/src/registry/lib/primitives/SessionTabbedDrawer.tsx +0 -165
- package/src/registry/lib/primitives/index.ts +0 -18
- package/src/registry/lib/session-context-builder.ts +0 -84
- package/src/registry/lib/session-context-provider.tsx +0 -33
- package/src/registry/lib/session-context.ts +0 -21
- package/src/registry/lib/session-selectors.ts +0 -272
- package/src/session/comments/CommentRail.tsx +0 -164
- package/src/session/index.ts +0 -3
- package/src/session/lib/cn.ts +0 -11
- package/src/session/shell/SessionWorkspaceShell.tsx +0 -147
- package/src/session-kit/approvals/ApprovalButton.tsx +0 -89
- package/src/session-kit/approvals/ApprovalCard.tsx +0 -341
- package/src/session-kit/approvals/ApprovalsCenter.tsx +0 -336
- package/src/session-kit/approvals/CapabilityApprovalStickyBar.tsx +0 -118
- package/src/session-kit/approvals/Obligation.tsx +0 -111
- package/src/session-kit/approvals/ScopedBody.tsx +0 -392
- package/src/session-kit/approvals/approval-icons.ts +0 -31
- package/src/session-kit/approvals/approval-model.ts +0 -204
- package/src/session-kit/approvals/index.ts +0 -22
- package/src/session-kit/approvals/obligation-display.ts +0 -100
- package/src/session-kit/approvals/risk-accent.ts +0 -47
- package/src/session-kit/approvals/scope-icons.ts +0 -19
- package/src/session-kit/combobox/Combobox.tsx +0 -358
- package/src/session-kit/combobox/use-click-outside.ts +0 -21
- package/src/session-kit/display/ContextHeader.tsx +0 -148
- package/src/session-kit/display/FileDiffCard.tsx +0 -140
- package/src/session-kit/display/MD.tsx +0 -162
- package/src/session-kit/display/MessageTurn.tsx +0 -157
- package/src/session-kit/display/ThinkingPanel.tsx +0 -81
- package/src/session-kit/display/TodoChecklist.tsx +0 -120
- package/src/session-kit/display/TokenMeter.tsx +0 -89
- package/src/session-kit/display/ToolStrip.tsx +0 -278
- package/src/session-kit/display/TypingDots.tsx +0 -24
- package/src/session-kit/index.ts +0 -44
- package/src/session-kit/lib/enums.ts +0 -66
- package/src/session-kit/lib/portal-accent.ts +0 -30
- package/src/session-kit/lib/status-dot.ts +0 -68
- package/src/session-kit/primitives/Avatar.tsx +0 -44
- package/src/session-kit/primitives/PortalGlyph.tsx +0 -51
- package/src/session-kit/primitives/RiskPill.tsx +0 -95
- package/src/session-kit/primitives/ScopeDot.tsx +0 -47
- package/src/session-kit/primitives/Segmented.tsx +0 -71
- package/src/ui/branding/app-branding-context.ts +0 -20
- package/src/ui/branding/app-branding-provider.tsx +0 -123
- package/src/ui/branding/sanitize.ts +0 -89
- package/src/ui/breakpoints.ts +0 -14
- package/src/ui/capture/document-preview.tsx +0 -166
- package/src/ui/capture/field-spec-to-zod.test.ts +0 -81
- package/src/ui/capture/field-spec-to-zod.ts +0 -174
- package/src/ui/capture/field-spec-validate.ts +0 -236
- package/src/ui/capture/field-spec.test.ts +0 -49
- package/src/ui/capture/field-spec.ts +0 -274
- package/src/ui/capture/index.ts +0 -51
- package/src/ui/capture/schema-field-input.tsx +0 -620
- package/src/ui/capture/schema-form.tsx +0 -149
- package/src/ui/capture/submit-values.test.ts +0 -70
- package/src/ui/capture/submit-values.ts +0 -63
- package/src/ui/capture/use-prefill.ts +0 -186
- package/src/ui/capture/use-schema-validation.ts +0 -145
- package/src/ui/capture/use-wizard.ts +0 -180
- package/src/ui/capture/value-format.ts +0 -32
- package/src/ui/capture/wizard.tsx +0 -105
- package/src/ui/charts/bar-chart.tsx +0 -139
- package/src/ui/charts/chart-frame.tsx +0 -125
- package/src/ui/charts/chart-types.ts +0 -52
- package/src/ui/charts/chart-utils.ts +0 -190
- package/src/ui/charts/charts.test.tsx +0 -204
- package/src/ui/charts/donut-chart.tsx +0 -129
- package/src/ui/charts/index.ts +0 -27
- package/src/ui/charts/line-chart.tsx +0 -202
- package/src/ui/charts/sparkline.tsx +0 -109
- package/src/ui/chrome/AsyncBoundary.tsx +0 -74
- package/src/ui/chrome/EmptyState.tsx +0 -257
- package/src/ui/chrome/ErrorCard.tsx +0 -63
- package/src/ui/chrome/LoadingState.tsx +0 -61
- package/src/ui/chrome/PageHeader.tsx +0 -137
- package/src/ui/chrome/RecordCardList.tsx +0 -98
- package/src/ui/chrome/StateCard.tsx +0 -150
- package/src/ui/chrome/confirm-delete-dialog.tsx +0 -59
- package/src/ui/chrome/page-shell.spec.tsx +0 -117
- package/src/ui/chrome/page-shell.tsx +0 -221
- package/src/ui/chrome/scope-badge.tsx +0 -40
- package/src/ui/chrome/status-badge.tsx +0 -79
- package/src/ui/cn.ts +0 -32
- package/src/ui/design-tokens.ts +0 -372
- package/src/ui/hooks/use-debounced-value.ts +0 -16
- package/src/ui/hooks/use-is-mobile.ts +0 -28
- package/src/ui/index.ts +0 -110
- package/src/ui/pdf/pdf-preview.tsx +0 -122
- package/src/ui/primitives/alert-dialog.tsx +0 -104
- package/src/ui/primitives/async-combobox.tsx +0 -182
- package/src/ui/primitives/badge-variants.ts +0 -21
- package/src/ui/primitives/badge.tsx +0 -25
- package/src/ui/primitives/button-variants.ts +0 -31
- package/src/ui/primitives/button.tsx +0 -29
- package/src/ui/primitives/card.tsx +0 -43
- package/src/ui/primitives/checkbox.tsx +0 -26
- package/src/ui/primitives/collapsible.tsx +0 -9
- package/src/ui/primitives/dialog.tsx +0 -103
- package/src/ui/primitives/dropdown-menu.tsx +0 -179
- package/src/ui/primitives/form-stepper.tsx +0 -94
- package/src/ui/primitives/form-stepper.types.ts +0 -15
- package/src/ui/primitives/input.tsx +0 -22
- package/src/ui/primitives/label.tsx +0 -17
- package/src/ui/primitives/overflow-tabs.tsx +0 -331
- package/src/ui/primitives/popover.tsx +0 -33
- package/src/ui/primitives/preview-device-frame.tsx +0 -213
- package/src/ui/primitives/radio-group.tsx +0 -36
- package/src/ui/primitives/resizable.tsx +0 -67
- package/src/ui/primitives/scroll-area.tsx +0 -38
- package/src/ui/primitives/select.tsx +0 -143
- package/src/ui/primitives/separator.tsx +0 -20
- package/src/ui/primitives/sheet.tsx +0 -107
- package/src/ui/primitives/skeleton.tsx +0 -99
- package/src/ui/primitives/switch.tsx +0 -27
- package/src/ui/primitives/table.tsx +0 -72
- package/src/ui/primitives/tabs.tsx +0 -53
- package/src/ui/primitives/tag-multi-select.tsx +0 -261
- package/src/ui/primitives/textarea.tsx +0 -21
- package/src/ui/primitives/tooltip.tsx +0 -30
- package/src/ui/primitives/url-preview-chrome.tsx +0 -103
- package/src/vista/GenericCatalogueSurface.tsx +0 -179
- package/src/vista/SurfaceFallbackCard.tsx +0 -109
- package/src/vista/VistaPanel.tsx +0 -163
- package/src/vista/form-payload.spec.ts +0 -139
- package/src/vista/html/CustomUiFrame.spec.tsx +0 -127
- package/src/vista/html/CustomUiFrame.tsx +0 -206
- package/src/vista/html/custom-ui-frame-bridge.spec.ts +0 -191
- package/src/vista/html/custom-ui-frame-bridge.ts +0 -98
- package/src/vista/html/custom-ui-frame-contract.ts +0 -88
- package/src/vista/html/custom-ui-intents.spec.ts +0 -135
- package/src/vista/html/custom-ui-intents.ts +0 -155
- package/src/vista/html/custom-ui-srcdoc.spec.ts +0 -151
- package/src/vista/html/custom-ui-srcdoc.ts +0 -193
- package/src/vista/html/index.ts +0 -13
- package/src/vista/index.ts +0 -52
- package/src/vista/surface-catalogue.spec.ts +0 -140
- package/src/vista/surface-catalogue.ts +0 -90
- package/src/vista/surface-icons.ts +0 -57
- package/src/vista/surface-registry.ts +0 -56
- package/src/vista/surfaces/ChartSurface.tsx +0 -34
- package/src/vista/surfaces/FilesSurface.tsx +0 -202
- package/src/vista/surfaces/FormSurface.tsx +0 -380
- package/src/vista/surfaces/HtmlSurface.tsx +0 -43
- package/src/vista/surfaces/TableSurface.tsx +0 -100
- package/src/vista/surfaces/UrlSurface.tsx +0 -40
- package/src/vista/surfaces/blob-surfaces.tsx +0 -139
- package/src/vista/surfaces/elicitation-transport.spec.ts +0 -369
- package/src/vista/surfaces/elicitation-transport.ts +0 -367
- package/src/vista/surfaces/file-surface-utils.ts +0 -69
- package/src/vista/surfaces/index.ts +0 -24
- package/src/vista/surfaces/register-surfaces.tsx +0 -139
- package/src/vista/surfaces/text-surfaces.tsx +0 -132
- package/src/vista/use-vista-tabs.ts +0 -196
- package/src/vista/vista-code-renderer-context.ts +0 -16
- package/src/vista/vista-code-renderer.tsx +0 -35
- package/src/vista/vista-file-context.ts +0 -14
- package/src/vista/vista-file-source.tsx +0 -141
- package/src/vista/vista-form-context.ts +0 -20
- package/src/vista/vista-form-host.tsx +0 -48
- package/src/vista/vista-model.ts +0 -137
- package/src/vista/vista-open-context.tsx +0 -27
- package/src/vista/vista-open-state.ts +0 -11
- package/src/vista/vista-payloads.ts +0 -635
- package/src/vista/vista-tabs-manifest.ts +0 -138
- package/src/vista/vista-tools.ts +0 -232
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared Orval-client wiring for frontend biomes.
|
|
3
|
-
*
|
|
4
|
-
* Every biome that calls a platform API through an Orval-generated client must
|
|
5
|
-
* configure that client with the SAME auth/header/401 behavior the host shell
|
|
6
|
-
* uses for its own clients. Hand-copying a `configureOrgDbClient`-style helper
|
|
7
|
-
* into each biome (as `org-databases` did across three hook files) duplicates
|
|
8
|
-
* the correlation-id + org-header + token logic and lets it drift from the
|
|
9
|
-
* host. This factory centralizes it: a biome builds one config from its
|
|
10
|
-
* `HostBridge` and feeds it straight into the client's `configureClient(...)`.
|
|
11
|
-
*
|
|
12
|
-
* Auth/refresh/redirect stay HOST-owned — this only READS the bridge's
|
|
13
|
-
* host-implemented methods (`getActorToken`, `ensureFreshToken`,
|
|
14
|
-
* `onUnauthorized`, `getOrgId`). It owns no auth state of its own.
|
|
15
|
-
*/
|
|
16
|
-
import type { HostBridge } from './host-bridge';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* The Orval client config shape every generated `configureClient(...)` accepts.
|
|
20
|
-
* Kept structural (not imported from a client package) so the kernel stays free
|
|
21
|
-
* of any concrete service-client dependency — biomes pass the result straight
|
|
22
|
-
* into their own client's `configureClient`, which validates the shape.
|
|
23
|
-
*/
|
|
24
|
-
export interface BiomeOrvalConfig {
|
|
25
|
-
readonly baseUrl: string;
|
|
26
|
-
readonly maxRetries: number;
|
|
27
|
-
getAuthToken(): Promise<string>;
|
|
28
|
-
getHeaders(): Promise<Record<string, string>>;
|
|
29
|
-
onUnauthorized(): Promise<void>;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface CreateBiomeOrvalConfigOptions {
|
|
33
|
-
/** Absolute base URL of the target service. */
|
|
34
|
-
readonly baseUrl: string;
|
|
35
|
-
/**
|
|
36
|
-
* Retry budget for the underlying fetch wrapper. Defaults to 1 — a single
|
|
37
|
-
* retry covers the `onUnauthorized` refresh-then-retry path without turning
|
|
38
|
-
* a hard failure into a retry storm.
|
|
39
|
-
*/
|
|
40
|
-
readonly maxRetries?: number;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Build an Orval client config from a `HostBridge`. Wires:
|
|
45
|
-
* - `getAuthToken` → `bridge.auth.ensureFreshToken()` (proactive refresh;
|
|
46
|
-
* the host owns the refresh + login-redirect single-flight).
|
|
47
|
-
* - `getHeaders` → a fresh `X-Correlation-Id` per call, plus `X-Xema-Org-Id`
|
|
48
|
-
* (the canonical Xema-platform tenant header every platform service reads
|
|
49
|
-
* via `RequestContext`) when an org is selected. Biome web clients only ever
|
|
50
|
-
* call Xema-platform biome-apis, so this is the single correct tenant
|
|
51
|
-
* header; the previously-sent legacy `X-Org-Id` was dead (the central
|
|
52
|
-
* read-side was decommissioned) and left context-derived reads without an
|
|
53
|
-
* org. Per-call headers set by the caller still win in the client's own
|
|
54
|
-
* header merge.
|
|
55
|
-
* - `onUnauthorized` → `bridge.auth.onUnauthorized()` (host force-refresh +
|
|
56
|
-
* redirect-on-failure).
|
|
57
|
-
*
|
|
58
|
-
* @example
|
|
59
|
-
* configureClient(createBiomeOrvalConfig(bridge, { baseUrl: API_BASE_URL }));
|
|
60
|
-
*/
|
|
61
|
-
export function createBiomeOrvalConfig(
|
|
62
|
-
bridge: HostBridge,
|
|
63
|
-
opts: CreateBiomeOrvalConfigOptions,
|
|
64
|
-
): BiomeOrvalConfig {
|
|
65
|
-
return {
|
|
66
|
-
baseUrl: opts.baseUrl,
|
|
67
|
-
maxRetries: opts.maxRetries ?? 1,
|
|
68
|
-
getAuthToken: () => bridge.auth.ensureFreshToken(),
|
|
69
|
-
getHeaders: () => {
|
|
70
|
-
const headers: Record<string, string> = {
|
|
71
|
-
'X-Correlation-Id': crypto.randomUUID(),
|
|
72
|
-
};
|
|
73
|
-
const orgId = bridge.auth.getOrgId();
|
|
74
|
-
if (orgId) {
|
|
75
|
-
// Canonical header read by every Xema-platform service's RequestContext.
|
|
76
|
-
headers['X-Xema-Org-Id'] = orgId;
|
|
77
|
-
}
|
|
78
|
-
return Promise.resolve(headers);
|
|
79
|
-
},
|
|
80
|
-
onUnauthorized: () => bridge.auth.onUnauthorized(),
|
|
81
|
-
};
|
|
82
|
-
}
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `defineWebBiome` — define-time scope/access locking, the fail-closed
|
|
3
|
-
* defaults, and closed-set completeness.
|
|
4
|
-
*
|
|
5
|
-
* This suite replaces an earlier `define-web-biome.test.js` written for
|
|
6
|
-
* Node's test runner against `dist/`. That file ran NOWHERE: the package's
|
|
7
|
-
* `test` script is a notice (the kernel carries no test runner by design) and
|
|
8
|
-
* the host-web vitest `include` is `*.{test,spec}.{ts,tsx}` — so a `.js` file
|
|
9
|
-
* was invisible to both. A security test that never executes reads as
|
|
10
|
-
* coverage while proving nothing, so it is gone; this vitest spec is synced
|
|
11
|
-
* into the host mirror by the frontend bridge and runs there with everything
|
|
12
|
-
* else.
|
|
13
|
-
*
|
|
14
|
-
* The invariant under test is the security one: a route is public ONLY when
|
|
15
|
-
* its author said so twice, explicitly. Nothing defaulted, inferred, or
|
|
16
|
-
* coerced can produce a public route.
|
|
17
|
-
*/
|
|
18
|
-
import { describe, expect, it } from 'vitest';
|
|
19
|
-
|
|
20
|
-
import { defineWebBiome } from './define-web-biome';
|
|
21
|
-
import {
|
|
22
|
-
PAGE_CHROME_VALUES,
|
|
23
|
-
ROUTE_ACCESS_VALUES,
|
|
24
|
-
ROUTE_SCOPE_VALUES,
|
|
25
|
-
type PageChrome,
|
|
26
|
-
type RouteAccess,
|
|
27
|
-
type RouteScope,
|
|
28
|
-
} from './frontend-biome';
|
|
29
|
-
|
|
30
|
-
import type { HostBridge } from './host-bridge';
|
|
31
|
-
|
|
32
|
-
const load = () => Promise.resolve({ default: () => null });
|
|
33
|
-
const bridge = {} as HostBridge;
|
|
34
|
-
|
|
35
|
-
describe('defineWebBiome — public/chromeless scope', () => {
|
|
36
|
-
it('rejects a public page whose access defaults to member', () => {
|
|
37
|
-
expect(() =>
|
|
38
|
-
defineWebBiome({
|
|
39
|
-
id: 'test-biome',
|
|
40
|
-
displayName: 'Test',
|
|
41
|
-
pages: [{ slug: 'ext/:id', label: 'Ext', scope: 'public', load }],
|
|
42
|
-
}),
|
|
43
|
-
).toThrow(/a 'public' scope page MUST declare access: 'public'/);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('rejects a public page with an explicit non-public access tier', () => {
|
|
47
|
-
expect(() =>
|
|
48
|
-
defineWebBiome({
|
|
49
|
-
id: 'test-biome',
|
|
50
|
-
displayName: 'Test',
|
|
51
|
-
pages: [
|
|
52
|
-
{
|
|
53
|
-
slug: 'ext/:id',
|
|
54
|
-
label: 'Ext',
|
|
55
|
-
scope: 'public',
|
|
56
|
-
access: 'org-admin',
|
|
57
|
-
load,
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
}),
|
|
61
|
-
).toThrow(/a 'public' scope page MUST declare access: 'public'/);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('rejects a non-public page that declares access public', () => {
|
|
65
|
-
expect(() =>
|
|
66
|
-
defineWebBiome({
|
|
67
|
-
id: 'test-biome',
|
|
68
|
-
displayName: 'Test',
|
|
69
|
-
pages: [{ slug: 'system/x', label: 'X', access: 'public', load }],
|
|
70
|
-
}),
|
|
71
|
-
).toThrow(/access: 'public' is only valid on a 'public' scope page/);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('accepts a public + access:public page and emits no nav item', () => {
|
|
75
|
-
const biome = defineWebBiome({
|
|
76
|
-
id: 'test-biome',
|
|
77
|
-
displayName: 'Test',
|
|
78
|
-
pages: [
|
|
79
|
-
{
|
|
80
|
-
slug: 'ext/:id',
|
|
81
|
-
label: 'Ext',
|
|
82
|
-
scope: 'public',
|
|
83
|
-
access: 'public',
|
|
84
|
-
load,
|
|
85
|
-
},
|
|
86
|
-
],
|
|
87
|
-
})(bridge);
|
|
88
|
-
|
|
89
|
-
expect(biome.routes).toHaveLength(1);
|
|
90
|
-
const route = biome.routes?.[0];
|
|
91
|
-
expect(route?.path).toBe('ext/:id');
|
|
92
|
-
expect(route?.access).toBe('public');
|
|
93
|
-
expect(route?.projectScoped).toBe(false);
|
|
94
|
-
// A public route is un-authenticated; it never belongs in the authed rail.
|
|
95
|
-
expect(biome.navItems).toHaveLength(0);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('chrome defaults to host and none passes through', () => {
|
|
99
|
-
const biome = defineWebBiome({
|
|
100
|
-
id: 'test-biome',
|
|
101
|
-
displayName: 'Test',
|
|
102
|
-
pages: [
|
|
103
|
-
{ slug: 'system/a', label: 'A', load },
|
|
104
|
-
{ slug: 'system/b', label: 'B', chrome: 'none', load },
|
|
105
|
-
],
|
|
106
|
-
})(bridge);
|
|
107
|
-
|
|
108
|
-
expect(biome.routes?.find((r) => r.path === 'system/a')?.chrome).toBe(
|
|
109
|
-
'host',
|
|
110
|
-
);
|
|
111
|
-
expect(biome.routes?.find((r) => r.path === 'system/b')?.chrome).toBe(
|
|
112
|
-
'none',
|
|
113
|
-
);
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
describe('defineWebBiome — public is opt-in, never a default', () => {
|
|
118
|
-
/**
|
|
119
|
-
* THE fail-closed invariant. An ordinarily-declared page (no `scope`, no
|
|
120
|
-
* `access`) must emit a route that carries NO `access` marker at all — the
|
|
121
|
-
* host then applies its own `member` default. If the kernel ever started
|
|
122
|
-
* defaulting `access` (or `scope`) to `'public'`, every existing biome route
|
|
123
|
-
* in the fleet would silently become reachable without a session.
|
|
124
|
-
*
|
|
125
|
-
* Mutation-proof (recorded 2026-07-22): flipping the emit in
|
|
126
|
-
* `defineWebBiome` to `access: page.access ?? 'public'` turns this test RED
|
|
127
|
-
* ("expected 'public' to be undefined"), and flipping the validator default
|
|
128
|
-
* `page.access ?? 'member'` to `?? 'public'` makes the ordinary page throw
|
|
129
|
-
* at define time — RED in this test and in the two below. There is no
|
|
130
|
-
* mutation of a default that makes a route public and keeps the suite green.
|
|
131
|
-
*/
|
|
132
|
-
it('an ordinary page emits NO access marker (host defaults to member)', () => {
|
|
133
|
-
const biome = defineWebBiome({
|
|
134
|
-
id: 'test-biome',
|
|
135
|
-
displayName: 'Test',
|
|
136
|
-
pages: [{ slug: 'system/a', label: 'A', load }],
|
|
137
|
-
})(bridge);
|
|
138
|
-
|
|
139
|
-
const route = biome.routes?.[0];
|
|
140
|
-
expect(route?.access).toBeUndefined();
|
|
141
|
-
expect(route?.projectScoped).toBe(false);
|
|
142
|
-
// …and it DOES contribute a nav item, unlike a public route.
|
|
143
|
-
expect(biome.navItems).toHaveLength(1);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('an authenticated page can be chromeless without becoming public', () => {
|
|
147
|
-
// Chrome and access are orthogonal axes: the embedded-app case.
|
|
148
|
-
const biome = defineWebBiome({
|
|
149
|
-
id: 'test-biome',
|
|
150
|
-
displayName: 'Test',
|
|
151
|
-
pages: [
|
|
152
|
-
{
|
|
153
|
-
slug: 'apps/:appId/embed',
|
|
154
|
-
label: 'Embedded app',
|
|
155
|
-
chrome: 'none',
|
|
156
|
-
navHidden: true,
|
|
157
|
-
load,
|
|
158
|
-
},
|
|
159
|
-
],
|
|
160
|
-
})(bridge);
|
|
161
|
-
|
|
162
|
-
const route = biome.routes?.[0];
|
|
163
|
-
expect(route?.chrome).toBe('none');
|
|
164
|
-
expect(route?.access).toBeUndefined();
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('every non-public access tier is rejected on a public page', () => {
|
|
168
|
-
for (const access of ROUTE_ACCESS_VALUES.filter((a) => a !== 'public')) {
|
|
169
|
-
expect(() =>
|
|
170
|
-
defineWebBiome({
|
|
171
|
-
id: 'test-biome',
|
|
172
|
-
displayName: 'Test',
|
|
173
|
-
pages: [{ slug: 'ext', label: 'Ext', scope: 'public', access, load }],
|
|
174
|
-
}),
|
|
175
|
-
).toThrow(/MUST declare access: 'public'/);
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it('every non-public scope rejects access public', () => {
|
|
180
|
-
for (const scope of ROUTE_SCOPE_VALUES.filter((s) => s !== 'public')) {
|
|
181
|
-
expect(() =>
|
|
182
|
-
defineWebBiome({
|
|
183
|
-
id: 'test-biome',
|
|
184
|
-
displayName: 'Test',
|
|
185
|
-
pages: [{ slug: 'x', label: 'X', scope, access: 'public', load }],
|
|
186
|
-
}),
|
|
187
|
-
).toThrow(/only valid on a 'public' scope page/);
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
describe('closed-set completeness', () => {
|
|
193
|
-
/**
|
|
194
|
-
* The runtime tuples ARE the types (`RouteAccess = typeof
|
|
195
|
-
* ROUTE_ACCESS_VALUES[number]`), so a new member cannot be added to the type
|
|
196
|
-
* without appearing here — which is what makes the exhaustive iteration in
|
|
197
|
-
* this file, and the host resolver's `default`-less switch, complete by
|
|
198
|
-
* construction. These assertions are the compile-time proof made executable:
|
|
199
|
-
* the `satisfies` widening below fails to compile if a tuple ever drifts
|
|
200
|
-
* from its type.
|
|
201
|
-
*/
|
|
202
|
-
it('the access tuple enumerates exactly the RouteAccess union', () => {
|
|
203
|
-
const all: readonly RouteAccess[] =
|
|
204
|
-
ROUTE_ACCESS_VALUES satisfies readonly RouteAccess[];
|
|
205
|
-
expect([...all].sort()).toEqual(
|
|
206
|
-
['member', 'org-admin', 'platform-admin', 'public'].sort(),
|
|
207
|
-
);
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it('the scope tuple enumerates exactly the RouteScope union', () => {
|
|
211
|
-
const all: readonly RouteScope[] =
|
|
212
|
-
ROUTE_SCOPE_VALUES satisfies readonly RouteScope[];
|
|
213
|
-
expect([...all].sort()).toEqual(['org', 'project', 'public'].sort());
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it('the chrome tuple enumerates exactly the PageChrome union', () => {
|
|
217
|
-
const all: readonly PageChrome[] =
|
|
218
|
-
PAGE_CHROME_VALUES satisfies readonly PageChrome[];
|
|
219
|
-
expect([...all].sort()).toEqual(['host', 'none'].sort());
|
|
220
|
-
});
|
|
221
|
-
});
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `defineWebBiome` — the authoring helper for `target: 'web'` biomes.
|
|
3
|
-
*
|
|
4
|
-
* Every web biome used to hand-repeat the same ~30 lines: one
|
|
5
|
-
* `lazy(() => import('./pages/X'))` per page, a `navItems` array, and a
|
|
6
|
-
* parallel `routes` array — writing the slug TWICE (once as `navItem.route`,
|
|
7
|
-
* once as `route.path`), the classic source of "nav points at a path no route
|
|
8
|
-
* serves" bugs.
|
|
9
|
-
*
|
|
10
|
-
* `defineWebBiome` collapses that to one declarative page list. Each
|
|
11
|
-
* {@link WebBiomePage} single-sources its `slug` for BOTH the nav route and the
|
|
12
|
-
* route path, so they can never drift. The helper emits one nav item (unless
|
|
13
|
-
* `navHidden`) and one route per page, wrapping the lazily-loaded page in a
|
|
14
|
-
* `<Suspense fallback={null}>` boundary via {@link lazyRoute}.
|
|
15
|
-
*
|
|
16
|
-
* Authors declare WHAT each surface is (`category`) and let the HOST own WHERE
|
|
17
|
-
* it renders in the global menu. `init`/`dispose`/`panels`/`session`/
|
|
18
|
-
* `outputRenderers` pass through to the `FrontendBiome` untouched, so bespoke
|
|
19
|
-
* biomes (one-shot registrations, slot fillers, session contributions) still
|
|
20
|
-
* use this helper.
|
|
21
|
-
*
|
|
22
|
-
* Host-framework-agnostic: pure React (`lazy`/`Suspense`/`createElement` via
|
|
23
|
-
* `lazyRoute`), no router and no Next.js.
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* export default defineWebBiome({
|
|
27
|
-
* id: 'spaces-web',
|
|
28
|
-
* displayName: 'Spaces',
|
|
29
|
-
* pages: [
|
|
30
|
-
* {
|
|
31
|
-
* slug: 'system/spaces',
|
|
32
|
-
* label: 'Spaces',
|
|
33
|
-
* icon: Layers,
|
|
34
|
-
* category: 'knowledge',
|
|
35
|
-
* load: () => import('./pages/SpacesPage'),
|
|
36
|
-
* },
|
|
37
|
-
* ],
|
|
38
|
-
* });
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
import { lazyRoute } from './biome-builders';
|
|
42
|
-
|
|
43
|
-
import type {
|
|
44
|
-
FrontendBiome,
|
|
45
|
-
FrontendBiomeFactory,
|
|
46
|
-
NavItemContribution,
|
|
47
|
-
PageChrome,
|
|
48
|
-
RouteAccess,
|
|
49
|
-
RouteContribution,
|
|
50
|
-
RouteScope,
|
|
51
|
-
} from './frontend-biome';
|
|
52
|
-
import type { ComponentType } from 'react';
|
|
53
|
-
|
|
54
|
-
export interface WebBiomePage {
|
|
55
|
-
/**
|
|
56
|
-
* Single source of truth for BOTH the nav route AND the route path — the
|
|
57
|
-
* helper sets `navItem.route === route.path === slug`, so they can never
|
|
58
|
-
* diverge. May include `:param` segments (e.g. `system/concepts/:slug`).
|
|
59
|
-
*/
|
|
60
|
-
readonly slug: string;
|
|
61
|
-
/**
|
|
62
|
-
* Stable nav-item id used for active-state matching + analytics. Defaults to
|
|
63
|
-
* {@link slug} when omitted — pass it explicitly only to preserve a
|
|
64
|
-
* pre-existing short id (e.g. `grants`) that differs from the route slug
|
|
65
|
-
* (`system/grants`). Must be unique across the biome's pages.
|
|
66
|
-
*/
|
|
67
|
-
readonly id?: string;
|
|
68
|
-
/** Displayed nav label. */
|
|
69
|
-
readonly label: string;
|
|
70
|
-
/** Optional nav icon. Typed to match {@link NavItemContribution.icon}. */
|
|
71
|
-
readonly icon?: ComponentType<{ className?: string | undefined }>;
|
|
72
|
-
/**
|
|
73
|
-
* Host menu category — the biome's INTENT (`primary` | `build` | `operate` |
|
|
74
|
-
* `knowledge` | `admin` | `account`). The host maps it to a concrete rail
|
|
75
|
-
* group + order.
|
|
76
|
-
*/
|
|
77
|
-
readonly category?: string;
|
|
78
|
-
/**
|
|
79
|
-
* Route scope. `'org'` (default) mounts at the org root; `'project'` mounts
|
|
80
|
-
* under the canonical project-scoped route prefix; `'public'` mounts OUTSIDE
|
|
81
|
-
* the host's authenticated group (no Keycloak session, no login redirect, no
|
|
82
|
-
* org switcher). Maps to {@link RouteContribution.projectScoped} for the
|
|
83
|
-
* authenticated scopes; a `'public'` page MUST also declare
|
|
84
|
-
* `access: 'public'` (enforced at define time).
|
|
85
|
-
*/
|
|
86
|
-
readonly scope?: RouteScope;
|
|
87
|
-
/**
|
|
88
|
-
* Access tier required to render the route. Defaults to `'member'`. A
|
|
89
|
-
* `'public'` page (scope `'public'`) MUST set this to `'public'`; a non-public
|
|
90
|
-
* page MUST NOT — both contradictions are rejected at define time.
|
|
91
|
-
*/
|
|
92
|
-
readonly access?: RouteAccess;
|
|
93
|
-
/**
|
|
94
|
-
* Whether the host wraps the page in its application shell. Defaults to
|
|
95
|
-
* `'host'`; `'none'` renders with no app rail, topbar, or org switcher.
|
|
96
|
-
*/
|
|
97
|
-
readonly chrome?: PageChrome;
|
|
98
|
-
/** Dynamic import of the page module (`() => import('./pages/X')`). */
|
|
99
|
-
readonly load: () => Promise<{ default: ComponentType<unknown> }>;
|
|
100
|
-
/**
|
|
101
|
-
* When true the route is registered but NO nav item is emitted — for hidden
|
|
102
|
-
* surfaces such as a `:param` detail route reached only by deeplink.
|
|
103
|
-
*/
|
|
104
|
-
readonly navHidden?: boolean;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export interface DefineWebBiomeOptions {
|
|
108
|
-
/** Stable biome id; matches `xema-biome.json` `xema.id`. */
|
|
109
|
-
readonly id: string;
|
|
110
|
-
/** Displayed name. */
|
|
111
|
-
readonly displayName: string;
|
|
112
|
-
/** One-shot initializer; passed through to {@link FrontendBiome.init}. */
|
|
113
|
-
readonly init?: FrontendBiome['init'];
|
|
114
|
-
/** Teardown counterpart; passed through to {@link FrontendBiome.dispose}. */
|
|
115
|
-
readonly dispose?: FrontendBiome['dispose'];
|
|
116
|
-
/** The biome's pages — one nav item (unless `navHidden`) + one route each. */
|
|
117
|
-
readonly pages: readonly WebBiomePage[];
|
|
118
|
-
/** Slot fillers; passed through to {@link FrontendBiome.panels}. */
|
|
119
|
-
readonly panels?: FrontendBiome['panels'];
|
|
120
|
-
/** Session-shell contributions; passed through to {@link FrontendBiome.session}. */
|
|
121
|
-
readonly session?: FrontendBiome['session'];
|
|
122
|
-
/** Output renderers; passed through to {@link FrontendBiome.outputRenderers}. */
|
|
123
|
-
readonly outputRenderers?: FrontendBiome['outputRenderers'];
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/** Default host-shell mode a page renders in when it omits `chrome`. */
|
|
127
|
-
const DEFAULT_PAGE_CHROME: PageChrome = 'host';
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Reject the two `scope`/`access` contradictions at define time (module load,
|
|
131
|
-
* when the biome calls `defineWebBiome(...)` at its top level) — fail fast, do
|
|
132
|
-
* NOT silently coerce:
|
|
133
|
-
*
|
|
134
|
-
* 1. A `public` page with any `access` other than `'public'`: a public route
|
|
135
|
-
* is un-authenticated, so a privilege tier is meaningless. The author must
|
|
136
|
-
* set `access: 'public'` explicitly (an omitted `access` defaults to
|
|
137
|
-
* `'member'`, which is exactly this contradiction).
|
|
138
|
-
* 2. A non-`public` page declaring `access: 'public'`: `'public'` is the
|
|
139
|
-
* no-auth marker; an org/project route is always authenticated.
|
|
140
|
-
*
|
|
141
|
-
* `scope` defaults to `'org'` and `access` defaults to `'member'` for the
|
|
142
|
-
* purpose of this check, matching the emit defaults below.
|
|
143
|
-
*/
|
|
144
|
-
function assertPageScopeAccessLocked(biomeId: string, page: WebBiomePage): void {
|
|
145
|
-
const scope: RouteScope = page.scope ?? 'org';
|
|
146
|
-
const access: RouteAccess = page.access ?? 'member';
|
|
147
|
-
const where = `web biome '${biomeId}' page '${page.slug}'`;
|
|
148
|
-
|
|
149
|
-
if (scope === 'public' && access !== 'public') {
|
|
150
|
-
throw new Error(
|
|
151
|
-
`${where}: a 'public' scope page MUST declare access: 'public' ` +
|
|
152
|
-
`(got access: '${access}'). A public route is un-authenticated, so an ` +
|
|
153
|
-
`authenticated privilege tier is a contradiction. Set access: 'public' ` +
|
|
154
|
-
`or change the scope.`,
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
if (scope !== 'public' && access === 'public') {
|
|
158
|
-
throw new Error(
|
|
159
|
-
`${where}: access: 'public' is only valid on a 'public' scope page ` +
|
|
160
|
-
`(got scope: '${scope}'). '${access}' is the no-auth marker; an ` +
|
|
161
|
-
`org/project route is always authenticated. Set scope: 'public' or ` +
|
|
162
|
-
`change the access tier.`,
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Build the `FrontendBiomeFactory` a web biome default-exports. See the
|
|
169
|
-
* module doc-comment for the rationale and an example.
|
|
170
|
-
*/
|
|
171
|
-
export function defineWebBiome(
|
|
172
|
-
options: DefineWebBiomeOptions,
|
|
173
|
-
): FrontendBiomeFactory {
|
|
174
|
-
// Validate every page's scope/access pairing NOW, at module load, so a
|
|
175
|
-
// contradiction surfaces the instant the biome module is imported — not
|
|
176
|
-
// lazily when the host first calls the factory.
|
|
177
|
-
for (const page of options.pages) {
|
|
178
|
-
assertPageScopeAccessLocked(options.id, page);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return () => {
|
|
182
|
-
const navItems: NavItemContribution[] = [];
|
|
183
|
-
const routes: RouteContribution[] = [];
|
|
184
|
-
|
|
185
|
-
for (const page of options.pages) {
|
|
186
|
-
routes.push({
|
|
187
|
-
path: page.slug,
|
|
188
|
-
projectScoped: page.scope === 'project',
|
|
189
|
-
element: lazyRoute(page.load),
|
|
190
|
-
chrome: page.chrome ?? DEFAULT_PAGE_CHROME,
|
|
191
|
-
...(page.access ? { access: page.access } : {}),
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// Public routes mount OUTSIDE the authenticated group and never belong in
|
|
195
|
-
// the authenticated app rail — so they contribute no nav item regardless
|
|
196
|
-
// of `navHidden`. Authenticated pages emit a nav item unless hidden.
|
|
197
|
-
if (!page.navHidden && page.scope !== 'public') {
|
|
198
|
-
navItems.push({
|
|
199
|
-
id: page.id ?? page.slug,
|
|
200
|
-
label: page.label,
|
|
201
|
-
route: page.slug,
|
|
202
|
-
...(page.icon ? { icon: page.icon } : {}),
|
|
203
|
-
...(page.category !== undefined ? { category: page.category } : {}),
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return {
|
|
209
|
-
id: options.id,
|
|
210
|
-
displayName: options.displayName,
|
|
211
|
-
navItems,
|
|
212
|
-
routes,
|
|
213
|
-
...(options.init ? { init: options.init } : {}),
|
|
214
|
-
...(options.dispose ? { dispose: options.dispose } : {}),
|
|
215
|
-
...(options.panels ? { panels: options.panels } : {}),
|
|
216
|
-
...(options.session ? { session: options.session } : {}),
|
|
217
|
-
...(options.outputRenderers
|
|
218
|
-
? { outputRenderers: options.outputRenderers }
|
|
219
|
-
: {}),
|
|
220
|
-
};
|
|
221
|
-
};
|
|
222
|
-
}
|