@burdenoff/microfe-bigconsole 2026.730.3 → 2026.730.4
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/bigconsole/components/embed/EmbedErrorBoundary.js +25 -0
- package/dist/bigconsole/components/embed/EmbedErrorBoundary.js.map +1 -0
- package/dist/bigconsole/components/embed/EmbedWidgetRenderer.js +36 -0
- package/dist/bigconsole/components/embed/EmbedWidgetRenderer.js.map +1 -0
- package/dist/bigconsole/components/embed/ReadOnlyDashboardRenderer.js +62 -0
- package/dist/bigconsole/components/embed/ReadOnlyDashboardRenderer.js.map +1 -0
- package/dist/bigconsole/components/embed/index.js +7 -0
- package/dist/bigconsole/components/embed/renderer-manifest.js +77 -0
- package/dist/bigconsole/components/embed/renderer-manifest.js.map +1 -0
- package/dist/bigconsole/components/embed/types.js +6 -0
- package/dist/bigconsole/components/embed/types.js.map +1 -0
- package/dist/bigconsole/components/embed/validate.js +29 -0
- package/dist/bigconsole/components/embed/validate.js.map +1 -0
- package/dist/bigconsole/components/embed/widgets/KpiComparisonEmbed.js +34 -0
- package/dist/bigconsole/components/embed/widgets/KpiComparisonEmbed.js.map +1 -0
- package/dist/bigconsole/components/embed/widgets/ListEmbed.js +36 -0
- package/dist/bigconsole/components/embed/widgets/ListEmbed.js.map +1 -0
- package/dist/bigconsole/components/embed/widgets/MetricCardEmbed.js +20 -0
- package/dist/bigconsole/components/embed/widgets/MetricCardEmbed.js.map +1 -0
- package/dist/bigconsole/components/embed/widgets/TableEmbed.js +40 -0
- package/dist/bigconsole/components/embed/widgets/TableEmbed.js.map +1 -0
- package/dist/bigconsole/components/embed/widgets/TextEmbed.js +20 -0
- package/dist/bigconsole/components/embed/widgets/TextEmbed.js.map +1 -0
- package/dist/bigconsole/components/embed/widgets/shared.js +29 -0
- package/dist/bigconsole/components/embed/widgets/shared.js.map +1 -0
- package/package.json +5 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Component as e } from "react";
|
|
2
|
+
import { jsxs as t } from "react/jsx-runtime";
|
|
3
|
+
//#region src/bigconsole/components/embed/EmbedErrorBoundary.tsx
|
|
4
|
+
var n = class extends e {
|
|
5
|
+
state = { hasError: !1 };
|
|
6
|
+
static getDerivedStateFromError() {
|
|
7
|
+
return { hasError: !0 };
|
|
8
|
+
}
|
|
9
|
+
componentDidCatch(e, t) {}
|
|
10
|
+
render() {
|
|
11
|
+
return this.state.hasError ? /* @__PURE__ */ t("div", {
|
|
12
|
+
role: "alert",
|
|
13
|
+
className: "flex h-full w-full items-center justify-center bg-surface p-4 text-center text-sm text-secondary",
|
|
14
|
+
children: [
|
|
15
|
+
"This ",
|
|
16
|
+
this.props.label ?? "content",
|
|
17
|
+
" could not be displayed."
|
|
18
|
+
]
|
|
19
|
+
}) : this.props.children;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
//#endregion
|
|
23
|
+
export { n as EmbedErrorBoundary };
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=EmbedErrorBoundary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmbedErrorBoundary.js","names":[],"sources":["../../../../src/bigconsole/components/embed/EmbedErrorBoundary.tsx"],"sourcesContent":["/**\n * Customer-facing error boundary for embedded widgets/dashboards (BOFF-2986).\n *\n * Renders a neutral, safe message and NEVER surfaces internal details (stack\n * traces, ids, endpoints, config). One failing widget must not tear down the\n * whole dashboard, so this wraps each widget individually as well as the page.\n */\n\nimport { Component, type ErrorInfo, type ReactNode } from 'react';\n\ninterface Props {\n readonly children: ReactNode;\n /** Short, non-sensitive label for what failed (e.g. \"widget\"). */\n readonly label?: string;\n}\n\ninterface State {\n readonly hasError: boolean;\n}\n\nexport class EmbedErrorBoundary extends Component<Props, State> {\n state: State = { hasError: false };\n\n static getDerivedStateFromError(): State {\n return { hasError: true };\n }\n\n componentDidCatch(_error: Error, _info: ErrorInfo): void {\n // Intentionally no external reporting from the anonymous embed context — the\n // safe render below is the only user-visible outcome.\n }\n\n render(): ReactNode {\n if (this.state.hasError) {\n return (\n <div\n role=\"alert\"\n className=\"flex h-full w-full items-center justify-center bg-surface p-4 text-center text-sm text-secondary\"\n >\n This {this.props.label ?? 'content'} could not be displayed.\n </div>\n );\n }\n return this.props.children;\n }\n}\n"],"mappings":";;;AAoBA,IAAa,IAAb,cAAwC,EAAwB;CAC9D,QAAe,EAAE,UAAU,IAAO;CAElC,OAAO,2BAAkC;AACvC,SAAO,EAAE,UAAU,IAAM;;CAG3B,kBAAkB,GAAe,GAAwB;CAKzD,SAAoB;AAWlB,SAVI,KAAK,MAAM,WAEX,kBAAC,OAAD;GACE,MAAK;GACL,WAAU;aAFZ;IAGC;IACO,KAAK,MAAM,SAAS;IAAU;IAChC;OAGH,KAAK,MAAM"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { EMBED_WIDGET_COVERAGE as e } from "./renderer-manifest.js";
|
|
2
|
+
import { EmbedErrorBoundary as t } from "./EmbedErrorBoundary.js";
|
|
3
|
+
import { MetricCardEmbed as n } from "./widgets/MetricCardEmbed.js";
|
|
4
|
+
import { KpiComparisonEmbed as r } from "./widgets/KpiComparisonEmbed.js";
|
|
5
|
+
import { TableEmbed as i } from "./widgets/TableEmbed.js";
|
|
6
|
+
import { ListEmbed as a } from "./widgets/ListEmbed.js";
|
|
7
|
+
import { TextEmbed as o } from "./widgets/TextEmbed.js";
|
|
8
|
+
import { jsx as s } from "react/jsx-runtime";
|
|
9
|
+
//#region src/bigconsole/components/embed/EmbedWidgetRenderer.tsx
|
|
10
|
+
var c = {
|
|
11
|
+
metric_card: n,
|
|
12
|
+
kpi_card_comparison: r,
|
|
13
|
+
table: i,
|
|
14
|
+
list: a,
|
|
15
|
+
text: o
|
|
16
|
+
};
|
|
17
|
+
function l() {
|
|
18
|
+
return Object.keys(c);
|
|
19
|
+
}
|
|
20
|
+
function u({ title: e }) {
|
|
21
|
+
return /* @__PURE__ */ s("div", {
|
|
22
|
+
className: "flex h-full w-full items-center justify-center bg-surface p-4 text-center text-sm text-secondary",
|
|
23
|
+
children: e ? `“${e}” is not available in this embed.` : "This widget is not available in this embed."
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function d(n) {
|
|
27
|
+
let { widget: r } = n, i = e[r.type]?.support === "implemented" ? c[r.type] : void 0;
|
|
28
|
+
return i ? /* @__PURE__ */ s(t, {
|
|
29
|
+
label: "widget",
|
|
30
|
+
children: /* @__PURE__ */ s(i, { ...n })
|
|
31
|
+
}) : /* @__PURE__ */ s(u, { title: r.title });
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { d as EmbedWidgetRenderer, l as getImplementedRendererTypes };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=EmbedWidgetRenderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmbedWidgetRenderer.js","names":[],"sources":["../../../../src/bigconsole/components/embed/EmbedWidgetRenderer.tsx"],"sourcesContent":["/**\n * Strict embed widget dispatcher (BOFF-2986).\n *\n * Maps a sanitized widget DTO to its embed renderer via an EXACT registry.\n * There is NO silent fallback (unlike the authenticated WidgetRendererFactory):\n * a type that is not `implemented` in the coverage manifest renders a neutral\n * \"not available\" placeholder, never a guessed or partial widget. Each widget is\n * wrapped in its own error boundary so one failure can't take down the page.\n */\n\nimport type { ComponentType } from 'react';\nimport type { WidgetType } from '../../types';\nimport type { EmbedWidgetRenderProps } from './types';\nimport { EMBED_WIDGET_COVERAGE } from './renderer-manifest';\nimport { EmbedErrorBoundary } from './EmbedErrorBoundary';\nimport { MetricCardEmbed } from './widgets/MetricCardEmbed';\nimport { KpiComparisonEmbed } from './widgets/KpiComparisonEmbed';\nimport { TableEmbed } from './widgets/TableEmbed';\nimport { ListEmbed } from './widgets/ListEmbed';\nimport { TextEmbed } from './widgets/TextEmbed';\n\n/**\n * Registry of implemented renderers. Every key here MUST be marked\n * `implemented` in EMBED_WIDGET_COVERAGE, and vice-versa — the completeness test\n * asserts the two stay in lockstep, so a renderer can never be silently missing\n * for a type the manifest claims to support.\n */\nconst IMPLEMENTED_RENDERERS: Partial<Record<WidgetType, ComponentType<EmbedWidgetRenderProps>>> = {\n metric_card: MetricCardEmbed,\n kpi_card_comparison: KpiComparisonEmbed,\n table: TableEmbed,\n list: ListEmbed,\n text: TextEmbed,\n};\n\nexport function getImplementedRendererTypes(): WidgetType[] {\n return Object.keys(IMPLEMENTED_RENDERERS) as WidgetType[];\n}\n\nfunction UnavailableWidget({ title }: { title?: string }) {\n return (\n <div className=\"flex h-full w-full items-center justify-center bg-surface p-4 text-center text-sm text-secondary\">\n {title ? `“${title}” is not available in this embed.` : 'This widget is not available in this embed.'}\n </div>\n );\n}\n\nexport function EmbedWidgetRenderer(props: EmbedWidgetRenderProps) {\n const { widget } = props;\n const coverage = EMBED_WIDGET_COVERAGE[widget.type];\n const Renderer = coverage?.support === 'implemented' ? IMPLEMENTED_RENDERERS[widget.type] : undefined;\n\n if (!Renderer) {\n return <UnavailableWidget title={widget.title} />;\n }\n\n return (\n <EmbedErrorBoundary label=\"widget\">\n <Renderer {...props} />\n </EmbedErrorBoundary>\n );\n}\n"],"mappings":";;;;;;;;;AA2BA,IAAM,IAA4F;CAChG,aAAa;CACb,qBAAqB;CACrB,OAAO;CACP,MAAM;CACN,MAAM;CACP;AAED,SAAgB,IAA4C;AAC1D,QAAO,OAAO,KAAK,EAAsB;;AAG3C,SAAS,EAAkB,EAAE,YAA6B;AACxD,QACE,kBAAC,OAAD;EAAK,WAAU;YACZ,IAAQ,IAAI,EAAM,qCAAqC;EACpD,CAAA;;AAIV,SAAgB,EAAoB,GAA+B;CACjE,IAAM,EAAE,cAAW,GAEb,IADW,EAAsB,EAAO,OACnB,YAAY,gBAAgB,EAAsB,EAAO,QAAQ,KAAA;AAM5F,QAJK,IAKH,kBAAC,GAAD;EAAoB,OAAM;YACxB,kBAAC,GAAD,EAAU,GAAI,GAAS,CAAA;EACJ,CAAA,GANd,kBAAC,GAAD,EAAmB,OAAO,EAAO,OAAS,CAAA"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { EmbedErrorBoundary as e } from "./EmbedErrorBoundary.js";
|
|
2
|
+
import { EmbedWidgetRenderer as t } from "./EmbedWidgetRenderer.js";
|
|
3
|
+
import { useState as n } from "react";
|
|
4
|
+
import { jsx as r, jsxs as i } from "react/jsx-runtime";
|
|
5
|
+
//#region src/bigconsole/components/embed/ReadOnlyDashboardRenderer.tsx
|
|
6
|
+
var a = 12;
|
|
7
|
+
function o({ page: e, capabilities: n }) {
|
|
8
|
+
return e.widgets.length === 0 ? /* @__PURE__ */ r("div", {
|
|
9
|
+
className: "flex h-40 items-center justify-center text-sm text-secondary",
|
|
10
|
+
children: "This page has no widgets."
|
|
11
|
+
}) : /* @__PURE__ */ r("div", {
|
|
12
|
+
className: "grid w-full gap-3 p-3",
|
|
13
|
+
style: {
|
|
14
|
+
gridTemplateColumns: `repeat(${a}, minmax(0, 1fr))`,
|
|
15
|
+
gridAutoRows: "minmax(80px, auto)"
|
|
16
|
+
},
|
|
17
|
+
children: e.widgets.map((e) => /* @__PURE__ */ r("div", {
|
|
18
|
+
className: "overflow-hidden rounded border border-border-default",
|
|
19
|
+
style: {
|
|
20
|
+
gridColumn: `${Math.min(Math.max(e.layout.x, 0), a - 1) + 1} / span ${Math.min(Math.max(e.layout.w, 1), a)}`,
|
|
21
|
+
gridRow: `${Math.max(e.layout.y, 0) + 1} / span ${Math.max(e.layout.h, 1)}`
|
|
22
|
+
},
|
|
23
|
+
children: /* @__PURE__ */ r(t, {
|
|
24
|
+
widget: e,
|
|
25
|
+
capabilities: n
|
|
26
|
+
})
|
|
27
|
+
}, e.id))
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function s({ model: t }) {
|
|
31
|
+
let [a, s] = n(0), c = t.pages, l = c[Math.min(a, c.length - 1)];
|
|
32
|
+
return l ? /* @__PURE__ */ r(e, {
|
|
33
|
+
label: "dashboard",
|
|
34
|
+
children: /* @__PURE__ */ i("div", {
|
|
35
|
+
className: "flex h-full w-full flex-col bg-surface",
|
|
36
|
+
children: [c.length > 1 && /* @__PURE__ */ r("div", {
|
|
37
|
+
role: "tablist",
|
|
38
|
+
className: "flex gap-1 border-b border-border-default px-2",
|
|
39
|
+
children: c.map((e, t) => /* @__PURE__ */ r("button", {
|
|
40
|
+
role: "tab",
|
|
41
|
+
"aria-selected": t === a,
|
|
42
|
+
onClick: () => s(t),
|
|
43
|
+
className: t === a ? "border-b-2 border-primary px-3 py-2 text-sm text-primary" : "px-3 py-2 text-sm text-secondary",
|
|
44
|
+
children: e.title ?? `Page ${t + 1}`
|
|
45
|
+
}, e.id))
|
|
46
|
+
}), /* @__PURE__ */ r("div", {
|
|
47
|
+
className: "min-h-0 flex-1 overflow-auto",
|
|
48
|
+
children: /* @__PURE__ */ r(o, {
|
|
49
|
+
page: l,
|
|
50
|
+
capabilities: t.capabilities
|
|
51
|
+
})
|
|
52
|
+
})]
|
|
53
|
+
})
|
|
54
|
+
}) : /* @__PURE__ */ r("div", {
|
|
55
|
+
className: "flex h-full items-center justify-center bg-surface text-sm text-secondary",
|
|
56
|
+
children: "This dashboard has no pages."
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { s as ReadOnlyDashboardRenderer };
|
|
61
|
+
|
|
62
|
+
//# sourceMappingURL=ReadOnlyDashboardRenderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReadOnlyDashboardRenderer.js","names":[],"sources":["../../../../src/bigconsole/components/embed/ReadOnlyDashboardRenderer.tsx"],"sourcesContent":["/**\n * Read-only embedded dashboard renderer (BOFF-2986).\n *\n * Renders the sanitized render model's pages + widgets on a simple responsive\n * grid. It is STRICTLY view-only: no builder toolbar, edit toggle, sharing,\n * save, palette, inspector or comments — those authenticated affordances do not\n * exist in this component tree at all.\n */\n\nimport { useState } from 'react';\nimport type { EmbedCapabilities, EmbedPageModel, EmbedRenderModel } from './types';\nimport { EmbedWidgetRenderer } from './EmbedWidgetRenderer';\nimport { EmbedErrorBoundary } from './EmbedErrorBoundary';\n\nconst GRID_COLUMNS = 12;\n\ninterface Props {\n readonly model: EmbedRenderModel;\n}\n\nfunction PageGrid({ page, capabilities }: { page: EmbedPageModel; capabilities: EmbedCapabilities }) {\n if (page.widgets.length === 0) {\n return (\n <div className=\"flex h-40 items-center justify-center text-sm text-secondary\">This page has no widgets.</div>\n );\n }\n return (\n <div\n className=\"grid w-full gap-3 p-3\"\n style={{ gridTemplateColumns: `repeat(${GRID_COLUMNS}, minmax(0, 1fr))`, gridAutoRows: 'minmax(80px, auto)' }}\n >\n {page.widgets.map((widget) => (\n <div\n key={widget.id}\n className=\"overflow-hidden rounded border border-border-default\"\n style={{\n // Clamp both ends: negative/oversized coordinates from malformed\n // model data must never produce an invalid CSS grid line.\n gridColumn: `${Math.min(Math.max(widget.layout.x, 0), GRID_COLUMNS - 1) + 1} / span ${Math.min(\n Math.max(widget.layout.w, 1),\n GRID_COLUMNS\n )}`,\n gridRow: `${Math.max(widget.layout.y, 0) + 1} / span ${Math.max(widget.layout.h, 1)}`,\n }}\n >\n <EmbedWidgetRenderer widget={widget} capabilities={capabilities} />\n </div>\n ))}\n </div>\n );\n}\n\nexport function ReadOnlyDashboardRenderer({ model }: Props) {\n const [activePageIndex, setActivePageIndex] = useState(0);\n const pages = model.pages;\n const activePage = pages[Math.min(activePageIndex, pages.length - 1)];\n\n if (!activePage) {\n return (\n <div className=\"flex h-full items-center justify-center bg-surface text-sm text-secondary\">\n This dashboard has no pages.\n </div>\n );\n }\n\n return (\n <EmbedErrorBoundary label=\"dashboard\">\n <div className=\"flex h-full w-full flex-col bg-surface\">\n {pages.length > 1 && (\n <div role=\"tablist\" className=\"flex gap-1 border-b border-border-default px-2\">\n {pages.map((page, i) => (\n <button\n key={page.id}\n role=\"tab\"\n aria-selected={i === activePageIndex}\n onClick={() => setActivePageIndex(i)}\n className={\n i === activePageIndex\n ? 'border-b-2 border-primary px-3 py-2 text-sm text-primary'\n : 'px-3 py-2 text-sm text-secondary'\n }\n >\n {page.title ?? `Page ${i + 1}`}\n </button>\n ))}\n </div>\n )}\n <div className=\"min-h-0 flex-1 overflow-auto\">\n <PageGrid page={activePage} capabilities={model.capabilities} />\n </div>\n </div>\n </EmbedErrorBoundary>\n );\n}\n"],"mappings":";;;;;AAcA,IAAM,IAAe;AAMrB,SAAS,EAAS,EAAE,SAAM,mBAA2E;AAMnG,QALI,EAAK,QAAQ,WAAW,IAExB,kBAAC,OAAD;EAAK,WAAU;YAA+D;EAA+B,CAAA,GAI/G,kBAAC,OAAD;EACE,WAAU;EACV,OAAO;GAAE,qBAAqB,UAAU,EAAa;GAAoB,cAAc;GAAsB;YAE5G,EAAK,QAAQ,KAAK,MACjB,kBAAC,OAAD;GAEE,WAAU;GACV,OAAO;IAGL,YAAY,GAAG,KAAK,IAAI,KAAK,IAAI,EAAO,OAAO,GAAG,EAAE,EAAE,IAAe,EAAE,GAAG,EAAE,UAAU,KAAK,IACzF,KAAK,IAAI,EAAO,OAAO,GAAG,EAAE,EAC5B,EACD;IACD,SAAS,GAAG,KAAK,IAAI,EAAO,OAAO,GAAG,EAAE,GAAG,EAAE,UAAU,KAAK,IAAI,EAAO,OAAO,GAAG,EAAE;IACpF;aAED,kBAAC,GAAD;IAA6B;IAAsB;IAAgB,CAAA;GAC/D,EAbC,EAAO,GAaR,CACN;EACE,CAAA;;AAIV,SAAgB,EAA0B,EAAE,YAAgB;CAC1D,IAAM,CAAC,GAAiB,KAAsB,EAAS,EAAE,EACnD,IAAQ,EAAM,OACd,IAAa,EAAM,KAAK,IAAI,GAAiB,EAAM,SAAS,EAAE;AAUpE,QARK,IASH,kBAAC,GAAD;EAAoB,OAAM;YACxB,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,EAAM,SAAS,KACd,kBAAC,OAAD;IAAK,MAAK;IAAU,WAAU;cAC3B,EAAM,KAAK,GAAM,MAChB,kBAAC,UAAD;KAEE,MAAK;KACL,iBAAe,MAAM;KACrB,eAAe,EAAmB,EAAE;KACpC,WACE,MAAM,IACF,6DACA;eAGL,EAAK,SAAS,QAAQ,IAAI;KACpB,EAXF,EAAK,GAWH,CACT;IACE,CAAA,EAER,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KAAU,MAAM;KAAY,cAAc,EAAM;KAAgB,CAAA;IAC5D,CAAA,CACF;;EACa,CAAA,GAhCnB,kBAAC,OAAD;EAAK,WAAU;YAA4E;EAErF,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { EMBED_RENDER_MODEL_VERSION as e } from "./types.js";
|
|
2
|
+
import { EMBED_RENDERER_MANIFEST as t, EMBED_WIDGET_COVERAGE as n, isWidgetTypeEmbeddable as r } from "./renderer-manifest.js";
|
|
3
|
+
import { EmbedErrorBoundary as i } from "./EmbedErrorBoundary.js";
|
|
4
|
+
import { EmbedWidgetRenderer as a, getImplementedRendererTypes as o } from "./EmbedWidgetRenderer.js";
|
|
5
|
+
import { ReadOnlyDashboardRenderer as s } from "./ReadOnlyDashboardRenderer.js";
|
|
6
|
+
import { validateEmbedRenderModel as c } from "./validate.js";
|
|
7
|
+
export { t as EMBED_RENDERER_MANIFEST, e as EMBED_RENDER_MODEL_VERSION, n as EMBED_WIDGET_COVERAGE, i as EmbedErrorBoundary, a as EmbedWidgetRenderer, s as ReadOnlyDashboardRenderer, o as getImplementedRendererTypes, r as isWidgetTypeEmbeddable, c as validateEmbedRenderModel };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import "./types.js";
|
|
2
|
+
//#region src/bigconsole/components/embed/renderer-manifest.ts
|
|
3
|
+
var e = {
|
|
4
|
+
metric_card: { support: "implemented" },
|
|
5
|
+
kpi_card_comparison: { support: "implemented" },
|
|
6
|
+
table: { support: "implemented" },
|
|
7
|
+
list: { support: "implemented" },
|
|
8
|
+
text: { support: "implemented" },
|
|
9
|
+
chart: {
|
|
10
|
+
support: "blocked",
|
|
11
|
+
reason: "chart renderer + series schema pending"
|
|
12
|
+
},
|
|
13
|
+
funnel_chart: {
|
|
14
|
+
support: "blocked",
|
|
15
|
+
reason: "funnel renderer pending"
|
|
16
|
+
},
|
|
17
|
+
pivot_table: {
|
|
18
|
+
support: "blocked",
|
|
19
|
+
reason: "pivot projection schema pending"
|
|
20
|
+
},
|
|
21
|
+
gauge: {
|
|
22
|
+
support: "blocked",
|
|
23
|
+
reason: "gauge renderer pending"
|
|
24
|
+
},
|
|
25
|
+
progress: {
|
|
26
|
+
support: "blocked",
|
|
27
|
+
reason: "progress renderer pending"
|
|
28
|
+
},
|
|
29
|
+
form: {
|
|
30
|
+
support: "blocked",
|
|
31
|
+
reason: "requires side-effect action broker (post read-only MVP)"
|
|
32
|
+
},
|
|
33
|
+
iframe: {
|
|
34
|
+
support: "blocked",
|
|
35
|
+
reason: "requires frozen exact-origin sandbox declaration"
|
|
36
|
+
},
|
|
37
|
+
map: {
|
|
38
|
+
support: "blocked",
|
|
39
|
+
reason: "requires frozen map-provider origins"
|
|
40
|
+
},
|
|
41
|
+
heatmap: {
|
|
42
|
+
support: "blocked",
|
|
43
|
+
reason: "heatmap renderer pending"
|
|
44
|
+
},
|
|
45
|
+
calendar: {
|
|
46
|
+
support: "blocked",
|
|
47
|
+
reason: "calendar renderer pending"
|
|
48
|
+
},
|
|
49
|
+
kanban: {
|
|
50
|
+
support: "blocked",
|
|
51
|
+
reason: "kanban renderer pending"
|
|
52
|
+
},
|
|
53
|
+
timeline: {
|
|
54
|
+
support: "blocked",
|
|
55
|
+
reason: "timeline renderer pending"
|
|
56
|
+
},
|
|
57
|
+
retention: {
|
|
58
|
+
support: "blocked",
|
|
59
|
+
reason: "retention/cohort renderer pending"
|
|
60
|
+
},
|
|
61
|
+
custom: {
|
|
62
|
+
support: "blocked",
|
|
63
|
+
reason: "requires nested opaque-origin sandbox + signed manifest"
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
function t(t) {
|
|
67
|
+
return e[t]?.support === "implemented";
|
|
68
|
+
}
|
|
69
|
+
var n = {
|
|
70
|
+
renderModelVersion: 1,
|
|
71
|
+
implemented: Object.entries(e).filter(([, e]) => e.support === "implemented").map(([e]) => e).sort(),
|
|
72
|
+
blocked: Object.entries(e).filter(([, e]) => e.support === "blocked").map(([e]) => e).sort()
|
|
73
|
+
};
|
|
74
|
+
//#endregion
|
|
75
|
+
export { n as EMBED_RENDERER_MANIFEST, e as EMBED_WIDGET_COVERAGE, t as isWidgetTypeEmbeddable };
|
|
76
|
+
|
|
77
|
+
//# sourceMappingURL=renderer-manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renderer-manifest.js","names":[],"sources":["../../../../src/bigconsole/components/embed/renderer-manifest.ts"],"sourcesContent":["/**\n * Embed widget coverage manifest (BOFF-2986).\n *\n * A COMPLETE, explicit decision for every persisted widget type. There is no\n * silent fallback: a type is either `implemented` (has a sanitizer + embed\n * renderer + test) or `blocked` (publication containing it is rejected, and the\n * runtime renders a safe placeholder — never a wrong or partial widget).\n *\n * The completeness test (`renderer-manifest.test.ts`) enumerates the\n * `WidgetType` union and fails the build if any type is missing from this map,\n * so a newly-added widget type cannot ship without an explicit embed decision.\n */\n\nimport type { WidgetType } from '../../types';\nimport { EMBED_RENDER_MODEL_VERSION } from './types';\n\nexport type EmbedWidgetSupport = 'implemented' | 'blocked';\n\nexport interface EmbedWidgetCoverage {\n readonly support: EmbedWidgetSupport;\n /** Why a type is blocked (surfaced to publication validation + operators). */\n readonly reason?: string;\n}\n\n/**\n * Every `WidgetType` MUST appear here. Implemented set is the read-only MVP\n * native-data core; the rest are blocked with a concrete reason until their\n * sanitizer + renderer + tests land (each will flip to `implemented` in a\n * follow-up within this same renderer PR).\n */\nexport const EMBED_WIDGET_COVERAGE: Record<WidgetType, EmbedWidgetCoverage> = {\n // --- implemented (native data, no external fetch, no sandbox) ---\n metric_card: { support: 'implemented' },\n kpi_card_comparison: { support: 'implemented' },\n table: { support: 'implemented' },\n list: { support: 'implemented' },\n text: { support: 'implemented' },\n\n // --- blocked (pending sanitizer/renderer/tests in this PR) ---\n chart: { support: 'blocked', reason: 'chart renderer + series schema pending' },\n funnel_chart: { support: 'blocked', reason: 'funnel renderer pending' },\n pivot_table: { support: 'blocked', reason: 'pivot projection schema pending' },\n gauge: { support: 'blocked', reason: 'gauge renderer pending' },\n progress: { support: 'blocked', reason: 'progress renderer pending' },\n form: { support: 'blocked', reason: 'requires side-effect action broker (post read-only MVP)' },\n iframe: { support: 'blocked', reason: 'requires frozen exact-origin sandbox declaration' },\n map: { support: 'blocked', reason: 'requires frozen map-provider origins' },\n heatmap: { support: 'blocked', reason: 'heatmap renderer pending' },\n calendar: { support: 'blocked', reason: 'calendar renderer pending' },\n kanban: { support: 'blocked', reason: 'kanban renderer pending' },\n timeline: { support: 'blocked', reason: 'timeline renderer pending' },\n retention: { support: 'blocked', reason: 'retention/cohort renderer pending' },\n custom: { support: 'blocked', reason: 'requires nested opaque-origin sandbox + signed manifest' },\n};\n\nexport function isWidgetTypeEmbeddable(type: WidgetType): boolean {\n return EMBED_WIDGET_COVERAGE[type]?.support === 'implemented';\n}\n\n/** Immutable manifest emitted alongside the asset bundle for contract auditing. */\nexport const EMBED_RENDERER_MANIFEST = {\n renderModelVersion: EMBED_RENDER_MODEL_VERSION,\n implemented: Object.entries(EMBED_WIDGET_COVERAGE)\n .filter(([, v]) => v.support === 'implemented')\n .map(([k]) => k)\n .sort(),\n blocked: Object.entries(EMBED_WIDGET_COVERAGE)\n .filter(([, v]) => v.support === 'blocked')\n .map(([k]) => k)\n .sort(),\n} as const;\n"],"mappings":";;AA8BA,IAAa,IAAiE;CAE5E,aAAa,EAAE,SAAS,eAAe;CACvC,qBAAqB,EAAE,SAAS,eAAe;CAC/C,OAAO,EAAE,SAAS,eAAe;CACjC,MAAM,EAAE,SAAS,eAAe;CAChC,MAAM,EAAE,SAAS,eAAe;CAGhC,OAAO;EAAE,SAAS;EAAW,QAAQ;EAA0C;CAC/E,cAAc;EAAE,SAAS;EAAW,QAAQ;EAA2B;CACvE,aAAa;EAAE,SAAS;EAAW,QAAQ;EAAmC;CAC9E,OAAO;EAAE,SAAS;EAAW,QAAQ;EAA0B;CAC/D,UAAU;EAAE,SAAS;EAAW,QAAQ;EAA6B;CACrE,MAAM;EAAE,SAAS;EAAW,QAAQ;EAA2D;CAC/F,QAAQ;EAAE,SAAS;EAAW,QAAQ;EAAoD;CAC1F,KAAK;EAAE,SAAS;EAAW,QAAQ;EAAwC;CAC3E,SAAS;EAAE,SAAS;EAAW,QAAQ;EAA4B;CACnE,UAAU;EAAE,SAAS;EAAW,QAAQ;EAA6B;CACrE,QAAQ;EAAE,SAAS;EAAW,QAAQ;EAA2B;CACjE,UAAU;EAAE,SAAS;EAAW,QAAQ;EAA6B;CACrE,WAAW;EAAE,SAAS;EAAW,QAAQ;EAAqC;CAC9E,QAAQ;EAAE,SAAS;EAAW,QAAQ;EAA2D;CAClG;AAED,SAAgB,EAAuB,GAA2B;AAChE,QAAO,EAAsB,IAAO,YAAY;;AAIlD,IAAa,IAA0B;CACrC,oBAAA;CACA,aAAa,OAAO,QAAQ,EAAsB,CAC/C,QAAQ,GAAG,OAAO,EAAE,YAAY,cAAc,CAC9C,KAAK,CAAC,OAAO,EAAE,CACf,MAAM;CACT,SAAS,OAAO,QAAQ,EAAsB,CAC3C,QAAQ,GAAG,OAAO,EAAE,YAAY,UAAU,CAC1C,KAAK,CAAC,OAAO,EAAE,CACf,MAAM;CACV"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../../../src/bigconsole/components/embed/types.ts"],"sourcesContent":["/**\n * Stable, versioned embed render-model contract (BOFF-2986).\n *\n * This is the ONLY shape the read-only embed renderer consumes. It is a\n * sanitized DTO produced by the BigConsole publication builder — it deliberately\n * carries NO authenticated stores, Apollo clients, data-source configs,\n * credentials or internal ids. The renderer never fetches data itself; all\n * values are pre-projected into the model or fetched later through the\n * publication-scoped interaction controller using opaque aliases.\n *\n * Backend (publication builder) and frontend (renderer) MUST agree on\n * `EMBED_RENDER_MODEL_VERSION`. A mismatch fails closed at load time rather than\n * rendering a stale/incompatible view.\n */\n\nimport type { WidgetType } from '../../types';\n\n/** Bump on any breaking change to the DTO shapes below. */\nexport const EMBED_RENDER_MODEL_VERSION = 1 as const;\n\n/** Pure layout box (grid units); no store, no event emission. */\nexport interface EmbedWidgetLayout {\n readonly x: number;\n readonly y: number;\n readonly w: number;\n readonly h: number;\n}\n\n/**\n * A single widget as projected for embedding. `config` and `data` are already\n * sanitized and schema-narrowed per widget type by the publication builder; the\n * renderer validates them again against its per-type schema before rendering.\n */\nexport interface EmbedWidgetModel {\n /** Opaque, publication-scoped widget id (not the internal DB id). */\n readonly id: string;\n readonly type: WidgetType;\n readonly title?: string;\n readonly layout: EmbedWidgetLayout;\n /** Sanitized, type-narrowed presentation config. */\n readonly config: Readonly<Record<string, unknown>>;\n /** Sanitized, pre-projected data payload (or null for interaction-fetched). */\n readonly data: Readonly<Record<string, unknown>> | null;\n}\n\nexport interface EmbedPageModel {\n readonly id: string;\n readonly title?: string;\n readonly widgets: readonly EmbedWidgetModel[];\n}\n\n/**\n * Publication-approved runtime capabilities. Everything defaults OFF; the\n * renderer must never enable an interaction the publication did not grant.\n */\nexport interface EmbedCapabilities {\n readonly filters: boolean;\n readonly drilldown: boolean;\n readonly downloads: boolean;\n /** Side-effect actions (forms/webhooks/workflows) — out of scope for read-only MVP. */\n readonly actions: boolean;\n}\n\nexport interface EmbedRenderModel {\n readonly renderModelVersion: number;\n readonly clientContractVersion: number;\n readonly title?: string;\n readonly pages: readonly EmbedPageModel[];\n readonly capabilities: EmbedCapabilities;\n /** ISO timestamp the immutable publication was frozen. */\n readonly definitionPublishedAt: string;\n}\n\n/** Result of validating an inbound render model before rendering. */\nexport type EmbedModelValidation =\n { readonly ok: true; readonly model: EmbedRenderModel } | { readonly ok: false; readonly reason: string };\n\n/**\n * Shape every embed widget renderer component receives. It gets the sanitized\n * model plus a controller for publication-scoped interactions — never raw hooks.\n */\nexport interface EmbedWidgetRenderProps {\n readonly widget: EmbedWidgetModel;\n readonly capabilities: EmbedCapabilities;\n}\n"],"mappings":";AAkBA,IAAa,IAA6B"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import "./types.js";
|
|
2
|
+
//#region src/bigconsole/components/embed/validate.ts
|
|
3
|
+
function e(e) {
|
|
4
|
+
if (!e || typeof e != "object") return {
|
|
5
|
+
ok: !1,
|
|
6
|
+
reason: "model is not an object"
|
|
7
|
+
};
|
|
8
|
+
let t = e;
|
|
9
|
+
if (t.renderModelVersion !== 1) return {
|
|
10
|
+
ok: !1,
|
|
11
|
+
reason: `unsupported renderModelVersion ${String(t.renderModelVersion)} (expected 1)`
|
|
12
|
+
};
|
|
13
|
+
if (!Array.isArray(t.pages)) return {
|
|
14
|
+
ok: !1,
|
|
15
|
+
reason: "pages missing or not an array"
|
|
16
|
+
};
|
|
17
|
+
let n = t.capabilities;
|
|
18
|
+
return !n || typeof n != "object" || Array.isArray(n) ? {
|
|
19
|
+
ok: !1,
|
|
20
|
+
reason: "capabilities missing or malformed"
|
|
21
|
+
} : {
|
|
22
|
+
ok: !0,
|
|
23
|
+
model: e
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { e as validateEmbedRenderModel };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","names":[],"sources":["../../../../src/bigconsole/components/embed/validate.ts"],"sourcesContent":["/**\n * Render-model validation (BOFF-2986).\n *\n * Fails closed: an inbound model with the wrong version or a structurally\n * invalid shape is rejected before rendering, rather than partially displayed.\n */\nimport { EMBED_RENDER_MODEL_VERSION, type EmbedModelValidation, type EmbedRenderModel } from './types';\n\nexport function validateEmbedRenderModel(input: unknown): EmbedModelValidation {\n if (!input || typeof input !== 'object') {\n return { ok: false, reason: 'model is not an object' };\n }\n const m = input as Record<string, unknown>;\n if (m.renderModelVersion !== EMBED_RENDER_MODEL_VERSION) {\n return {\n ok: false,\n reason: `unsupported renderModelVersion ${String(m.renderModelVersion)} (expected ${EMBED_RENDER_MODEL_VERSION})`,\n };\n }\n if (!Array.isArray(m.pages)) {\n return { ok: false, reason: 'pages missing or not an array' };\n }\n const caps = m.capabilities;\n // `typeof [] === 'object'`, so an array would slip through a bare object check\n // and then every `capabilities.<flag>` read as undefined (silently disabling\n // capabilities) — reject non-plain-object capabilities explicitly (fail closed).\n if (!caps || typeof caps !== 'object' || Array.isArray(caps)) {\n return { ok: false, reason: 'capabilities missing or malformed' };\n }\n // Structural shape is acceptable; per-widget config/data are re-narrowed at\n // render time by each renderer, so we don't deep-validate every widget here.\n return { ok: true, model: input as EmbedRenderModel };\n}\n"],"mappings":";;AAQA,SAAgB,EAAyB,GAAsC;AAC7E,KAAI,CAAC,KAAS,OAAO,KAAU,SAC7B,QAAO;EAAE,IAAI;EAAO,QAAQ;EAA0B;CAExD,IAAM,IAAI;AACV,KAAI,EAAE,uBAAA,EACJ,QAAO;EACL,IAAI;EACJ,QAAQ,kCAAkC,OAAO,EAAE,mBAAmB,CAAC;EACxE;AAEH,KAAI,CAAC,MAAM,QAAQ,EAAE,MAAM,CACzB,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAiC;CAE/D,IAAM,IAAO,EAAE;AASf,QALI,CAAC,KAAQ,OAAO,KAAS,YAAY,MAAM,QAAQ,EAAK,GACnD;EAAE,IAAI;EAAO,QAAQ;EAAqC,GAI5D;EAAE,IAAI;EAAM,OAAO;EAA2B"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { asFiniteNumber as e, asRecord as t, asString as n, formatNumber as r } from "./shared.js";
|
|
2
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
3
|
+
//#region src/bigconsole/components/embed/widgets/KpiComparisonEmbed.tsx
|
|
4
|
+
function o({ widget: o }) {
|
|
5
|
+
let s = t(o.config), c = t(o.data), l = e(c.current ?? s.current), u = e(c.previous ?? s.previous), d = n(s.label ?? o.title, "KPI"), f = null;
|
|
6
|
+
l !== null && u !== null && u !== 0 && (f = (l - u) / Math.abs(u) * 100);
|
|
7
|
+
let p = f !== null && f >= 0;
|
|
8
|
+
return /* @__PURE__ */ a("div", {
|
|
9
|
+
className: "flex h-full w-full flex-col justify-center gap-1 bg-surface p-4",
|
|
10
|
+
children: [
|
|
11
|
+
/* @__PURE__ */ i("div", {
|
|
12
|
+
className: "text-xs uppercase tracking-wide text-secondary",
|
|
13
|
+
children: d
|
|
14
|
+
}),
|
|
15
|
+
/* @__PURE__ */ i("div", {
|
|
16
|
+
className: "text-3xl font-semibold text-primary",
|
|
17
|
+
children: l === null ? "—" : r(l, s)
|
|
18
|
+
}),
|
|
19
|
+
f !== null && /* @__PURE__ */ a("div", {
|
|
20
|
+
className: p ? "text-sm text-status-success-text" : "text-sm text-status-error-text",
|
|
21
|
+
children: [
|
|
22
|
+
p ? "▲" : "▼",
|
|
23
|
+
" ",
|
|
24
|
+
Math.abs(f).toFixed(1),
|
|
25
|
+
"%"
|
|
26
|
+
]
|
|
27
|
+
})
|
|
28
|
+
]
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { o as KpiComparisonEmbed };
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=KpiComparisonEmbed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KpiComparisonEmbed.js","names":[],"sources":["../../../../../src/bigconsole/components/embed/widgets/KpiComparisonEmbed.tsx"],"sourcesContent":["/** Read-only KPI comparison embed renderer (BOFF-2986). */\nimport type { EmbedWidgetRenderProps } from '../types';\nimport { asRecord, asString, asFiniteNumber, formatNumber } from './shared';\n\nexport function KpiComparisonEmbed({ widget }: EmbedWidgetRenderProps) {\n const config = asRecord(widget.config);\n const data = asRecord(widget.data);\n const current = asFiniteNumber(data.current ?? config.current);\n const previous = asFiniteNumber(data.previous ?? config.previous);\n const label = asString(config.label ?? widget.title, 'KPI');\n\n let deltaPct: number | null = null;\n if (current !== null && previous !== null && previous !== 0) {\n deltaPct = ((current - previous) / Math.abs(previous)) * 100;\n }\n const positive = deltaPct !== null && deltaPct >= 0;\n\n return (\n <div className=\"flex h-full w-full flex-col justify-center gap-1 bg-surface p-4\">\n <div className=\"text-xs uppercase tracking-wide text-secondary\">{label}</div>\n <div className=\"text-3xl font-semibold text-primary\">\n {current !== null ? formatNumber(current, config) : '—'}\n </div>\n {deltaPct !== null && (\n <div className={positive ? 'text-sm text-status-success-text' : 'text-sm text-status-error-text'}>\n {positive ? '▲' : '▼'} {Math.abs(deltaPct).toFixed(1)}%\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;AAIA,SAAgB,EAAmB,EAAE,aAAkC;CACrE,IAAM,IAAS,EAAS,EAAO,OAAO,EAChC,IAAO,EAAS,EAAO,KAAK,EAC5B,IAAU,EAAe,EAAK,WAAW,EAAO,QAAQ,EACxD,IAAW,EAAe,EAAK,YAAY,EAAO,SAAS,EAC3D,IAAQ,EAAS,EAAO,SAAS,EAAO,OAAO,MAAM,EAEvD,IAA0B;AAC9B,CAAI,MAAY,QAAQ,MAAa,QAAQ,MAAa,MACxD,KAAa,IAAU,KAAY,KAAK,IAAI,EAAS,GAAI;CAE3D,IAAM,IAAW,MAAa,QAAQ,KAAY;AAElD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cAAkD;IAAY,CAAA;GAC7E,kBAAC,OAAD;IAAK,WAAU;cACZ,MAAY,OAAuC,MAAhC,EAAa,GAAS,EAAO;IAC7C,CAAA;GACL,MAAa,QACZ,kBAAC,OAAD;IAAK,WAAW,IAAW,qCAAqC;cAAhE;KACG,IAAW,MAAM;KAAI;KAAE,KAAK,IAAI,EAAS,CAAC,QAAQ,EAAE;KAAC;KAClD;;GAEJ"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { asArray as e, asRecord as t, asString as n, displayValue as r } from "./shared.js";
|
|
2
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
3
|
+
//#region src/bigconsole/components/embed/widgets/ListEmbed.tsx
|
|
4
|
+
var o = 500;
|
|
5
|
+
function s({ widget: s }) {
|
|
6
|
+
let c = t(s.config), l = t(s.data), u = n(c.primaryField ?? c.titleField, "title"), d = n(c.secondaryField ?? c.subtitleField, "subtitle"), f = e(l.items).map((e) => t(e)).slice(0, o);
|
|
7
|
+
return f.length === 0 ? /* @__PURE__ */ i("div", {
|
|
8
|
+
className: "flex h-full items-center justify-center bg-surface p-4 text-sm text-secondary",
|
|
9
|
+
children: "No items"
|
|
10
|
+
}) : /* @__PURE__ */ a("div", {
|
|
11
|
+
className: "h-full w-full overflow-auto bg-surface",
|
|
12
|
+
children: [s.title && /* @__PURE__ */ i("div", {
|
|
13
|
+
className: "px-3 py-2 text-sm font-medium text-primary",
|
|
14
|
+
children: s.title
|
|
15
|
+
}), /* @__PURE__ */ i("ul", {
|
|
16
|
+
className: "divide-y divide-border-default",
|
|
17
|
+
children: f.map((e, t) => {
|
|
18
|
+
let n = r(e[u]), o = r(e[d]);
|
|
19
|
+
return /* @__PURE__ */ a("li", {
|
|
20
|
+
className: "px-3 py-2",
|
|
21
|
+
children: [/* @__PURE__ */ i("div", {
|
|
22
|
+
className: "text-sm text-primary",
|
|
23
|
+
children: n
|
|
24
|
+
}), o && /* @__PURE__ */ i("div", {
|
|
25
|
+
className: "text-xs text-secondary",
|
|
26
|
+
children: o
|
|
27
|
+
})]
|
|
28
|
+
}, t);
|
|
29
|
+
})
|
|
30
|
+
})]
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { s as ListEmbed };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=ListEmbed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ListEmbed.js","names":[],"sources":["../../../../../src/bigconsole/components/embed/widgets/ListEmbed.tsx"],"sourcesContent":["/** Read-only list embed renderer (BOFF-2986). */\nimport type { EmbedWidgetRenderProps } from '../types';\nimport { asRecord, asArray, asString, displayValue } from './shared';\n\nconst MAX_ITEMS = 500;\n\nexport function ListEmbed({ widget }: EmbedWidgetRenderProps) {\n const config = asRecord(widget.config);\n const data = asRecord(widget.data);\n const primaryKey = asString(config.primaryField ?? config.titleField, 'title');\n const secondaryKey = asString(config.secondaryField ?? config.subtitleField, 'subtitle');\n\n const items = asArray(data.items)\n .map((item) => asRecord(item))\n .slice(0, MAX_ITEMS);\n\n if (items.length === 0) {\n return (\n <div className=\"flex h-full items-center justify-center bg-surface p-4 text-sm text-secondary\">No items</div>\n );\n }\n\n return (\n <div className=\"h-full w-full overflow-auto bg-surface\">\n {widget.title && <div className=\"px-3 py-2 text-sm font-medium text-primary\">{widget.title}</div>}\n <ul className=\"divide-y divide-border-default\">\n {items.map((item, i) => {\n const primary = displayValue(item[primaryKey]);\n const secondary = displayValue(item[secondaryKey]);\n return (\n <li key={i} className=\"px-3 py-2\">\n <div className=\"text-sm text-primary\">{primary}</div>\n {secondary && <div className=\"text-xs text-secondary\">{secondary}</div>}\n </li>\n );\n })}\n </ul>\n </div>\n );\n}\n"],"mappings":";;;AAIA,IAAM,IAAY;AAElB,SAAgB,EAAU,EAAE,aAAkC;CAC5D,IAAM,IAAS,EAAS,EAAO,OAAO,EAChC,IAAO,EAAS,EAAO,KAAK,EAC5B,IAAa,EAAS,EAAO,gBAAgB,EAAO,YAAY,QAAQ,EACxE,IAAe,EAAS,EAAO,kBAAkB,EAAO,eAAe,WAAW,EAElF,IAAQ,EAAQ,EAAK,MAAM,CAC9B,KAAK,MAAS,EAAS,EAAK,CAAC,CAC7B,MAAM,GAAG,EAAU;AAQtB,QANI,EAAM,WAAW,IAEjB,kBAAC,OAAD;EAAK,WAAU;YAAgF;EAAc,CAAA,GAK/G,kBAAC,OAAD;EAAK,WAAU;YAAf,CACG,EAAO,SAAS,kBAAC,OAAD;GAAK,WAAU;aAA8C,EAAO;GAAY,CAAA,EACjG,kBAAC,MAAD;GAAI,WAAU;aACX,EAAM,KAAK,GAAM,MAAM;IACtB,IAAM,IAAU,EAAa,EAAK,GAAY,EACxC,IAAY,EAAa,EAAK,GAAc;AAClD,WACE,kBAAC,MAAD;KAAY,WAAU;eAAtB,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAwB;MAAc,CAAA,EACpD,KAAa,kBAAC,OAAD;MAAK,WAAU;gBAA0B;MAAgB,CAAA,CACpE;OAHI,EAGJ;KAEP;GACC,CAAA,CACD"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { asFiniteNumber as e, asRecord as t, asString as n, formatNumber as r } from "./shared.js";
|
|
2
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
3
|
+
//#region src/bigconsole/components/embed/widgets/MetricCardEmbed.tsx
|
|
4
|
+
function o({ widget: o }) {
|
|
5
|
+
let s = t(o.config), c = e(t(o.data).value ?? s.value), l = n(s.label ?? o.title, "Metric"), u = c === null ? "—" : r(c, s);
|
|
6
|
+
return /* @__PURE__ */ a("div", {
|
|
7
|
+
className: "flex h-full w-full flex-col justify-center gap-1 bg-surface p-4",
|
|
8
|
+
children: [/* @__PURE__ */ i("div", {
|
|
9
|
+
className: "text-xs uppercase tracking-wide text-secondary",
|
|
10
|
+
children: l
|
|
11
|
+
}), /* @__PURE__ */ i("div", {
|
|
12
|
+
className: "text-3xl font-semibold text-primary",
|
|
13
|
+
children: u
|
|
14
|
+
})]
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { o as MetricCardEmbed };
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=MetricCardEmbed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MetricCardEmbed.js","names":[],"sources":["../../../../../src/bigconsole/components/embed/widgets/MetricCardEmbed.tsx"],"sourcesContent":["/** Read-only metric card embed renderer (BOFF-2986). */\nimport type { EmbedWidgetRenderProps } from '../types';\nimport { asRecord, asString, asFiniteNumber, formatNumber } from './shared';\n\nexport function MetricCardEmbed({ widget }: EmbedWidgetRenderProps) {\n const config = asRecord(widget.config);\n const data = asRecord(widget.data);\n const rawValue = asFiniteNumber(data.value ?? config.value);\n const label = asString(config.label ?? widget.title, 'Metric');\n const display = rawValue !== null ? formatNumber(rawValue, config) : '—';\n\n return (\n <div className=\"flex h-full w-full flex-col justify-center gap-1 bg-surface p-4\">\n <div className=\"text-xs uppercase tracking-wide text-secondary\">{label}</div>\n <div className=\"text-3xl font-semibold text-primary\">{display}</div>\n </div>\n );\n}\n"],"mappings":";;;AAIA,SAAgB,EAAgB,EAAE,aAAkC;CAClE,IAAM,IAAS,EAAS,EAAO,OAAO,EAEhC,IAAW,EADJ,EAAS,EAAO,KAAK,CACG,SAAS,EAAO,MAAM,EACrD,IAAQ,EAAS,EAAO,SAAS,EAAO,OAAO,SAAS,EACxD,IAAU,MAAa,OAAwC,MAAjC,EAAa,GAAU,EAAO;AAElE,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAkD;GAAY,CAAA,EAC7E,kBAAC,OAAD;GAAK,WAAU;aAAuC;GAAc,CAAA,CAChE"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { asArray as e, asRecord as t, asString as n, displayValue as r } from "./shared.js";
|
|
2
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
3
|
+
//#region src/bigconsole/components/embed/widgets/TableEmbed.tsx
|
|
4
|
+
var o = 500;
|
|
5
|
+
function s(r, i) {
|
|
6
|
+
let a = e(r.columns).map((e) => t(e)).map((e) => ({
|
|
7
|
+
key: n(e.key),
|
|
8
|
+
label: n(e.label ?? e.key)
|
|
9
|
+
})).filter((e) => e.key.length > 0);
|
|
10
|
+
return a.length > 0 ? a : Object.keys(i).map((e) => ({
|
|
11
|
+
key: e,
|
|
12
|
+
label: e
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
15
|
+
function c({ widget: n }) {
|
|
16
|
+
let c = t(n.config), l = e(t(n.data).rows).map((e) => t(e)).slice(0, o), u = s(c, l[0] ?? {});
|
|
17
|
+
return u.length === 0 ? /* @__PURE__ */ i("div", {
|
|
18
|
+
className: "flex h-full items-center justify-center bg-surface p-4 text-sm text-secondary",
|
|
19
|
+
children: "No data"
|
|
20
|
+
}) : /* @__PURE__ */ a("div", {
|
|
21
|
+
className: "h-full w-full overflow-auto bg-surface",
|
|
22
|
+
children: [n.title && /* @__PURE__ */ i("div", {
|
|
23
|
+
className: "px-3 py-2 text-sm font-medium text-primary",
|
|
24
|
+
children: n.title
|
|
25
|
+
}), /* @__PURE__ */ a("table", {
|
|
26
|
+
className: "w-full border-collapse text-sm",
|
|
27
|
+
children: [/* @__PURE__ */ i("thead", { children: /* @__PURE__ */ i("tr", { children: u.map((e) => /* @__PURE__ */ i("th", {
|
|
28
|
+
className: "border-b border-border-default px-3 py-2 text-left font-medium text-secondary",
|
|
29
|
+
children: e.label
|
|
30
|
+
}, e.key)) }) }), /* @__PURE__ */ i("tbody", { children: l.map((e, t) => /* @__PURE__ */ i("tr", { children: u.map((t) => /* @__PURE__ */ i("td", {
|
|
31
|
+
className: "border-b border-border-default px-3 py-2 text-primary",
|
|
32
|
+
children: r(e[t.key])
|
|
33
|
+
}, t.key)) }, t)) })]
|
|
34
|
+
})]
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { c as TableEmbed };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=TableEmbed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TableEmbed.js","names":[],"sources":["../../../../../src/bigconsole/components/embed/widgets/TableEmbed.tsx"],"sourcesContent":["/** Read-only table embed renderer (BOFF-2986). */\nimport type { EmbedWidgetRenderProps } from '../types';\nimport { asRecord, asArray, asString, displayValue } from './shared';\n\ninterface Column {\n key: string;\n label: string;\n}\n\n/** Hard client-side cap; the publication builder is expected to page/limit rows. */\nconst MAX_ROWS = 500;\n\nfunction readColumns(config: Record<string, unknown>, firstRow: Record<string, unknown>): Column[] {\n const configured = asArray(config.columns)\n .map((c) => asRecord(c))\n .map((c) => ({ key: asString(c.key), label: asString(c.label ?? c.key) }))\n .filter((c) => c.key.length > 0);\n if (configured.length > 0) return configured;\n // Derive from the first row when the publication didn't pin columns.\n return Object.keys(firstRow).map((key) => ({ key, label: key }));\n}\n\nexport function TableEmbed({ widget }: EmbedWidgetRenderProps) {\n const config = asRecord(widget.config);\n const data = asRecord(widget.data);\n const rows = asArray(data.rows)\n .map((r) => asRecord(r))\n .slice(0, MAX_ROWS);\n const columns = readColumns(config, rows[0] ?? {});\n\n if (columns.length === 0) {\n return <div className=\"flex h-full items-center justify-center bg-surface p-4 text-sm text-secondary\">No data</div>;\n }\n\n return (\n <div className=\"h-full w-full overflow-auto bg-surface\">\n {widget.title && <div className=\"px-3 py-2 text-sm font-medium text-primary\">{widget.title}</div>}\n <table className=\"w-full border-collapse text-sm\">\n <thead>\n <tr>\n {columns.map((col) => (\n <th\n key={col.key}\n className=\"border-b border-border-default px-3 py-2 text-left font-medium text-secondary\"\n >\n {col.label}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {rows.map((row, i) => (\n <tr key={i}>\n {columns.map((col) => (\n <td key={col.key} className=\"border-b border-border-default px-3 py-2 text-primary\">\n {displayValue(row[col.key])}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n );\n}\n"],"mappings":";;;AAUA,IAAM,IAAW;AAEjB,SAAS,EAAY,GAAiC,GAA6C;CACjG,IAAM,IAAa,EAAQ,EAAO,QAAQ,CACvC,KAAK,MAAM,EAAS,EAAE,CAAC,CACvB,KAAK,OAAO;EAAE,KAAK,EAAS,EAAE,IAAI;EAAE,OAAO,EAAS,EAAE,SAAS,EAAE,IAAI;EAAE,EAAE,CACzE,QAAQ,MAAM,EAAE,IAAI,SAAS,EAAE;AAGlC,QAFI,EAAW,SAAS,IAAU,IAE3B,OAAO,KAAK,EAAS,CAAC,KAAK,OAAS;EAAE;EAAK,OAAO;EAAK,EAAE;;AAGlE,SAAgB,EAAW,EAAE,aAAkC;CAC7D,IAAM,IAAS,EAAS,EAAO,OAAO,EAEhC,IAAO,EADA,EAAS,EAAO,KAAK,CACR,KAAK,CAC5B,KAAK,MAAM,EAAS,EAAE,CAAC,CACvB,MAAM,GAAG,EAAS,EACf,IAAU,EAAY,GAAQ,EAAK,MAAM,EAAE,CAAC;AAMlD,QAJI,EAAQ,WAAW,IACd,kBAAC,OAAD;EAAK,WAAU;YAAgF;EAAa,CAAA,GAInH,kBAAC,OAAD;EAAK,WAAU;YAAf,CACG,EAAO,SAAS,kBAAC,OAAD;GAAK,WAAU;aAA8C,EAAO;GAAY,CAAA,EACjG,kBAAC,SAAD;GAAO,WAAU;aAAjB,CACE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD,EAAA,UACG,EAAQ,KAAK,MACZ,kBAAC,MAAD;IAEE,WAAU;cAET,EAAI;IACF,EAJE,EAAI,IAIN,CACL,EACC,CAAA,EACC,CAAA,EACR,kBAAC,SAAD,EAAA,UACG,EAAK,KAAK,GAAK,MACd,kBAAC,MAAD,EAAA,UACG,EAAQ,KAAK,MACZ,kBAAC,MAAD;IAAkB,WAAU;cACzB,EAAa,EAAI,EAAI,KAAK;IACxB,EAFI,EAAI,IAER,CACL,EACC,EANI,EAMJ,CACL,EACI,CAAA,CACF;KACJ"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { asRecord as e, asString as t } from "./shared.js";
|
|
2
|
+
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
3
|
+
//#region src/bigconsole/components/embed/widgets/TextEmbed.tsx
|
|
4
|
+
function i({ widget: i }) {
|
|
5
|
+
let a = e(i.config), o = t(a.content ?? a.text ?? a.markdown);
|
|
6
|
+
return /* @__PURE__ */ r("div", {
|
|
7
|
+
className: "h-full w-full overflow-auto bg-surface p-4",
|
|
8
|
+
children: [i.title && /* @__PURE__ */ n("div", {
|
|
9
|
+
className: "mb-2 text-sm font-medium text-primary",
|
|
10
|
+
children: i.title
|
|
11
|
+
}), /* @__PURE__ */ n("div", {
|
|
12
|
+
className: "whitespace-pre-wrap break-words text-sm text-primary",
|
|
13
|
+
children: o
|
|
14
|
+
})]
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { i as TextEmbed };
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=TextEmbed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TextEmbed.js","names":[],"sources":["../../../../../src/bigconsole/components/embed/widgets/TextEmbed.tsx"],"sourcesContent":["/**\n * Read-only text embed renderer (BOFF-2986).\n *\n * Renders the text content as PLAIN TEXT — React escapes it, so no HTML/script\n * from the content can execute. Rich Markdown/HTML rendering is deferred until a\n * vetted sanitizer is wired; until then plain text is the safe default rather\n * than `dangerouslySetInnerHTML`.\n */\nimport type { EmbedWidgetRenderProps } from '../types';\nimport { asRecord, asString } from './shared';\n\nexport function TextEmbed({ widget }: EmbedWidgetRenderProps) {\n const config = asRecord(widget.config);\n const content = asString(config.content ?? config.text ?? config.markdown);\n\n return (\n <div className=\"h-full w-full overflow-auto bg-surface p-4\">\n {widget.title && <div className=\"mb-2 text-sm font-medium text-primary\">{widget.title}</div>}\n <div className=\"whitespace-pre-wrap break-words text-sm text-primary\">{content}</div>\n </div>\n );\n}\n"],"mappings":";;;AAWA,SAAgB,EAAU,EAAE,aAAkC;CAC5D,IAAM,IAAS,EAAS,EAAO,OAAO,EAChC,IAAU,EAAS,EAAO,WAAW,EAAO,QAAQ,EAAO,SAAS;AAE1E,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACG,EAAO,SAAS,kBAAC,OAAD;GAAK,WAAU;aAAyC,EAAO;GAAY,CAAA,EAC5F,kBAAC,OAAD;GAAK,WAAU;aAAwD;GAAc,CAAA,CACjF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/bigconsole/components/embed/widgets/shared.ts
|
|
2
|
+
function e(e, t = "") {
|
|
3
|
+
return typeof e == "string" ? e : t;
|
|
4
|
+
}
|
|
5
|
+
function t(e) {
|
|
6
|
+
if (typeof e == "number" && Number.isFinite(e)) return e;
|
|
7
|
+
if (typeof e == "string" && e.trim() !== "") {
|
|
8
|
+
let t = Number(e);
|
|
9
|
+
if (Number.isFinite(t)) return t;
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
function n(e) {
|
|
14
|
+
return Array.isArray(e) ? e : [];
|
|
15
|
+
}
|
|
16
|
+
function r(e) {
|
|
17
|
+
return e && typeof e == "object" && !Array.isArray(e) ? e : {};
|
|
18
|
+
}
|
|
19
|
+
function i(e) {
|
|
20
|
+
return e == null ? "" : typeof e == "string" ? e : typeof e == "number" ? Number.isFinite(e) ? String(e) : "" : typeof e == "boolean" ? e ? "true" : "false" : "";
|
|
21
|
+
}
|
|
22
|
+
function a(n, r) {
|
|
23
|
+
let i = t(r.decimals), a = e(r.prefix), o = e(r.suffix);
|
|
24
|
+
return `${a}${i === null ? String(n) : n.toFixed(Math.min(Math.max(i, 0), 10))}${o}`;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { n as asArray, t as asFiniteNumber, r as asRecord, e as asString, i as displayValue, a as formatNumber };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","names":[],"sources":["../../../../../src/bigconsole/components/embed/widgets/shared.ts"],"sourcesContent":["/**\n * Defensive value coercion for embed widget renderers (BOFF-2986).\n *\n * The render model is sanitized by the publication builder, but the renderer\n * re-narrows every value it reads: an embedded widget is anonymous, untrusted\n * customer-facing surface, so we never assume a field is the type we expect.\n */\n\nexport function asString(value: unknown, fallback = ''): string {\n return typeof value === 'string' ? value : fallback;\n}\n\nexport function asFiniteNumber(value: unknown): number | null {\n if (typeof value === 'number' && Number.isFinite(value)) return value;\n if (typeof value === 'string' && value.trim() !== '') {\n const n = Number(value);\n if (Number.isFinite(n)) return n;\n }\n return null;\n}\n\nexport function asArray(value: unknown): unknown[] {\n return Array.isArray(value) ? value : [];\n}\n\nexport function asRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record<string, unknown>) : {};\n}\n\n/**\n * Render an arbitrary cell value as a display string. Objects/arrays collapse to\n * an empty string rather than `[object Object]`, so no internal structure leaks.\n */\nexport function displayValue(value: unknown): string {\n if (value === null || value === undefined) return '';\n if (typeof value === 'string') return value;\n if (typeof value === 'number') return Number.isFinite(value) ? String(value) : '';\n if (typeof value === 'boolean') return value ? 'true' : 'false';\n return '';\n}\n\n/** Format a number with the widget's optional locale/format hints, safely. */\nexport function formatNumber(value: number, config: Record<string, unknown>): string {\n const decimals = asFiniteNumber(config.decimals);\n const prefix = asString(config.prefix);\n const suffix = asString(config.suffix);\n const fixed = decimals !== null ? value.toFixed(Math.min(Math.max(decimals, 0), 10)) : String(value);\n return `${prefix}${fixed}${suffix}`;\n}\n"],"mappings":";AAQA,SAAgB,EAAS,GAAgB,IAAW,IAAY;AAC9D,QAAO,OAAO,KAAU,WAAW,IAAQ;;AAG7C,SAAgB,EAAe,GAA+B;AAC5D,KAAI,OAAO,KAAU,YAAY,OAAO,SAAS,EAAM,CAAE,QAAO;AAChE,KAAI,OAAO,KAAU,YAAY,EAAM,MAAM,KAAK,IAAI;EACpD,IAAM,IAAI,OAAO,EAAM;AACvB,MAAI,OAAO,SAAS,EAAE,CAAE,QAAO;;AAEjC,QAAO;;AAGT,SAAgB,EAAQ,GAA2B;AACjD,QAAO,MAAM,QAAQ,EAAM,GAAG,IAAQ,EAAE;;AAG1C,SAAgB,EAAS,GAAyC;AAChE,QAAO,KAAS,OAAO,KAAU,YAAY,CAAC,MAAM,QAAQ,EAAM,GAAI,IAAoC,EAAE;;AAO9G,SAAgB,EAAa,GAAwB;AAKnD,QAJI,KAAU,OAAoC,KAC9C,OAAO,KAAU,WAAiB,IAClC,OAAO,KAAU,WAAiB,OAAO,SAAS,EAAM,GAAG,OAAO,EAAM,GAAG,KAC3E,OAAO,KAAU,YAAkB,IAAQ,SAAS,UACjD;;AAIT,SAAgB,EAAa,GAAe,GAAyC;CACnF,IAAM,IAAW,EAAe,EAAO,SAAS,EAC1C,IAAS,EAAS,EAAO,OAAO,EAChC,IAAS,EAAS,EAAO,OAAO;AAEtC,QAAO,GAAG,IADI,MAAa,OAA4D,OAAO,EAAM,GAAlE,EAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,GAAU,EAAE,EAAE,GAAG,CAAC,GACzD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-bigconsole",
|
|
3
|
-
"version": "2026.730.
|
|
3
|
+
"version": "2026.730.4",
|
|
4
4
|
"description": "BigConsole - AI-powered analytics and dashboard platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"./assistant": {
|
|
17
17
|
"types": "./dist/bigconsole/assistant/index.d.ts",
|
|
18
18
|
"import": "./dist/bigconsole/assistant/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./embed": {
|
|
21
|
+
"types": "./dist/bigconsole/components/embed/index.d.ts",
|
|
22
|
+
"import": "./dist/bigconsole/components/embed/index.js"
|
|
19
23
|
}
|
|
20
24
|
},
|
|
21
25
|
"main": "./dist/index.js",
|