@xemahq/ui-kernel 0.5.0 → 0.6.1
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/lib/biome-host/define-web-biome.d.ts +3 -3
- package/dist/lib/biome-host/define-web-biome.d.ts.map +1 -1
- package/dist/lib/biome-host/define-web-biome.js +23 -2
- package/dist/lib/biome-host/define-web-biome.js.map +1 -1
- package/dist/lib/biome-host/frontend-biome.d.ts +4 -3
- package/dist/lib/biome-host/frontend-biome.d.ts.map +1 -1
- package/dist/lib/biome-host/host-bridge.d.ts +2 -0
- package/dist/lib/biome-host/host-bridge.d.ts.map +1 -1
- package/dist/lib/biome-host/host-bridge.js.map +1 -1
- package/dist/lib/biome-host/i18n-hooks.d.ts +4 -0
- package/dist/lib/biome-host/i18n-hooks.d.ts.map +1 -0
- package/dist/lib/biome-host/i18n-hooks.js +22 -0
- package/dist/lib/biome-host/i18n-hooks.js.map +1 -0
- package/dist/lib/biome-host/i18n-port.d.ts +15 -0
- package/dist/lib/biome-host/i18n-port.d.ts.map +1 -0
- package/dist/lib/biome-host/i18n-port.js +29 -0
- package/dist/lib/biome-host/i18n-port.js.map +1 -0
- package/dist/lib/biome-host/index.d.ts +2 -1
- package/dist/lib/biome-host/index.d.ts.map +1 -1
- package/dist/lib/biome-host/index.js +2 -1
- package/dist/lib/biome-host/index.js.map +1 -1
- package/dist/lib/biome-host/realtime-port.d.ts +1 -1
- package/dist/lib/biome-host/realtime-port.d.ts.map +1 -1
- package/dist/session-kit/approvals/approval-model.d.ts.map +1 -1
- package/dist/session-kit/approvals/approval-model.js.map +1 -1
- package/dist/ui/branding/app-branding-provider.d.ts +17 -0
- package/dist/ui/branding/app-branding-provider.d.ts.map +1 -0
- package/dist/ui/branding/app-branding-provider.js +81 -0
- package/dist/ui/branding/app-branding-provider.js.map +1 -0
- package/dist/ui/branding/sanitize.d.ts +4 -0
- package/dist/ui/branding/sanitize.d.ts.map +1 -0
- package/dist/ui/branding/sanitize.js +47 -0
- package/dist/ui/branding/sanitize.js.map +1 -0
- package/dist/ui/breakpoints.d.ts +2 -0
- package/dist/ui/breakpoints.d.ts.map +1 -0
- package/dist/ui/breakpoints.js +5 -0
- package/dist/ui/breakpoints.js.map +1 -0
- package/dist/ui/charts/bar-chart.d.ts +7 -0
- package/dist/ui/charts/bar-chart.d.ts.map +1 -0
- package/dist/ui/charts/bar-chart.js +47 -0
- package/dist/ui/charts/bar-chart.js.map +1 -0
- package/dist/ui/charts/chart-frame.d.ts +25 -0
- package/dist/ui/charts/chart-frame.d.ts.map +1 -0
- package/dist/ui/charts/chart-frame.js +23 -0
- package/dist/ui/charts/chart-frame.js.map +1 -0
- package/dist/ui/charts/chart-types.d.ts +19 -0
- package/dist/ui/charts/chart-types.d.ts.map +1 -0
- package/dist/ui/charts/chart-types.js +3 -0
- package/dist/ui/charts/chart-types.js.map +1 -0
- package/dist/ui/charts/chart-utils.d.ts +22 -0
- package/dist/ui/charts/chart-utils.d.ts.map +1 -0
- package/dist/ui/charts/chart-utils.js +97 -0
- package/dist/ui/charts/chart-utils.js.map +1 -0
- package/dist/ui/charts/donut-chart.d.ts +7 -0
- package/dist/ui/charts/donut-chart.d.ts.map +1 -0
- package/dist/ui/charts/donut-chart.js +55 -0
- package/dist/ui/charts/donut-chart.js.map +1 -0
- package/dist/ui/charts/index.d.ts +8 -0
- package/dist/ui/charts/index.d.ts.map +1 -0
- package/dist/ui/charts/index.js +21 -0
- package/dist/ui/charts/index.js.map +1 -0
- package/dist/ui/charts/line-chart.d.ts +7 -0
- package/dist/ui/charts/line-chart.d.ts.map +1 -0
- package/dist/ui/charts/line-chart.js +70 -0
- package/dist/ui/charts/line-chart.js.map +1 -0
- package/dist/ui/charts/sparkline.d.ts +13 -0
- package/dist/ui/charts/sparkline.d.ts.map +1 -0
- package/dist/ui/charts/sparkline.js +39 -0
- package/dist/ui/charts/sparkline.js.map +1 -0
- package/dist/ui/hooks/use-is-mobile.d.ts +2 -0
- package/dist/ui/hooks/use-is-mobile.d.ts.map +1 -0
- package/dist/ui/hooks/use-is-mobile.js +19 -0
- package/dist/ui/hooks/use-is-mobile.js.map +1 -0
- package/dist/ui/index.d.ts +6 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +14 -1
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/pdf/pdf-preview.d.ts +8 -0
- package/dist/ui/pdf/pdf-preview.d.ts.map +1 -0
- package/dist/ui/pdf/pdf-preview.js +34 -0
- package/dist/ui/pdf/pdf-preview.js.map +1 -0
- package/dist/ui/primitives/preview-device-frame.d.ts +9 -0
- package/dist/ui/primitives/preview-device-frame.d.ts.map +1 -0
- package/dist/ui/primitives/preview-device-frame.js +90 -0
- package/dist/ui/primitives/preview-device-frame.js.map +1 -0
- package/package.json +8 -7
- package/src/lib/biome-host/define-web-biome.test.js +93 -0
- package/src/lib/biome-host/define-web-biome.ts +76 -15
- package/src/lib/biome-host/frontend-biome.ts +47 -18
- package/src/lib/biome-host/host-bridge.ts +13 -0
- package/src/lib/biome-host/i18n-hooks.ts +50 -0
- package/src/lib/biome-host/i18n-port.test.ts +50 -0
- package/src/lib/biome-host/i18n-port.ts +96 -0
- package/src/lib/biome-host/index.ts +2 -1
- package/src/lib/biome-host/realtime-port.ts +9 -3
- package/src/lib/system-bus/enums.ts +1 -1
- package/src/lib/system-bus/index.ts +1 -1
- package/src/lib/system-bus/intents.ts +1 -1
- package/src/session-kit/approvals/ApprovalCard.tsx +1 -1
- package/src/session-kit/approvals/ApprovalsCenter.tsx +2 -2
- package/src/session-kit/approvals/CapabilityApprovalStickyBar.tsx +1 -1
- package/src/session-kit/approvals/Obligation.tsx +1 -1
- package/src/session-kit/approvals/ScopedBody.tsx +1 -1
- package/src/session-kit/approvals/approval-model.ts +7 -8
- package/src/session-kit/approvals/index.ts +1 -1
- package/src/session-kit/approvals/obligation-display.ts +1 -1
- package/src/session-kit/approvals/risk-accent.ts +1 -1
- package/src/session-kit/combobox/Combobox.tsx +1 -1
- package/src/session-kit/display/MessageTurn.tsx +1 -1
- package/src/session-kit/display/TokenMeter.tsx +1 -1
- package/src/session-kit/index.ts +3 -3
- package/src/session-kit/lib/enums.ts +1 -1
- package/src/session-kit/lib/status-dot.ts +1 -1
- package/src/session-kit/primitives/RiskPill.tsx +1 -1
- package/src/ui/branding/app-branding-provider.tsx +146 -0
- package/src/ui/branding/sanitize.ts +77 -0
- package/src/ui/breakpoints.ts +14 -0
- package/src/ui/charts/bar-chart.tsx +135 -0
- package/src/ui/charts/chart-frame.tsx +112 -0
- package/src/ui/charts/chart-types.ts +52 -0
- package/src/ui/charts/chart-utils.ts +158 -0
- package/src/ui/charts/charts.test.tsx +204 -0
- package/src/ui/charts/donut-chart.tsx +111 -0
- package/src/ui/charts/index.ts +27 -0
- package/src/ui/charts/line-chart.tsx +180 -0
- package/src/ui/charts/sparkline.tsx +101 -0
- package/src/ui/hooks/use-is-mobile.ts +28 -0
- package/src/ui/index.ts +22 -0
- package/src/ui/pdf/pdf-preview.tsx +122 -0
- package/src/ui/primitives/preview-device-frame.tsx +205 -0
- package/dist/lib/biome-host/composition-validation.d.ts +0 -22
- package/dist/lib/biome-host/composition-validation.d.ts.map +0 -1
- package/dist/lib/biome-host/composition-validation.js +0 -127
- package/dist/lib/biome-host/composition-validation.js.map +0 -1
- package/dist/lib/biome-host/nav.d.ts +0 -17
- package/dist/lib/biome-host/nav.d.ts.map +0 -1
- package/dist/lib/biome-host/nav.js +0 -52
- package/dist/lib/biome-host/nav.js.map +0 -1
- package/dist/registry/lib/composition-validation-host.d.ts +0 -3
- package/dist/registry/lib/composition-validation-host.d.ts.map +0 -1
- package/dist/registry/lib/composition-validation-host.js +0 -10
- package/dist/registry/lib/composition-validation-host.js.map +0 -1
- package/src/lib/biome-host/nav.ts +0 -83
package/dist/ui/index.js
CHANGED
|
@@ -17,12 +17,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
17
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.AsyncBoundary = exports.LoadingState = exports.PageToolbar = exports.PageHeader = exports.StateCard = exports.EmptyState = exports.PageShell = exports.ConfirmDeleteDialog = exports.ScopeBadge = exports.StatusBadge = exports.ErrorCard = exports.useDebouncedValue = exports.cn = void 0;
|
|
20
|
+
exports.PdfPreview = exports.AsyncBoundary = exports.LoadingState = exports.PageToolbar = exports.PageHeader = exports.StateCard = exports.EmptyState = exports.PageShell = exports.ConfirmDeleteDialog = exports.ScopeBadge = exports.StatusBadge = exports.ErrorCard = exports.APP_PRIMARY_COLOR_VAR = exports.useAppBranding = exports.AppBrandingProvider = exports.PreviewDeviceFrame = exports.useIsMobile = exports.useDebouncedValue = exports.MOBILE_BREAKPOINT = exports.cn = void 0;
|
|
21
21
|
var cn_1 = require("./cn");
|
|
22
22
|
Object.defineProperty(exports, "cn", { enumerable: true, get: function () { return cn_1.cn; } });
|
|
23
23
|
__exportStar(require("./design-tokens"), exports);
|
|
24
|
+
var breakpoints_1 = require("./breakpoints");
|
|
25
|
+
Object.defineProperty(exports, "MOBILE_BREAKPOINT", { enumerable: true, get: function () { return breakpoints_1.MOBILE_BREAKPOINT; } });
|
|
24
26
|
var use_debounced_value_1 = require("./hooks/use-debounced-value");
|
|
25
27
|
Object.defineProperty(exports, "useDebouncedValue", { enumerable: true, get: function () { return use_debounced_value_1.useDebouncedValue; } });
|
|
28
|
+
var use_is_mobile_1 = require("./hooks/use-is-mobile");
|
|
29
|
+
Object.defineProperty(exports, "useIsMobile", { enumerable: true, get: function () { return use_is_mobile_1.useIsMobile; } });
|
|
26
30
|
__exportStar(require("./primitives/button"), exports);
|
|
27
31
|
__exportStar(require("./primitives/badge"), exports);
|
|
28
32
|
__exportStar(require("./primitives/input"), exports);
|
|
@@ -50,6 +54,13 @@ __exportStar(require("./primitives/tag-multi-select"), exports);
|
|
|
50
54
|
__exportStar(require("./primitives/sheet"), exports);
|
|
51
55
|
__exportStar(require("./primitives/form-stepper"), exports);
|
|
52
56
|
__exportStar(require("./primitives/async-combobox"), exports);
|
|
57
|
+
var preview_device_frame_1 = require("./primitives/preview-device-frame");
|
|
58
|
+
Object.defineProperty(exports, "PreviewDeviceFrame", { enumerable: true, get: function () { return preview_device_frame_1.PreviewDeviceFrame; } });
|
|
59
|
+
__exportStar(require("./charts"), exports);
|
|
60
|
+
var app_branding_provider_1 = require("./branding/app-branding-provider");
|
|
61
|
+
Object.defineProperty(exports, "AppBrandingProvider", { enumerable: true, get: function () { return app_branding_provider_1.AppBrandingProvider; } });
|
|
62
|
+
Object.defineProperty(exports, "useAppBranding", { enumerable: true, get: function () { return app_branding_provider_1.useAppBranding; } });
|
|
63
|
+
Object.defineProperty(exports, "APP_PRIMARY_COLOR_VAR", { enumerable: true, get: function () { return app_branding_provider_1.APP_PRIMARY_COLOR_VAR; } });
|
|
53
64
|
var ErrorCard_1 = require("./chrome/ErrorCard");
|
|
54
65
|
Object.defineProperty(exports, "ErrorCard", { enumerable: true, get: function () { return __importDefault(ErrorCard_1).default; } });
|
|
55
66
|
var status_badge_1 = require("./chrome/status-badge");
|
|
@@ -71,4 +82,6 @@ var LoadingState_1 = require("./chrome/LoadingState");
|
|
|
71
82
|
Object.defineProperty(exports, "LoadingState", { enumerable: true, get: function () { return __importDefault(LoadingState_1).default; } });
|
|
72
83
|
var AsyncBoundary_1 = require("./chrome/AsyncBoundary");
|
|
73
84
|
Object.defineProperty(exports, "AsyncBoundary", { enumerable: true, get: function () { return __importDefault(AsyncBoundary_1).default; } });
|
|
85
|
+
var pdf_preview_1 = require("./pdf/pdf-preview");
|
|
86
|
+
Object.defineProperty(exports, "PdfPreview", { enumerable: true, get: function () { return pdf_preview_1.PdfPreview; } });
|
|
74
87
|
//# sourceMappingURL=index.js.map
|
package/dist/ui/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAYA,2BAA0B;AAAjB,wFAAA,EAAE,OAAA;AAGX,kDAAgC;AAGhC,mEAAgE;AAAvD,wHAAA,iBAAiB,OAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAYA,2BAA0B;AAAjB,wFAAA,EAAE,OAAA;AAGX,kDAAgC;AAGhC,6CAAkD;AAAzC,gHAAA,iBAAiB,OAAA;AAG1B,mEAAgE;AAAvD,wHAAA,iBAAiB,OAAA;AAC1B,uDAAoD;AAA3C,4GAAA,WAAW,OAAA;AAGpB,sDAAoC;AACpC,qDAAmC;AACnC,qDAAmC;AACnC,qDAAmC;AACnC,sDAAoC;AACpC,sDAAoC;AACpC,oDAAkC;AAClC,wDAAsC;AACtC,oDAAkC;AAClC,6DAA2C;AAC3C,wDAAsC;AACtC,wDAAsC;AACtC,qDAAmC;AACnC,uDAAqC;AACrC,sDAAoC;AACpC,yDAAuC;AACvC,2DAAyC;AACzC,4DAA0C;AAC1C,uDAAqC;AACrC,2DAAyC;AACzC,yDAAuC;AACvC,2DAAyC;AACzC,6DAA2C;AAC3C,gEAA8C;AAC9C,qDAAmC;AACnC,4DAA0C;AAC1C,8DAA4C;AAC5C,0EAG2C;AAFzC,0HAAA,kBAAkB,OAAA;AAKpB,2CAAyB;AAGzB,0EAM0C;AALxC,4HAAA,mBAAmB,OAAA;AACnB,uHAAA,cAAc,OAAA;AACd,8HAAA,qBAAqB,OAAA;AAMvB,gDAAsF;AAA7E,uHAAA,OAAO,OAAa;AAC7B,sDAA4G;AAAnG,4HAAA,OAAO,OAAe;AAC/B,oDAAmF;AAA1E,0HAAA,OAAO,OAAc;AAC9B,wEAGwC;AAFtC,6IAAA,OAAO,OAAuB;AAGhC,kDAA2D;AAAlD,wHAAA,OAAO,OAAa;AAC7B,kDAK6B;AAJ3B,yHAAA,OAAO,OAAc;AAKvB,gDAA+C;AAAtC,sGAAA,SAAS,OAAA;AAClB,kDAAyE;AAAhE,yHAAA,OAAO,OAAc;AAAE,yGAAA,WAAW,OAAA;AAC3C,sDAA0F;AAAjF,6HAAA,OAAO,OAAgB;AAChC,wDAAkE;AAAzD,+HAAA,OAAO,OAAiB;AAEjC,iDAAqE;AAA5D,yGAAA,UAAU,OAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type JSX } from 'react';
|
|
2
|
+
export interface PdfPreviewProps {
|
|
3
|
+
readonly url: string;
|
|
4
|
+
readonly title?: string;
|
|
5
|
+
readonly className?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function PdfPreview({ url, title, className }: PdfPreviewProps): JSX.Element;
|
|
8
|
+
//# sourceMappingURL=pdf-preview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pdf-preview.d.ts","sourceRoot":"","sources":["../../../src/ui/pdf/pdf-preview.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAkC,KAAK,GAAG,EAAE,MAAM,OAAO,CAAC;AAsBjE,MAAM,WAAW,eAAe;IAE9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAErB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,UAAU,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,eAAe,GAAG,GAAG,CAAC,OAAO,CA2ElF"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.PdfPreview = PdfPreview;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
const lucide_react_1 = require("lucide-react");
|
|
7
|
+
const react_1 = require("react");
|
|
8
|
+
const react_pdf_1 = require("react-pdf");
|
|
9
|
+
const cn_1 = require("../cn");
|
|
10
|
+
if (!react_pdf_1.pdfjs.GlobalWorkerOptions.workerSrc) {
|
|
11
|
+
react_pdf_1.pdfjs.GlobalWorkerOptions.workerSrc = '/pdf.worker.min.mjs';
|
|
12
|
+
}
|
|
13
|
+
const MIN_SCALE = 0.5;
|
|
14
|
+
const MAX_SCALE = 3;
|
|
15
|
+
const STEP = 0.25;
|
|
16
|
+
const DEFAULT_SCALE = 1;
|
|
17
|
+
const clampScale = (s) => Math.min(MAX_SCALE, Math.max(MIN_SCALE, Math.round(s * 100) / 100));
|
|
18
|
+
function PdfPreview({ url, title, className }) {
|
|
19
|
+
const [numPages, setNumPages] = (0, react_1.useState)(0);
|
|
20
|
+
const [scale, setScale] = (0, react_1.useState)(DEFAULT_SCALE);
|
|
21
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
22
|
+
const file = (0, react_1.useMemo)(() => ({ url }), [url]);
|
|
23
|
+
const zoomOut = (0, react_1.useCallback)(() => setScale((s) => clampScale(s - STEP)), []);
|
|
24
|
+
const zoomIn = (0, react_1.useCallback)(() => setScale((s) => clampScale(s + STEP)), []);
|
|
25
|
+
const reset = (0, react_1.useCallback)(() => setScale(DEFAULT_SCALE), []);
|
|
26
|
+
const btn = 'inline-flex h-7 w-7 items-center justify-center rounded text-neutral-600 ' +
|
|
27
|
+
'hover:bg-neutral-200/70 disabled:pointer-events-none disabled:opacity-40 ' +
|
|
28
|
+
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1';
|
|
29
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, cn_1.cn)('flex h-[520px] flex-col overflow-hidden rounded border border-neutral-200 bg-white', className), children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1 border-b border-neutral-200 bg-neutral-50 px-2 py-1", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: zoomOut, disabled: scale <= MIN_SCALE, "aria-label": "Zoom out", className: btn, children: (0, jsx_runtime_1.jsx)(lucide_react_1.ZoomOut, { className: "h-4 w-4" }) }), (0, jsx_runtime_1.jsxs)("span", { className: "w-12 text-center text-xs tabular-nums text-neutral-500", children: [Math.round(scale * 100), "%"] }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: zoomIn, disabled: scale >= MAX_SCALE, "aria-label": "Zoom in", className: btn, children: (0, jsx_runtime_1.jsx)(lucide_react_1.ZoomIn, { className: "h-4 w-4" }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: reset, disabled: scale === DEFAULT_SCALE, "aria-label": "Reset zoom", className: btn, children: (0, jsx_runtime_1.jsx)(lucide_react_1.RotateCcw, { className: "h-4 w-4" }) }), title ? ((0, jsx_runtime_1.jsx)("span", { className: "ml-2 min-w-0 flex-1 truncate text-xs text-neutral-400", title: title, children: title })) : null] }), (0, jsx_runtime_1.jsx)("div", { className: "flex-1 overflow-auto bg-neutral-100 p-3", children: error ? ((0, jsx_runtime_1.jsxs)("div", { className: "p-4 text-xs text-neutral-500", children: ["Couldn't render this PDF (", error, "). Use the download affordance."] })) : ((0, jsx_runtime_1.jsx)(react_pdf_1.Document, { file: file, onLoadSuccess: ({ numPages: n }) => {
|
|
30
|
+
setNumPages(n);
|
|
31
|
+
setError(null);
|
|
32
|
+
}, onLoadError: (e) => setError(e.message), loading: (0, jsx_runtime_1.jsx)("div", { className: "p-4 text-xs text-neutral-500", children: "Loading PDF\u2026" }), className: "flex flex-col items-center gap-3", children: Array.from({ length: numPages }, (_, i) => ((0, jsx_runtime_1.jsx)(react_pdf_1.Page, { pageNumber: i + 1, scale: scale, renderTextLayer: false, renderAnnotationLayer: false, className: "shadow-sm" }, i))) })) })] }));
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=pdf-preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pdf-preview.js","sourceRoot":"","sources":["../../../src/ui/pdf/pdf-preview.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export type PreviewDevice = 'desktop' | 'tablet' | 'mobile' | 'presentation' | 'a4';
|
|
3
|
+
interface PreviewDeviceFrameProps {
|
|
4
|
+
readonly device: PreviewDevice;
|
|
5
|
+
readonly children: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
export declare function PreviewDeviceFrame({ device, children, }: PreviewDeviceFrameProps): import("react").JSX.Element;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=preview-device-frame.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview-device-frame.d.ts","sourceRoot":"","sources":["../../../src/ui/primitives/preview-device-frame.tsx"],"names":[],"mappings":"AAmBA,OAAO,EAAqC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAS1E,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,cAAc,GACd,IAAI,CAAC;AAET,UAAU,uBAAuB;IAC/B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;CAC9B;AAoBD,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,QAAQ,GACT,EAAE,uBAAuB,+BAmBzB"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PreviewDeviceFrame = PreviewDeviceFrame;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const cn_1 = require("../cn");
|
|
7
|
+
const BEZELS = {
|
|
8
|
+
tablet: { width: 768, height: 1024, radius: 18 },
|
|
9
|
+
mobile: { width: 390, height: 844, radius: 36 },
|
|
10
|
+
};
|
|
11
|
+
const PRESENTATION_VIEWPORT = { width: 1280, height: 720, radius: 20 };
|
|
12
|
+
const A4_VIEWPORT = { width: 794, height: 1123, radius: 4 };
|
|
13
|
+
function PreviewDeviceFrame({ device, children, }) {
|
|
14
|
+
if (device === 'desktop') {
|
|
15
|
+
return (0, jsx_runtime_1.jsx)("div", { className: "h-full w-full", children: children });
|
|
16
|
+
}
|
|
17
|
+
if (device === 'presentation') {
|
|
18
|
+
return ((0, jsx_runtime_1.jsx)(ResponsiveViewport, { bezel: PRESENTATION_VIEWPORT, aspectRatio: 16 / 9, children: children }));
|
|
19
|
+
}
|
|
20
|
+
if (device === 'a4') {
|
|
21
|
+
return ((0, jsx_runtime_1.jsx)(ResponsiveViewport, { bezel: A4_VIEWPORT, aspectRatio: 794 / 1123, children: children }));
|
|
22
|
+
}
|
|
23
|
+
return (0, jsx_runtime_1.jsx)(ScaledBezel, { bezel: BEZELS[device], children: children });
|
|
24
|
+
}
|
|
25
|
+
function ScaledBezel({ bezel, children }) {
|
|
26
|
+
const containerRef = (0, react_1.useRef)(null);
|
|
27
|
+
const [scale, setScale] = (0, react_1.useState)(1);
|
|
28
|
+
(0, react_1.useLayoutEffect)(() => {
|
|
29
|
+
const el = containerRef.current;
|
|
30
|
+
if (!el)
|
|
31
|
+
return;
|
|
32
|
+
const compute = () => {
|
|
33
|
+
const { clientWidth, clientHeight } = el;
|
|
34
|
+
if (clientWidth === 0 || clientHeight === 0)
|
|
35
|
+
return;
|
|
36
|
+
const margin = 24;
|
|
37
|
+
const availW = Math.max(0, clientWidth - margin);
|
|
38
|
+
const availH = Math.max(0, clientHeight - margin);
|
|
39
|
+
const next = Math.min(1, availW / bezel.width, availH / bezel.height);
|
|
40
|
+
setScale(next);
|
|
41
|
+
};
|
|
42
|
+
compute();
|
|
43
|
+
const observer = new ResizeObserver(compute);
|
|
44
|
+
observer.observe(el);
|
|
45
|
+
return () => observer.disconnect();
|
|
46
|
+
}, [bezel.width, bezel.height]);
|
|
47
|
+
return ((0, jsx_runtime_1.jsx)("div", { ref: containerRef, className: "flex h-full w-full items-center justify-center overflow-hidden bg-paper-elev/60 p-2", children: (0, jsx_runtime_1.jsx)("div", { className: (0, cn_1.cn)('relative shrink-0 overflow-hidden bg-paper shadow-2xl ring-1 ring-border/75'), style: {
|
|
48
|
+
width: bezel.width,
|
|
49
|
+
height: bezel.height,
|
|
50
|
+
borderRadius: bezel.radius,
|
|
51
|
+
transform: `scale(${scale})`,
|
|
52
|
+
transformOrigin: 'center center',
|
|
53
|
+
}, children: children }) }));
|
|
54
|
+
}
|
|
55
|
+
function ResponsiveViewport({ bezel, aspectRatio, children, }) {
|
|
56
|
+
const containerRef = (0, react_1.useRef)(null);
|
|
57
|
+
const [size, setSize] = (0, react_1.useState)(() => ({
|
|
58
|
+
width: bezel.width,
|
|
59
|
+
height: bezel.height,
|
|
60
|
+
}));
|
|
61
|
+
(0, react_1.useLayoutEffect)(() => {
|
|
62
|
+
const el = containerRef.current;
|
|
63
|
+
if (!el)
|
|
64
|
+
return;
|
|
65
|
+
const compute = () => {
|
|
66
|
+
const { clientWidth, clientHeight } = el;
|
|
67
|
+
if (clientWidth === 0 || clientHeight === 0)
|
|
68
|
+
return;
|
|
69
|
+
const margin = 24;
|
|
70
|
+
const availW = Math.max(0, clientWidth - margin);
|
|
71
|
+
const availH = Math.max(0, clientHeight - margin);
|
|
72
|
+
const nextWidth = Math.min(availW, availH * aspectRatio);
|
|
73
|
+
const nextHeight = nextWidth / aspectRatio;
|
|
74
|
+
setSize({
|
|
75
|
+
width: Math.max(1, Math.round(nextWidth)),
|
|
76
|
+
height: Math.max(1, Math.round(nextHeight)),
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
compute();
|
|
80
|
+
const observer = new ResizeObserver(compute);
|
|
81
|
+
observer.observe(el);
|
|
82
|
+
return () => observer.disconnect();
|
|
83
|
+
}, [bezel.width, bezel.height]);
|
|
84
|
+
return ((0, jsx_runtime_1.jsx)("div", { ref: containerRef, className: "flex h-full w-full items-center justify-center overflow-hidden bg-paper-elev/60 p-2", children: (0, jsx_runtime_1.jsx)("div", { className: (0, cn_1.cn)('relative shrink-0 overflow-hidden bg-paper shadow-2xl ring-1 ring-border/75'), style: {
|
|
85
|
+
width: size.width,
|
|
86
|
+
height: size.height,
|
|
87
|
+
borderRadius: bezel.radius,
|
|
88
|
+
}, children: children }) }));
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=preview-device-frame.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview-device-frame.js","sourceRoot":"","sources":["../../../src/ui/primitives/preview-device-frame.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xemahq/ui-kernel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Host-framework-agnostic UI kernel for the Xema OS. Defines the SystemBus orchestration contract (capability.invoke, cross-biome intents, command palette, xema:// deeplinks, window manager) AND the biome-host contract surface (FrontendBiome/FrontendBiomeFactory, HostBridge, the singleton biomeRegistry, session contributions) that every frontend biome composes against. No Vite, Next.js, or React-Router — React itself IS allowed as the shared component model (the contracts traffic in ReactNode/ComponentType and a React context). Concrete host adapters (router/auth/toast wiring) live in separate packages that consume this kernel. The SystemBus is pure orchestration: it never authorizes, the backend capability-router enforces all policy.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"xema",
|
|
@@ -12,16 +12,15 @@
|
|
|
12
12
|
"deeplink",
|
|
13
13
|
"intents"
|
|
14
14
|
],
|
|
15
|
+
"license": "Apache-2.0",
|
|
16
|
+
"author": "Neuralchowder Inc. <developer@xema.dev> (https://xema.dev)",
|
|
15
17
|
"homepage": "https://docs.xema.dev/biomes",
|
|
16
|
-
"bugs": {
|
|
17
|
-
"url": "https://github.com/xema-dev/xema-monorepo/issues"
|
|
18
|
-
},
|
|
19
18
|
"repository": {
|
|
20
19
|
"type": "git",
|
|
21
|
-
"url": "git+https://github.com/xema-dev/xema-
|
|
20
|
+
"url": "git+https://github.com/xema-dev/xema-kernel-sdk.git",
|
|
22
21
|
"directory": "packages/ui/xema-ui-kernel"
|
|
23
22
|
},
|
|
24
|
-
"
|
|
23
|
+
"bugs": "https://github.com/xema-dev/xema-kernel-sdk/issues",
|
|
25
24
|
"publishConfig": {
|
|
26
25
|
"access": "public",
|
|
27
26
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -54,6 +53,7 @@
|
|
|
54
53
|
"class-variance-authority": "^0.7.1",
|
|
55
54
|
"clsx": "^2.1.1",
|
|
56
55
|
"lucide-react": "^0.462.0",
|
|
56
|
+
"react-pdf": "^9.1.1",
|
|
57
57
|
"react-resizable-panels": "^4.9.0",
|
|
58
58
|
"tailwind-merge": "^2.5.4"
|
|
59
59
|
},
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"prettier": "3.6.2",
|
|
75
75
|
"react": "^18.3.1",
|
|
76
76
|
"typescript": "5.9.3",
|
|
77
|
-
"@xemahq/kernel-contracts": "^0.
|
|
77
|
+
"@xemahq/kernel-contracts": "^0.36.0"
|
|
78
78
|
},
|
|
79
79
|
"exports": {
|
|
80
80
|
".": {
|
|
@@ -101,6 +101,7 @@
|
|
|
101
101
|
"scripts": {
|
|
102
102
|
"clean": "rm -rf dist",
|
|
103
103
|
"build": "tsc -p tsconfig.json",
|
|
104
|
+
"test": "echo '[ui-kernel] *.{test,spec} files run under host-web vitest (this package carries no vitest dep by design — see tsconfig exclude). Write tests with vitest; the frontend-bridge sync fails on a node:test file.'",
|
|
104
105
|
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
|
|
105
106
|
"lint": "tsc -p tsconfig.lint.json --noEmit",
|
|
106
107
|
"typecheck": "tsc -p tsconfig.lint.json --noEmit"
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Test suite for defineWebBiome's define-time scope/access validation and the
|
|
2
|
+
// chrome default. Runs on Node's native test runner against the COMPILED dist
|
|
3
|
+
// (CommonJS), so the package needs no jest/vitest/ts-jest toolchain — the
|
|
4
|
+
// `test` script builds first, then runs this file with `node --test`.
|
|
5
|
+
//
|
|
6
|
+
// Covered:
|
|
7
|
+
// 1. A `public` page with access `'member'` (or omitted) throws at define time.
|
|
8
|
+
// 2. A `public` + access `'public'` page is accepted; its route carries no
|
|
9
|
+
// nav item and mounts with access `'public'`.
|
|
10
|
+
// 3. A non-public page declaring access `'public'` throws at define time.
|
|
11
|
+
// 4. `chrome` defaults to `'host'` when a page omits it; `'none'` passes through.
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const test = require('node:test');
|
|
16
|
+
const assert = require('node:assert/strict');
|
|
17
|
+
|
|
18
|
+
const { defineWebBiome } = require('../../../dist/lib/biome-host/define-web-biome.js');
|
|
19
|
+
|
|
20
|
+
const load = () => Promise.resolve({ default: () => null });
|
|
21
|
+
const bridge = /** @type {any} */ ({});
|
|
22
|
+
|
|
23
|
+
test('rejects a public page whose access is not public (member default)', () => {
|
|
24
|
+
assert.throws(
|
|
25
|
+
() =>
|
|
26
|
+
defineWebBiome({
|
|
27
|
+
id: 'test-biome',
|
|
28
|
+
displayName: 'Test',
|
|
29
|
+
pages: [{ slug: 'ext/:id', label: 'Ext', scope: 'public', load }],
|
|
30
|
+
}),
|
|
31
|
+
/a 'public' scope page MUST declare access: 'public'/,
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('rejects a public page with an explicit non-public access tier', () => {
|
|
36
|
+
assert.throws(
|
|
37
|
+
() =>
|
|
38
|
+
defineWebBiome({
|
|
39
|
+
id: 'test-biome',
|
|
40
|
+
displayName: 'Test',
|
|
41
|
+
pages: [
|
|
42
|
+
{ slug: 'ext/:id', label: 'Ext', scope: 'public', access: 'org-admin', load },
|
|
43
|
+
],
|
|
44
|
+
}),
|
|
45
|
+
/a 'public' scope page MUST declare access: 'public'/,
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('rejects a non-public page that declares access public', () => {
|
|
50
|
+
assert.throws(
|
|
51
|
+
() =>
|
|
52
|
+
defineWebBiome({
|
|
53
|
+
id: 'test-biome',
|
|
54
|
+
displayName: 'Test',
|
|
55
|
+
pages: [{ slug: 'system/x', label: 'X', access: 'public', load }],
|
|
56
|
+
}),
|
|
57
|
+
/access: 'public' is only valid on a 'public' scope page/,
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('accepts a public + access:public page and emits no nav item', () => {
|
|
62
|
+
const factory = defineWebBiome({
|
|
63
|
+
id: 'test-biome',
|
|
64
|
+
displayName: 'Test',
|
|
65
|
+
pages: [
|
|
66
|
+
{ slug: 'ext/:id', label: 'Ext', scope: 'public', access: 'public', load },
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
const biome = factory(bridge);
|
|
70
|
+
assert.equal(biome.routes.length, 1);
|
|
71
|
+
const route = biome.routes[0];
|
|
72
|
+
assert.equal(route.path, 'ext/:id');
|
|
73
|
+
assert.equal(route.access, 'public');
|
|
74
|
+
assert.equal(route.projectScoped, false);
|
|
75
|
+
// Public routes never enter the authenticated app rail.
|
|
76
|
+
assert.equal(biome.navItems.length, 0);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('chrome defaults to host and none passes through', () => {
|
|
80
|
+
const factory = defineWebBiome({
|
|
81
|
+
id: 'test-biome',
|
|
82
|
+
displayName: 'Test',
|
|
83
|
+
pages: [
|
|
84
|
+
{ slug: 'system/a', label: 'A', load },
|
|
85
|
+
{ slug: 'system/b', label: 'B', chrome: 'none', load },
|
|
86
|
+
],
|
|
87
|
+
});
|
|
88
|
+
const biome = factory(bridge);
|
|
89
|
+
const a = biome.routes.find((r) => r.path === 'system/a');
|
|
90
|
+
const b = biome.routes.find((r) => r.path === 'system/b');
|
|
91
|
+
assert.equal(a.chrome, 'host');
|
|
92
|
+
assert.equal(b.chrome, 'none');
|
|
93
|
+
});
|
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
* `<Suspense fallback={null}>` boundary via {@link lazyRoute}.
|
|
15
15
|
*
|
|
16
16
|
* Authors declare WHAT each surface is (`category`) and let the HOST own WHERE
|
|
17
|
-
* it renders in the global menu
|
|
18
|
-
* `
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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
21
|
*
|
|
22
22
|
* Host-framework-agnostic: pure React (`lazy`/`Suspense`/`createElement` via
|
|
23
23
|
* `lazyRoute`), no router and no Next.js.
|
|
@@ -44,8 +44,10 @@ import type {
|
|
|
44
44
|
FrontendBiome,
|
|
45
45
|
FrontendBiomeFactory,
|
|
46
46
|
NavItemContribution,
|
|
47
|
+
PageChrome,
|
|
47
48
|
RouteAccess,
|
|
48
49
|
RouteContribution,
|
|
50
|
+
RouteScope,
|
|
49
51
|
} from './frontend-biome';
|
|
50
52
|
import type { ComponentType } from 'react';
|
|
51
53
|
|
|
@@ -70,23 +72,31 @@ export interface WebBiomePage {
|
|
|
70
72
|
/**
|
|
71
73
|
* Host menu category — the biome's INTENT (`primary` | `build` | `operate` |
|
|
72
74
|
* `knowledge` | `admin` | `account`). The host maps it to a concrete rail
|
|
73
|
-
* group + order.
|
|
75
|
+
* group + order.
|
|
74
76
|
*/
|
|
75
77
|
readonly category?: string;
|
|
76
78
|
/**
|
|
77
79
|
* Route scope. `'org'` (default) mounts at the org root; `'project'` mounts
|
|
78
|
-
* under
|
|
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.
|
|
79
91
|
*/
|
|
80
|
-
readonly scope?: 'org' | 'project';
|
|
81
|
-
/** Access tier required to render the route. Defaults to `'member'`. */
|
|
82
92
|
readonly access?: RouteAccess;
|
|
83
|
-
/** Dynamic import of the page module (`() => import('./pages/X')`). */
|
|
84
|
-
readonly load: () => Promise<{ default: ComponentType<unknown> }>;
|
|
85
93
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
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.
|
|
88
96
|
*/
|
|
89
|
-
readonly
|
|
97
|
+
readonly chrome?: PageChrome;
|
|
98
|
+
/** Dynamic import of the page module (`() => import('./pages/X')`). */
|
|
99
|
+
readonly load: () => Promise<{ default: ComponentType<unknown> }>;
|
|
90
100
|
/**
|
|
91
101
|
* When true the route is registered but NO nav item is emitted — for hidden
|
|
92
102
|
* surfaces such as a `:param` detail route reached only by deeplink.
|
|
@@ -113,6 +123,47 @@ export interface DefineWebBiomeOptions {
|
|
|
113
123
|
readonly outputRenderers?: FrontendBiome['outputRenderers'];
|
|
114
124
|
}
|
|
115
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
|
+
|
|
116
167
|
/**
|
|
117
168
|
* Build the `FrontendBiomeFactory` a web biome default-exports. See the
|
|
118
169
|
* module doc-comment for the rationale and an example.
|
|
@@ -120,6 +171,13 @@ export interface DefineWebBiomeOptions {
|
|
|
120
171
|
export function defineWebBiome(
|
|
121
172
|
options: DefineWebBiomeOptions,
|
|
122
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
|
+
|
|
123
181
|
return () => {
|
|
124
182
|
const navItems: NavItemContribution[] = [];
|
|
125
183
|
const routes: RouteContribution[] = [];
|
|
@@ -129,17 +187,20 @@ export function defineWebBiome(
|
|
|
129
187
|
path: page.slug,
|
|
130
188
|
projectScoped: page.scope === 'project',
|
|
131
189
|
element: lazyRoute(page.load),
|
|
190
|
+
chrome: page.chrome ?? DEFAULT_PAGE_CHROME,
|
|
132
191
|
...(page.access ? { access: page.access } : {}),
|
|
133
192
|
});
|
|
134
193
|
|
|
135
|
-
|
|
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') {
|
|
136
198
|
navItems.push({
|
|
137
199
|
id: page.id ?? page.slug,
|
|
138
200
|
label: page.label,
|
|
139
201
|
route: page.slug,
|
|
140
202
|
...(page.icon ? { icon: page.icon } : {}),
|
|
141
203
|
...(page.category !== undefined ? { category: page.category } : {}),
|
|
142
|
-
...(page.weight !== undefined ? { weight: page.weight } : {}),
|
|
143
204
|
});
|
|
144
205
|
}
|
|
145
206
|
}
|
|
@@ -33,22 +33,10 @@ export interface NavItemContribution {
|
|
|
33
33
|
* information-architecture: it maps a category to a concrete rail group,
|
|
34
34
|
* heading, and order. Authors declare WHAT a surface is, not WHERE in the
|
|
35
35
|
* global menu it renders — so the platform can re-organise the menu without
|
|
36
|
-
* editing every biome.
|
|
37
|
-
*
|
|
38
|
-
* last, to the legacy `section`.
|
|
36
|
+
* editing every biome. When omitted, the host falls back to its own
|
|
37
|
+
* taxonomy (keyed on the nav-item id).
|
|
39
38
|
*/
|
|
40
39
|
readonly category?: string;
|
|
41
|
-
/**
|
|
42
|
-
* @deprecated Legacy direct section-heading placement. The host now owns
|
|
43
|
-
* grouping via `category` + its taxonomy; retained only until every biome
|
|
44
|
-
* declares a `category`. Do NOT use in new biomes.
|
|
45
|
-
*/
|
|
46
|
-
readonly section?: string;
|
|
47
|
-
/**
|
|
48
|
-
* @deprecated Legacy intra-section sort weight. Ordering is host-owned per
|
|
49
|
-
* category now. Retained only for un-migrated biomes.
|
|
50
|
-
*/
|
|
51
|
-
readonly weight?: number;
|
|
52
40
|
}
|
|
53
41
|
|
|
54
42
|
/**
|
|
@@ -56,8 +44,42 @@ export interface NavItemContribution {
|
|
|
56
44
|
* declaratively — nav-hide is NOT a security boundary, a deeplink bypasses it.
|
|
57
45
|
* Precedence (least → most privileged): `member` < `org-admin` <
|
|
58
46
|
* `platform-admin`. Defaults to `member` when a route omits the field.
|
|
47
|
+
*
|
|
48
|
+
* `public` is NOT a privilege tier — it is the *no-auth* marker: a `public`
|
|
49
|
+
* route requires no Keycloak session at all and mounts OUTSIDE the host's
|
|
50
|
+
* authenticated route group. It is value-locked to `RouteScope` `'public'`
|
|
51
|
+
* (`defineWebBiome` rejects any other pairing at define time), so a route is
|
|
52
|
+
* public in BOTH senses or in neither. Closed set — a new member is a
|
|
53
|
+
* one-line change here and one `case` in the host resolver's exhaustive
|
|
54
|
+
* `hasRouteAccess` switch.
|
|
55
|
+
*/
|
|
56
|
+
export type RouteAccess = 'member' | 'org-admin' | 'platform-admin' | 'public';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Where a route mounts in the host's URL/auth topology. Closed set — adding a
|
|
60
|
+
* member is a one-line change here (and the single mapping in `defineWebBiome`
|
|
61
|
+
* plus one resolver partition in the host).
|
|
62
|
+
*
|
|
63
|
+
* - `org` — mounts at the org root, inside the authenticated group.
|
|
64
|
+
* - `project` — mounts under the project-scoped route prefix, inside the
|
|
65
|
+
* authenticated group.
|
|
66
|
+
* - `public` — mounts OUTSIDE the authenticated group; no Keycloak session,
|
|
67
|
+
* no login redirect, no org switcher. Locked to `RouteAccess` `'public'`.
|
|
68
|
+
*/
|
|
69
|
+
export type RouteScope = 'org' | 'project' | 'public';
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Whether a page renders wrapped in the host application shell (app rail,
|
|
73
|
+
* topbar, org switcher) or standalone. Closed set — a new member is a one-line
|
|
74
|
+
* change here. Defaults to `'host'` when a page omits it.
|
|
75
|
+
*
|
|
76
|
+
* - `host` — the default; the host renders its chrome around the page.
|
|
77
|
+
* - `none` — the host renders the page with NO app rail, topbar, or org
|
|
78
|
+
* switcher (a full-bleed surface). `public` routes are structurally
|
|
79
|
+
* chromeless (their route group ships no shell); `none` also lets an
|
|
80
|
+
* authenticated route opt out of the shell.
|
|
59
81
|
*/
|
|
60
|
-
export type
|
|
82
|
+
export type PageChrome = 'host' | 'none';
|
|
61
83
|
|
|
62
84
|
export interface RouteContribution {
|
|
63
85
|
/** React-router path. May include params. */
|
|
@@ -75,9 +97,16 @@ export interface RouteContribution {
|
|
|
75
97
|
readonly projectScoped: boolean;
|
|
76
98
|
/**
|
|
77
99
|
* Access tier required to render this route. The host resolver enforces it
|
|
78
|
-
* declaratively. Defaults to `'member'` when omitted.
|
|
100
|
+
* declaratively. Defaults to `'member'` when omitted. `'public'` marks a
|
|
101
|
+
* no-auth route the host mounts outside its authenticated group.
|
|
79
102
|
*/
|
|
80
103
|
readonly access?: RouteAccess;
|
|
104
|
+
/**
|
|
105
|
+
* Whether the host wraps this route in its application shell. Defaults to
|
|
106
|
+
* `'host'` when omitted; `'none'` renders the page with no app rail,
|
|
107
|
+
* topbar, or org switcher. See {@link PageChrome}.
|
|
108
|
+
*/
|
|
109
|
+
readonly chrome?: PageChrome;
|
|
81
110
|
}
|
|
82
111
|
|
|
83
112
|
/**
|
|
@@ -194,7 +223,7 @@ export interface OutputRendererContribution {
|
|
|
194
223
|
*
|
|
195
224
|
* This factory shape keeps biome code free of any direct dependency
|
|
196
225
|
* on host-shell primitives (router, auth client, toast library), which
|
|
197
|
-
* is what allows the same biome package to load into
|
|
198
|
-
* today and a
|
|
226
|
+
* is what allows the same biome package to load into one host runtime
|
|
227
|
+
* today and a different host runtime tomorrow.
|
|
199
228
|
*/
|
|
200
229
|
export type FrontendBiomeFactory = (bridge: HostBridge) => FrontendBiome;
|
|
@@ -5,6 +5,7 @@ import { createContext, useContext, type ReactNode } from 'react';
|
|
|
5
5
|
import type { CapabilityPort } from '../capabilities';
|
|
6
6
|
import type { SystemBus } from '../system-bus';
|
|
7
7
|
|
|
8
|
+
import type { I18nSource } from './i18n-port';
|
|
8
9
|
import type { RealtimeSource } from './realtime-port';
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -235,6 +236,18 @@ export interface HostBridge {
|
|
|
235
236
|
* actionable error if a biome uses them before the host wires `bridge.realtime`.
|
|
236
237
|
*/
|
|
237
238
|
readonly realtime?: RealtimeSource;
|
|
239
|
+
/**
|
|
240
|
+
* Localization port. The HOST injects a concrete {@link I18nSource} backed by
|
|
241
|
+
* its locale provider (authoritative locale from org/app/user settings +
|
|
242
|
+
* catalog loading); biomes reach it through the kernel `useT` / `useLocale`
|
|
243
|
+
* hooks so biome code never imports an i18n library directly — same decoupling
|
|
244
|
+
* as `navigation` / `realtime`.
|
|
245
|
+
*
|
|
246
|
+
* OPTIONAL: a host without localization omits it; the kernel `useT`/`useLocale`
|
|
247
|
+
* hooks fail fast with an actionable error if a biome uses them before the host
|
|
248
|
+
* wires `bridge.i18n`.
|
|
249
|
+
*/
|
|
250
|
+
readonly i18n?: I18nSource;
|
|
238
251
|
/**
|
|
239
252
|
* Error-decoding surface — the canonical user-facing error decoder. Defaults
|
|
240
253
|
* (in the kernel default bridge wiring) to {@link getUserFacingErrorMessage};
|