@jini-ai/ui 0.5.1 → 0.5.2
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/CHANGELOG.md +5 -0
- package/dist/features/admin-widgets/components/InfoTip.rules.d.ts +1 -1
- package/dist/features/admin-widgets/components/InfoTip.rules.js +1 -1
- package/dist/features/mcp-ui/surfaces/document.d.ts +1 -1
- package/dist/features/mcp-ui/surfaces/document.js +1 -1
- package/dist/features/mcp-ui/surfaces/host-theme.d.ts +30 -0
- package/dist/features/mcp-ui/surfaces/host-theme.js +85 -0
- package/dist/features/mcp-ui/surfaces/index.d.ts +1 -0
- package/dist/features/mcp-ui/surfaces/index.js +1 -0
- package/dist/features/mcp-ui/surfaces/tokens.d.ts +11 -9
- package/dist/features/mcp-ui/surfaces/tokens.js +21 -12
- package/dist/features/panel-kit/helpers/format-timestamp.js +1 -1
- package/dist/react/mcp-ui/McpUiHost.js +16 -3
- package/dist/react/mcp-ui/useHostSurfaceTokens.d.ts +16 -0
- package/dist/react/mcp-ui/useHostSurfaceTokens.js +49 -0
- package/package.json +7 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# @jini-ai/ui
|
|
2
2
|
|
|
3
|
+
## 0.5.2 — 2026-10-13
|
|
4
|
+
|
|
5
|
+
- MCP-UI surfaces follow the host's live theme (da019dde): `McpUiHost` reads the host's resolved `--jini-*` theme where a card mounts and writes it into Jini-built surface documents as a `:root` rule after the token block; a MutationObserver re-themes cards already on screen when the theme or accent changes. Third-party Views are not rewritten. Builder token overrides render as `:root:root`, so a card that sets its own tokens keeps them. New `--jini-mcpui-accent-ink` token (host `--jini-primary-ink`) for primary button text.
|
|
6
|
+
- Rides along, no API change: interactive-ui/charts shared code declared as isolation-shared metadata instead of a file move (28e84348 moved it, bc2945ee reverted the move); html-editor lookup sets are `ReadonlySet` (27965ef0); comment scrubs (8e508fb6).
|
|
7
|
+
|
|
3
8
|
## 0.5.1 — 2026-10-13
|
|
4
9
|
|
|
5
10
|
- Phone layout fixes. Media provider cards put one field per line on phones and keep the fields plus Clear on one row on wider screens (was a three-column grid that squeezed inputs to a few characters). The snippet block's Copy button no longer covers the command: phones start the code below it, wider screens reserve its width. `TabBar` scrolls a sideways-scrolling strip so the active tab is fully visible (only the strip scrolls, never the page).
|
|
@@ -22,7 +22,7 @@ export interface InfoTipPlacement {
|
|
|
22
22
|
*
|
|
23
23
|
* Why the real measured width: the old clamp assumed every bubble was 18rem wide, so every short tip
|
|
24
24
|
* within 152px of the right edge got the same left — "Remove" sat ~86px away from its ✕ in the
|
|
25
|
-
* dock (Chrome re-check, 2026-10-11). Shared by `useInfoTip` and
|
|
25
|
+
* dock (Chrome re-check, 2026-10-11). Shared by `useInfoTip` and the host's dock-header tips so both
|
|
26
26
|
* place identically.
|
|
27
27
|
* @param input.anchor The icon's `getBoundingClientRect()`.
|
|
28
28
|
* @param input.bubble The rendered bubble's measured size; `null` before the portal exists.
|
|
@@ -16,7 +16,7 @@ const ARROW_INSET_PX = 10;
|
|
|
16
16
|
*
|
|
17
17
|
* Why the real measured width: the old clamp assumed every bubble was 18rem wide, so every short tip
|
|
18
18
|
* within 152px of the right edge got the same left — "Remove" sat ~86px away from its ✕ in the
|
|
19
|
-
* dock (Chrome re-check, 2026-10-11). Shared by `useInfoTip` and
|
|
19
|
+
* dock (Chrome re-check, 2026-10-11). Shared by `useInfoTip` and the host's dock-header tips so both
|
|
20
20
|
* place identically.
|
|
21
21
|
* @param input.anchor The icon's `getBoundingClientRect()`.
|
|
22
22
|
* @param input.bubble The rendered bubble's measured size; `null` before the portal exists.
|
|
@@ -8,7 +8,7 @@ import { type SurfaceTokenName } from './tokens.js';
|
|
|
8
8
|
* be a color no caller could reskin. `var(--x, fallback)` appears only where a value is genuinely
|
|
9
9
|
* optional.
|
|
10
10
|
*/
|
|
11
|
-
export declare const SURFACE_BASE_CSS = "*, *::before, *::after { box-sizing: border-box; }\nhtml, body { margin: 0; padding: 0; }\nbody {\n /* Transparent, not var(--jini-mcpui-bg): this document has no page of its own to paint. It is an\n allow-scripts-only sandboxed iframe embedded directly in a host's chat transcript, and html's\n background is left at its (also transparent) initial value, so the host's own pane shows\n straight through to .mcpui-surface below rather than through a second, unrelated rectangle of\n fill color sitting between the two. --jini-mcpui-bg stays declared for a caller styling\n something else via extraCss -- nothing in this stylesheet reads it anymore. */\n background: transparent;\n color: var(--jini-mcpui-text);\n font-family: var(--jini-mcpui-font);\n font-size: 14px;\n line-height: 1.5;\n padding: 16px;\n -webkit-font-smoothing: antialiased;\n}\n.mcpui-surface {\n background: linear-gradient(135deg,\n color-mix(in srgb, var(--jini-mcpui-accent) 5%, var(--jini-mcpui-panel)) 0%,\n var(--jini-mcpui-panel) 60%);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 18%, var(--jini-mcpui-border));\n border-radius: var(--jini-mcpui-radius-md);\n box-shadow: var(--jini-mcpui-surface-shadow);\n padding: 18px 20px 16px;\n max-width: 640px;\n margin: 0 auto;\n}\n.mcpui-title {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n letter-spacing: -0.01em;\n color: var(--jini-mcpui-text-strong);\n}\n.mcpui-description { margin: 6px 0 0; color: var(--jini-mcpui-text-muted); }\n.mcpui-details {\n display: grid;\n grid-template-columns: max-content minmax(0, 1fr);\n gap: 4px 14px;\n margin: 14px 0 0;\n}\n.mcpui-details dt { color: var(--jini-mcpui-text-soft); font-weight: 500; }\n.mcpui-details dd { margin: 0; overflow-wrap: anywhere; color: var(--jini-mcpui-text); }\n.mcpui-details-outcome { display: flex; flex-direction: column; gap: 8px; }\n.mcpui-outcome-row { display: flex; align-items: flex-start; gap: 14px; }\n.mcpui-outcome-row dt { flex: 1; min-width: 0; overflow-wrap: anywhere; }\n.mcpui-outcome-row dt .mcpui-hint { display: block; font-weight: 400; }\n.mcpui-outcome-row dd { flex: 1; min-width: 0; }\n/* Capped so a long status cannot squeeze the label back to one character per line; the full text\n stays in the cell's title. */\n.mcpui-outcome-row dd.mcpui-detail-status {\n flex: 0 1 auto; max-width: 60%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n}\n/* The iframe has 16px body padding on each side: 352px viewport = 320px card. Stacked, the value\n has the full width, so it wraps between words instead of truncating. */\n@media (max-width: 352px) {\n .mcpui-outcome-row { flex-direction: column; gap: 2px; }\n .mcpui-outcome-row dd.mcpui-detail-status { max-width: 100%; white-space: normal; overflow-wrap: normal; }\n}\n.mcpui-code {\n margin: 0;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n max-height: 16em;\n overflow: auto;\n font-family: var(--jini-mcpui-font-mono);\n font-size: 12.5px;\n line-height: 1.45;\n padding: 6px 8px;\n border: 1px solid var(--jini-mcpui-border);\n border-radius: var(--jini-mcpui-radius-sm);\n background: var(--jini-mcpui-bg);\n}\n.mcpui-warning {\n margin: 14px 0 0;\n padding: 9px 11px;\n border-radius: var(--jini-mcpui-radius-md);\n border: 1px solid var(--jini-mcpui-danger);\n background: var(--jini-mcpui-danger-tint);\n color: var(--jini-mcpui-text-strong);\n}\n.mcpui-fields { display: flex; flex-direction: column; gap: 12px; margin: 16px 0 0; border: 0; padding: 0; }\n.mcpui-field { display: flex; flex-direction: column; gap: 4px; }\n.mcpui-field-inline { flex-direction: row; align-items: flex-start; gap: 8px; }\n.mcpui-label { font-weight: 500; color: var(--jini-mcpui-text-strong); }\n.mcpui-required { color: var(--jini-mcpui-accent); margin-left: 2px; }\n.mcpui-hint { color: var(--jini-mcpui-text-soft); font-size: 13px; }\n.mcpui-input, .mcpui-select, .mcpui-textarea {\n font: inherit;\n color: var(--jini-mcpui-text);\n background: var(--jini-mcpui-panel);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 14%, var(--jini-mcpui-border-strong));\n border-radius: var(--jini-mcpui-radius-sm);\n padding: 7px 9px;\n width: 100%;\n min-width: 0;\n}\n.mcpui-textarea { resize: vertical; min-height: 72px; font-family: var(--jini-mcpui-font); }\n.mcpui-checkbox { margin: 3px 0 0; accent-color: var(--jini-mcpui-accent); }\n.mcpui-choice-group { border: 0; padding: 0; margin: 0; min-width: 0; }\n.mcpui-choice { display: flex; flex-direction: row; align-items: flex-start; gap: 8px; margin: 6px 0 0; }\n.mcpui-choice-label { color: var(--jini-mcpui-text); font-weight: 400; }\n.mcpui-input:focus-visible, .mcpui-select:focus-visible, .mcpui-textarea:focus-visible,\n.mcpui-checkbox:focus-visible, .mcpui-button:focus-visible {\n outline: 2px solid var(--jini-mcpui-accent-ring);\n outline-offset: 1px;\n border-color: var(--jini-mcpui-accent);\n}\n.mcpui-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 18px 0 0; }\n.mcpui-button {\n font: inherit;\n font-weight: 500;\n padding: 7px 15px;\n border-radius: var(--jini-mcpui-radius-pill);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 14%, var(--jini-mcpui-border-strong));\n background: var(--jini-mcpui-panel);\n color: var(--jini-mcpui-text);\n cursor: pointer;\n}\n.mcpui-button:hover:not([disabled]) { background: var(--jini-mcpui-accent-tint); }\n.mcpui-button-primary {\n background: var(--jini-mcpui-accent);\n border-color: var(--jini-mcpui-accent);\n color: var(--jini-mcpui-
|
|
11
|
+
export declare const SURFACE_BASE_CSS = "*, *::before, *::after { box-sizing: border-box; }\nhtml, body { margin: 0; padding: 0; }\nbody {\n /* Transparent, not var(--jini-mcpui-bg): this document has no page of its own to paint. It is an\n allow-scripts-only sandboxed iframe embedded directly in a host's chat transcript, and html's\n background is left at its (also transparent) initial value, so the host's own pane shows\n straight through to .mcpui-surface below rather than through a second, unrelated rectangle of\n fill color sitting between the two. --jini-mcpui-bg stays declared for a caller styling\n something else via extraCss -- nothing in this stylesheet reads it anymore. */\n background: transparent;\n color: var(--jini-mcpui-text);\n font-family: var(--jini-mcpui-font);\n font-size: 14px;\n line-height: 1.5;\n padding: 16px;\n -webkit-font-smoothing: antialiased;\n}\n.mcpui-surface {\n background: linear-gradient(135deg,\n color-mix(in srgb, var(--jini-mcpui-accent) 5%, var(--jini-mcpui-panel)) 0%,\n var(--jini-mcpui-panel) 60%);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 18%, var(--jini-mcpui-border));\n border-radius: var(--jini-mcpui-radius-md);\n box-shadow: var(--jini-mcpui-surface-shadow);\n padding: 18px 20px 16px;\n max-width: 640px;\n margin: 0 auto;\n}\n.mcpui-title {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n letter-spacing: -0.01em;\n color: var(--jini-mcpui-text-strong);\n}\n.mcpui-description { margin: 6px 0 0; color: var(--jini-mcpui-text-muted); }\n.mcpui-details {\n display: grid;\n grid-template-columns: max-content minmax(0, 1fr);\n gap: 4px 14px;\n margin: 14px 0 0;\n}\n.mcpui-details dt { color: var(--jini-mcpui-text-soft); font-weight: 500; }\n.mcpui-details dd { margin: 0; overflow-wrap: anywhere; color: var(--jini-mcpui-text); }\n.mcpui-details-outcome { display: flex; flex-direction: column; gap: 8px; }\n.mcpui-outcome-row { display: flex; align-items: flex-start; gap: 14px; }\n.mcpui-outcome-row dt { flex: 1; min-width: 0; overflow-wrap: anywhere; }\n.mcpui-outcome-row dt .mcpui-hint { display: block; font-weight: 400; }\n.mcpui-outcome-row dd { flex: 1; min-width: 0; }\n/* Capped so a long status cannot squeeze the label back to one character per line; the full text\n stays in the cell's title. */\n.mcpui-outcome-row dd.mcpui-detail-status {\n flex: 0 1 auto; max-width: 60%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n}\n/* The iframe has 16px body padding on each side: 352px viewport = 320px card. Stacked, the value\n has the full width, so it wraps between words instead of truncating. */\n@media (max-width: 352px) {\n .mcpui-outcome-row { flex-direction: column; gap: 2px; }\n .mcpui-outcome-row dd.mcpui-detail-status { max-width: 100%; white-space: normal; overflow-wrap: normal; }\n}\n.mcpui-code {\n margin: 0;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n max-height: 16em;\n overflow: auto;\n font-family: var(--jini-mcpui-font-mono);\n font-size: 12.5px;\n line-height: 1.45;\n padding: 6px 8px;\n border: 1px solid var(--jini-mcpui-border);\n border-radius: var(--jini-mcpui-radius-sm);\n background: var(--jini-mcpui-bg);\n}\n.mcpui-warning {\n margin: 14px 0 0;\n padding: 9px 11px;\n border-radius: var(--jini-mcpui-radius-md);\n border: 1px solid var(--jini-mcpui-danger);\n background: var(--jini-mcpui-danger-tint);\n color: var(--jini-mcpui-text-strong);\n}\n.mcpui-fields { display: flex; flex-direction: column; gap: 12px; margin: 16px 0 0; border: 0; padding: 0; }\n.mcpui-field { display: flex; flex-direction: column; gap: 4px; }\n.mcpui-field-inline { flex-direction: row; align-items: flex-start; gap: 8px; }\n.mcpui-label { font-weight: 500; color: var(--jini-mcpui-text-strong); }\n.mcpui-required { color: var(--jini-mcpui-accent); margin-left: 2px; }\n.mcpui-hint { color: var(--jini-mcpui-text-soft); font-size: 13px; }\n.mcpui-input, .mcpui-select, .mcpui-textarea {\n font: inherit;\n color: var(--jini-mcpui-text);\n background: var(--jini-mcpui-panel);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 14%, var(--jini-mcpui-border-strong));\n border-radius: var(--jini-mcpui-radius-sm);\n padding: 7px 9px;\n width: 100%;\n min-width: 0;\n}\n.mcpui-textarea { resize: vertical; min-height: 72px; font-family: var(--jini-mcpui-font); }\n.mcpui-checkbox { margin: 3px 0 0; accent-color: var(--jini-mcpui-accent); }\n.mcpui-choice-group { border: 0; padding: 0; margin: 0; min-width: 0; }\n.mcpui-choice { display: flex; flex-direction: row; align-items: flex-start; gap: 8px; margin: 6px 0 0; }\n.mcpui-choice-label { color: var(--jini-mcpui-text); font-weight: 400; }\n.mcpui-input:focus-visible, .mcpui-select:focus-visible, .mcpui-textarea:focus-visible,\n.mcpui-checkbox:focus-visible, .mcpui-button:focus-visible {\n outline: 2px solid var(--jini-mcpui-accent-ring);\n outline-offset: 1px;\n border-color: var(--jini-mcpui-accent);\n}\n.mcpui-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 18px 0 0; }\n.mcpui-button {\n font: inherit;\n font-weight: 500;\n padding: 7px 15px;\n border-radius: var(--jini-mcpui-radius-pill);\n border: 1px solid color-mix(in srgb, var(--jini-mcpui-accent) 14%, var(--jini-mcpui-border-strong));\n background: var(--jini-mcpui-panel);\n color: var(--jini-mcpui-text);\n cursor: pointer;\n}\n.mcpui-button:hover:not([disabled]) { background: var(--jini-mcpui-accent-tint); }\n.mcpui-button-primary {\n background: var(--jini-mcpui-accent);\n border-color: var(--jini-mcpui-accent);\n color: var(--jini-mcpui-accent-ink);\n}\n.mcpui-button-primary:hover:not([disabled]) { background: var(--jini-mcpui-accent-hover); border-color: var(--jini-mcpui-accent-hover); }\n.mcpui-button-danger {\n background: var(--jini-mcpui-danger);\n border-color: var(--jini-mcpui-danger);\n color: var(--jini-mcpui-panel);\n}\n.mcpui-button-danger:hover:not([disabled]) { background: var(--jini-mcpui-danger-hover); border-color: var(--jini-mcpui-danger-hover); }\n.mcpui-button[disabled] { opacity: 0.55; cursor: default; }\n.mcpui-status { margin: 12px 0 0; min-height: 1.4em; color: var(--jini-mcpui-text-muted); }\n.mcpui-status[data-state=\"failed\"], .mcpui-status[data-state=\"invalid\"], .mcpui-status[data-state=\"expired\"] { color: var(--jini-mcpui-danger); }\n.mcpui-status[data-state=\"done\"] { color: var(--jini-mcpui-text-strong); }\n/* Neither a plain success nor a failure \u2014 e.g. an outcome surface reporting \"uploaded, not yet\n reachable\" (outcome.ts's own state: partial). Distinct from both --jini-mcpui-danger (would\n wrongly read as \"this failed\") and --jini-mcpui-text-strong (would wrongly read as \"this is\n fully done\"); the accent token reads as \"worth a second look\" without asserting either. */\n.mcpui-status[data-state=\"partial\"] { color: var(--jini-mcpui-accent); }\n.mcpui-code { font-family: var(--jini-mcpui-font-mono); font-size: 13px; color: var(--jini-mcpui-text-faint); }";
|
|
12
12
|
/**
|
|
13
13
|
* The Content-Security-Policy every generated surface declares in its own `<head>`.
|
|
14
14
|
*
|
|
@@ -147,7 +147,7 @@ body {
|
|
|
147
147
|
.mcpui-button-primary {
|
|
148
148
|
background: var(--jini-mcpui-accent);
|
|
149
149
|
border-color: var(--jini-mcpui-accent);
|
|
150
|
-
color: var(--jini-mcpui-
|
|
150
|
+
color: var(--jini-mcpui-accent-ink);
|
|
151
151
|
}
|
|
152
152
|
.mcpui-button-primary:hover:not([disabled]) { background: var(--jini-mcpui-accent-hover); border-color: var(--jini-mcpui-accent-hover); }
|
|
153
153
|
.mcpui-button-danger {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { SurfaceTokenName } from './tokens.js';
|
|
2
|
+
/** The one slice of `CSSStyleDeclaration` this module reads — a test passes a plain object. */
|
|
3
|
+
export interface HostThemeStyle {
|
|
4
|
+
getPropertyValue(name: string): string;
|
|
5
|
+
}
|
|
6
|
+
export type HostSurfaceTokens = Readonly<Partial<Record<SurfaceTokenName, string>>>;
|
|
7
|
+
/**
|
|
8
|
+
* Reads the host's resolved theme at one element.
|
|
9
|
+
*
|
|
10
|
+
* @param style - The element's computed style (`getComputedStyle(el)`), where `--jini-*` resolve
|
|
11
|
+
* through every `var()` the host's theme inputs pass through.
|
|
12
|
+
* @returns The surface tokens to override. A variable the host leaves undeclared (or declares with
|
|
13
|
+
* an unsafe value) is omitted, so the surface keeps its own default for it.
|
|
14
|
+
*/
|
|
15
|
+
export declare function readHostSurfaceTokens(style: HostThemeStyle): HostSurfaceTokens;
|
|
16
|
+
/**
|
|
17
|
+
* Writes host tokens into a surface document.
|
|
18
|
+
*
|
|
19
|
+
* The rule is plain `:root` placed after the document's first `<style>`, so it beats the token
|
|
20
|
+
* block's defaults by source order; a builder's own `tokens` override is emitted as `:root:root`
|
|
21
|
+
* (`renderTokenBlock`), so a card that set its own colors keeps them. Documents carrying no
|
|
22
|
+
* `--jini-mcpui-` token at all are returned unchanged: a third-party View declares none of these
|
|
23
|
+
* names, and its HTML is not this module's to edit.
|
|
24
|
+
*
|
|
25
|
+
* @param html - The surface document.
|
|
26
|
+
* @param tokens - From {@link readHostSurfaceTokens}.
|
|
27
|
+
* @returns The document with the host rule inserted, or `html` itself when there is nothing to do.
|
|
28
|
+
*/
|
|
29
|
+
export declare function applyHostSurfaceTokens(html: string, tokens: HostSurfaceTokens): string;
|
|
30
|
+
//# sourceMappingURL=host-theme.d.ts.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module features/mcp-ui/surfaces/host-theme
|
|
3
|
+
*
|
|
4
|
+
* Makes a surface follow the embedding host's LIVE theme. A surface is a sandboxed document that
|
|
5
|
+
* cannot inherit the host's CSS (see `tokens.ts`), so without this every card wore Jini's neutral
|
|
6
|
+
* default palette — a near-black accent on grey — whatever the host's brand (owner, 2026-10-13:
|
|
7
|
+
* "every chat card should follow the theme color. It shouldn't be that ugly gray").
|
|
8
|
+
*
|
|
9
|
+
* The host side reads Jini's standard theme variables (`variables.css`'s `--jini-*`, which a host
|
|
10
|
+
* brands through `--jini-theme-*` inputs) where the card is mounted, and {@link applyHostSurfaceTokens}
|
|
11
|
+
* writes them into the document as a second `:root` rule right after the surface's token block. It
|
|
12
|
+
* rewrites the document at render time rather than asking the View to apply a value it was sent,
|
|
13
|
+
* so a card saved in an old chat — whose stored HTML predates this module — follows the theme too.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Which host variable each surface token follows. Radius and fonts are absent on purpose: a sandboxed
|
|
17
|
+
* frame has no network, so a host's web font cannot load there, and the surface's radius scale is
|
|
18
|
+
* its own. Text on an accent fill follows the host's primary ink, so a card's primary button
|
|
19
|
+
* reads exactly as legibly as the host's own.
|
|
20
|
+
*/
|
|
21
|
+
const HOST_THEME_SOURCES = {
|
|
22
|
+
'--jini-mcpui-bg': '--jini-bg',
|
|
23
|
+
'--jini-mcpui-panel': '--jini-surface',
|
|
24
|
+
'--jini-mcpui-border': '--jini-border',
|
|
25
|
+
'--jini-mcpui-border-strong': '--jini-border',
|
|
26
|
+
'--jini-mcpui-text': '--jini-text',
|
|
27
|
+
'--jini-mcpui-text-strong': '--jini-text',
|
|
28
|
+
'--jini-mcpui-text-muted': '--jini-muted',
|
|
29
|
+
'--jini-mcpui-text-soft': '--jini-muted',
|
|
30
|
+
'--jini-mcpui-text-faint': '--jini-muted',
|
|
31
|
+
'--jini-mcpui-accent': '--jini-primary',
|
|
32
|
+
'--jini-mcpui-accent-strong': '--jini-primary',
|
|
33
|
+
'--jini-mcpui-accent-ink': '--jini-primary-ink',
|
|
34
|
+
'--jini-mcpui-danger': '--jini-danger',
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* A value that could close the `<style>` it is written into, or end its declaration early, is
|
|
38
|
+
* dropped rather than escaped. Custom-property values accept almost any token, and the accent is an
|
|
39
|
+
* operator-typed setting, so `</style><script>` is a value the CSS parser would keep.
|
|
40
|
+
*/
|
|
41
|
+
const UNSAFE_VALUE = /[<>{};\\]/;
|
|
42
|
+
/**
|
|
43
|
+
* Reads the host's resolved theme at one element.
|
|
44
|
+
*
|
|
45
|
+
* @param style - The element's computed style (`getComputedStyle(el)`), where `--jini-*` resolve
|
|
46
|
+
* through every `var()` the host's theme inputs pass through.
|
|
47
|
+
* @returns The surface tokens to override. A variable the host leaves undeclared (or declares with
|
|
48
|
+
* an unsafe value) is omitted, so the surface keeps its own default for it.
|
|
49
|
+
*/
|
|
50
|
+
export function readHostSurfaceTokens(style) {
|
|
51
|
+
const tokens = {};
|
|
52
|
+
for (const [token, source] of Object.entries(HOST_THEME_SOURCES)) {
|
|
53
|
+
const value = style.getPropertyValue(source).trim();
|
|
54
|
+
if (value !== '' && !UNSAFE_VALUE.test(value))
|
|
55
|
+
tokens[token] = value;
|
|
56
|
+
}
|
|
57
|
+
return tokens;
|
|
58
|
+
}
|
|
59
|
+
/** Marks the rule {@link applyHostSurfaceTokens} writes. */
|
|
60
|
+
const HOST_THEME_STYLE_ATTRIBUTE = 'data-jini-host-theme';
|
|
61
|
+
/**
|
|
62
|
+
* Writes host tokens into a surface document.
|
|
63
|
+
*
|
|
64
|
+
* The rule is plain `:root` placed after the document's first `<style>`, so it beats the token
|
|
65
|
+
* block's defaults by source order; a builder's own `tokens` override is emitted as `:root:root`
|
|
66
|
+
* (`renderTokenBlock`), so a card that set its own colors keeps them. Documents carrying no
|
|
67
|
+
* `--jini-mcpui-` token at all are returned unchanged: a third-party View declares none of these
|
|
68
|
+
* names, and its HTML is not this module's to edit.
|
|
69
|
+
*
|
|
70
|
+
* @param html - The surface document.
|
|
71
|
+
* @param tokens - From {@link readHostSurfaceTokens}.
|
|
72
|
+
* @returns The document with the host rule inserted, or `html` itself when there is nothing to do.
|
|
73
|
+
*/
|
|
74
|
+
export function applyHostSurfaceTokens(html, tokens) {
|
|
75
|
+
const entries = Object.entries(tokens);
|
|
76
|
+
if (entries.length === 0 || !html.includes('--jini-mcpui-'))
|
|
77
|
+
return html;
|
|
78
|
+
const close = html.indexOf('</style>');
|
|
79
|
+
if (close === -1)
|
|
80
|
+
return html;
|
|
81
|
+
const at = close + '</style>'.length;
|
|
82
|
+
const rule = `\n<style ${HOST_THEME_STYLE_ATTRIBUTE}>\n:root {\n${entries.map(([name, value]) => ` ${name}: ${value};`).join('\n')}\n}\n</style>`;
|
|
83
|
+
return `${html.slice(0, at)}${rule}${html.slice(at)}`;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=host-theme.js.map
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
* because all four resolve through it.
|
|
19
19
|
*
|
|
20
20
|
* The sandbox cannot inherit the host's CSS. These names are a document adapter of
|
|
21
|
-
* defaultAdminTheme;
|
|
21
|
+
* defaultAdminTheme; the React host forwards the active theme into every surface it mounts
|
|
22
|
+
* (`host-theme.ts`), and a builder's `tokens` overrides outrank that.
|
|
22
23
|
*
|
|
23
24
|
* ## Dark mode
|
|
24
25
|
*
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
*
|
|
33
34
|
* Currently moot: {@link FORCE_LIGHT_SURFACE_THEME} suppresses the dark block entirely, because
|
|
34
35
|
* `prefers-color-scheme` reads the OS/browser, not any real app theme — see that constant's own doc.
|
|
36
|
+
* The host's live theme, dark or light, arrives through `host-theme.ts` instead.
|
|
35
37
|
*/
|
|
36
38
|
/**
|
|
37
39
|
* The base palette, radius scale, and font stack.
|
|
@@ -52,6 +54,7 @@ export declare const SURFACE_TOKENS: {
|
|
|
52
54
|
readonly '--jini-mcpui-text-faint': string;
|
|
53
55
|
readonly '--jini-mcpui-accent': string;
|
|
54
56
|
readonly '--jini-mcpui-accent-strong': string;
|
|
57
|
+
readonly '--jini-mcpui-accent-ink': string;
|
|
55
58
|
readonly '--jini-mcpui-danger': string;
|
|
56
59
|
readonly '--jini-mcpui-radius-xs': "4px";
|
|
57
60
|
readonly '--jini-mcpui-radius-sm': "6px";
|
|
@@ -77,12 +80,11 @@ export declare const SURFACE_TOKENS_DARK: Record<SurfaceTokenName, string>;
|
|
|
77
80
|
* Whether every generated surface is pinned to the light palette regardless of the embedding
|
|
78
81
|
* browser/OS's `prefers-color-scheme`.
|
|
79
82
|
*
|
|
80
|
-
* True by default because
|
|
81
|
-
* surface that
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* {@link renderTokenBlock} needs to change.
|
|
83
|
+
* True by default because `prefers-color-scheme` reads the operator's OS, not any app theme, and a
|
|
84
|
+
* surface that honored it produced a dark card floating in an always-light admin UI. The real app
|
|
85
|
+
* theme reaches a surface another way: the React host writes the host's resolved `--jini-*` values
|
|
86
|
+
* into the document at render time (`host-theme.ts`), so a host rendering in dark gets a dark card
|
|
87
|
+
* through that rule, and this pin only decides what a surface shows with no host theme at all.
|
|
86
88
|
*/
|
|
87
89
|
export declare const FORCE_LIGHT_SURFACE_THEME = true;
|
|
88
90
|
/**
|
|
@@ -95,8 +97,8 @@ export declare const FORCE_LIGHT_SURFACE_THEME = true;
|
|
|
95
97
|
* verbatim — a caller passing a token value is trusted the same way it is trusted with the surface's
|
|
96
98
|
* own body HTML. A caller wanting scheme-specific values is not served by this parameter; it exists
|
|
97
99
|
* for a host's brand tokens, which by design apply the same way regardless of scheme.
|
|
98
|
-
* @returns A `:root { … }` block,
|
|
99
|
-
* block unless {@link FORCE_LIGHT_SURFACE_THEME} suppresses it, ready to paste into the first
|
|
100
|
+
* @returns A `:root { … }` block, then a `:root:root { … }` block holding the overrides (when any),
|
|
101
|
+
* followed by a `@media (prefers-color-scheme: dark) { :root { … } }` block unless {@link FORCE_LIGHT_SURFACE_THEME} suppresses it, ready to paste into the first
|
|
100
102
|
* `<style>` tag.
|
|
101
103
|
* @complexity O(n) in the number of tokens.
|
|
102
104
|
*/
|
|
@@ -19,7 +19,8 @@ import { defaultAdminTheme } from '../../../core/theme/default.js';
|
|
|
19
19
|
* because all four resolve through it.
|
|
20
20
|
*
|
|
21
21
|
* The sandbox cannot inherit the host's CSS. These names are a document adapter of
|
|
22
|
-
* defaultAdminTheme;
|
|
22
|
+
* defaultAdminTheme; the React host forwards the active theme into every surface it mounts
|
|
23
|
+
* (`host-theme.ts`), and a builder's `tokens` overrides outrank that.
|
|
23
24
|
*
|
|
24
25
|
* ## Dark mode
|
|
25
26
|
*
|
|
@@ -33,6 +34,7 @@ import { defaultAdminTheme } from '../../../core/theme/default.js';
|
|
|
33
34
|
*
|
|
34
35
|
* Currently moot: {@link FORCE_LIGHT_SURFACE_THEME} suppresses the dark block entirely, because
|
|
35
36
|
* `prefers-color-scheme` reads the OS/browser, not any real app theme — see that constant's own doc.
|
|
37
|
+
* The host's live theme, dark or light, arrives through `host-theme.ts` instead.
|
|
36
38
|
*/
|
|
37
39
|
/**
|
|
38
40
|
* The base palette, radius scale, and font stack.
|
|
@@ -53,6 +55,8 @@ export const SURFACE_TOKENS = {
|
|
|
53
55
|
'--jini-mcpui-text-faint': defaultAdminTheme.light.muted,
|
|
54
56
|
'--jini-mcpui-accent': defaultAdminTheme.light.primary,
|
|
55
57
|
'--jini-mcpui-accent-strong': defaultAdminTheme.light.primary,
|
|
58
|
+
// Text on an accent fill — the host's primary ink, so a card's primary button reads like the host's own.
|
|
59
|
+
'--jini-mcpui-accent-ink': defaultAdminTheme.light.primaryInk,
|
|
56
60
|
// Desaturated and earthy, in the same family as the accent rather than a saturated pure red — a
|
|
57
61
|
// destructive action should read as serious, not as an alarm the eye learns to discount.
|
|
58
62
|
'--jini-mcpui-danger': defaultAdminTheme.light.danger,
|
|
@@ -85,6 +89,7 @@ export const SURFACE_TOKENS_DARK = {
|
|
|
85
89
|
'--jini-mcpui-text-faint': defaultAdminTheme.dark.muted,
|
|
86
90
|
'--jini-mcpui-accent': defaultAdminTheme.dark.primary,
|
|
87
91
|
'--jini-mcpui-accent-strong': defaultAdminTheme.dark.primary,
|
|
92
|
+
'--jini-mcpui-accent-ink': defaultAdminTheme.dark.primaryInk,
|
|
88
93
|
'--jini-mcpui-danger': defaultAdminTheme.dark.danger,
|
|
89
94
|
'--jini-mcpui-radius-xs': SURFACE_TOKENS['--jini-mcpui-radius-xs'],
|
|
90
95
|
'--jini-mcpui-radius-sm': SURFACE_TOKENS['--jini-mcpui-radius-sm'],
|
|
@@ -123,12 +128,11 @@ const DARK_SURFACE_SHADOW = '0 1px 2px color-mix(in srgb, var(--jini-mcpui-bg) 3
|
|
|
123
128
|
* Whether every generated surface is pinned to the light palette regardless of the embedding
|
|
124
129
|
* browser/OS's `prefers-color-scheme`.
|
|
125
130
|
*
|
|
126
|
-
* True by default because
|
|
127
|
-
* surface that
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* {@link renderTokenBlock} needs to change.
|
|
131
|
+
* True by default because `prefers-color-scheme` reads the operator's OS, not any app theme, and a
|
|
132
|
+
* surface that honored it produced a dark card floating in an always-light admin UI. The real app
|
|
133
|
+
* theme reaches a surface another way: the React host writes the host's resolved `--jini-*` values
|
|
134
|
+
* into the document at render time (`host-theme.ts`), so a host rendering in dark gets a dark card
|
|
135
|
+
* through that rule, and this pin only decides what a surface shows with no host theme at all.
|
|
132
136
|
*/
|
|
133
137
|
export const FORCE_LIGHT_SURFACE_THEME = true;
|
|
134
138
|
/**
|
|
@@ -141,22 +145,27 @@ export const FORCE_LIGHT_SURFACE_THEME = true;
|
|
|
141
145
|
* verbatim — a caller passing a token value is trusted the same way it is trusted with the surface's
|
|
142
146
|
* own body HTML. A caller wanting scheme-specific values is not served by this parameter; it exists
|
|
143
147
|
* for a host's brand tokens, which by design apply the same way regardless of scheme.
|
|
144
|
-
* @returns A `:root { … }` block,
|
|
145
|
-
* block unless {@link FORCE_LIGHT_SURFACE_THEME} suppresses it, ready to paste into the first
|
|
148
|
+
* @returns A `:root { … }` block, then a `:root:root { … }` block holding the overrides (when any),
|
|
149
|
+
* followed by a `@media (prefers-color-scheme: dark) { :root { … } }` block unless {@link FORCE_LIGHT_SURFACE_THEME} suppresses it, ready to paste into the first
|
|
146
150
|
* `<style>` tag.
|
|
147
151
|
* @complexity O(n) in the number of tokens.
|
|
148
152
|
*/
|
|
149
153
|
export function renderTokenBlock(overrides = {}) {
|
|
150
154
|
const declare = (entries) => entries.map(([name, value]) => ` ${name}: ${value};`).join('\n');
|
|
155
|
+
const notOverridden = ([name]) => !(name in overrides);
|
|
151
156
|
const light = declare([
|
|
152
|
-
...Object.entries(
|
|
157
|
+
...Object.entries(SURFACE_TOKENS).filter(notOverridden),
|
|
153
158
|
...Object.entries(DERIVED_TOKENS),
|
|
154
159
|
]);
|
|
155
|
-
|
|
160
|
+
// Overrides get their own, more specific rule so they outrank the host theme's plain `:root` rule
|
|
161
|
+
// (`host-theme.ts`) in both schemes: a card that chose its own colors keeps them.
|
|
162
|
+
const overrideEntries = Object.entries(overrides);
|
|
163
|
+
const overrideBlock = overrideEntries.length === 0 ? '' : `\n:root:root {\n${declare(overrideEntries)}\n}`;
|
|
164
|
+
const lightBlock = `:root {\n${light}\n}${overrideBlock}`;
|
|
156
165
|
if (FORCE_LIGHT_SURFACE_THEME)
|
|
157
166
|
return lightBlock;
|
|
158
167
|
const dark = declare([
|
|
159
|
-
...Object.entries(
|
|
168
|
+
...Object.entries(SURFACE_TOKENS_DARK).filter(notOverridden),
|
|
160
169
|
['--jini-mcpui-surface-shadow', DARK_SURFACE_SHADOW],
|
|
161
170
|
]);
|
|
162
171
|
return `${lightBlock}\n@media (prefers-color-scheme: dark) {\n :root {\n${dark}\n }\n}`;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
/** Render YYYY-MM-DD HH:MM; hosts can select the viewer's local timezone explicitly. */
|
|
7
7
|
export function formatTimestamp({ iso }, { timeZone = "stored" } = {}) {
|
|
8
8
|
if (timeZone === "local") {
|
|
9
|
-
// A stored UTC prefix can label server time as viewer time (
|
|
9
|
+
// A stored UTC prefix can label server time as viewer time (host issue D-06). Local getters
|
|
10
10
|
// follow the browser's timezone and DST; zone-less ISO is already local wall time.
|
|
11
11
|
const date = new Date(iso);
|
|
12
12
|
if (Number.isNaN(date.getTime()))
|
|
@@ -9,8 +9,15 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
9
9
|
*
|
|
10
10
|
* Thin on purpose, same as before — every protocol decision lives in `useMcpUiHost.ts`, so a
|
|
11
11
|
* consumer needing different chrome writes that chrome against the hook rather than forking this.
|
|
12
|
+
*
|
|
13
|
+
* It also themes the View: the host's live `--jini-*` theme is written into a Jini-built surface
|
|
14
|
+
* document before it is handed over (`surfaces/host-theme.ts`), and a theme change starts a fresh
|
|
15
|
+
* session with the re-themed document — the same remount any changed document gets.
|
|
12
16
|
*/
|
|
13
17
|
import { AppRenderer } from '@mcp-ui/client';
|
|
18
|
+
import { useRef } from 'react';
|
|
19
|
+
import { applyHostSurfaceTokens } from '../../features/mcp-ui/surfaces/host-theme.js';
|
|
20
|
+
import { useHostSurfaceTokens } from './useHostSurfaceTokens.js';
|
|
14
21
|
import { useMcpUiHost } from './useMcpUiHost.js';
|
|
15
22
|
const DEFAULT_INITIAL_HEIGHT = 220;
|
|
16
23
|
const DEFAULT_MAX_HEIGHT = 720;
|
|
@@ -21,14 +28,20 @@ const DEFAULT_MAX_HEIGHT = 720;
|
|
|
21
28
|
*/
|
|
22
29
|
export function McpUiHost(props) {
|
|
23
30
|
const { title, className, initialHeight = DEFAULT_INITIAL_HEIGHT, autoResize = true, maxHeight = DEFAULT_MAX_HEIGHT } = props;
|
|
24
|
-
const
|
|
31
|
+
const wrapperRef = useRef(null);
|
|
32
|
+
const hostTokens = useHostSurfaceTokens(wrapperRef);
|
|
33
|
+
const html = hostTokens === undefined ? props.html : applyHostSurfaceTokens(props.html, hostTokens);
|
|
34
|
+
// Only a document the theme actually changed takes the theme into its key, so a third-party View
|
|
35
|
+
// (left untouched) never remounts when the host's theme moves.
|
|
36
|
+
const sessionKey = `${String(props.sessionKey ?? props.html)}${html === props.html ? '' : JSON.stringify(hostTokens)}`;
|
|
37
|
+
const host = useMcpUiHost({ ...props, html, sessionKey });
|
|
25
38
|
const reported = host.size?.height;
|
|
26
39
|
const height = autoResize && reported !== undefined ? Math.min(Math.max(reported, 1), maxHeight) : initialHeight;
|
|
27
|
-
return (_jsx("div", { className: className, "data-mcpui-host": "", "data-mcpui-state": host.state, "aria-label": title, style: { display: 'block', width: '100%', height }, children: _jsx(AppRenderer
|
|
40
|
+
return (_jsx("div", { ref: wrapperRef, className: className, "data-mcpui-host": "", "data-mcpui-state": host.state, "aria-label": title, style: { display: 'block', width: '100%', height }, children: hostTokens !== undefined && _jsx(AppRenderer
|
|
28
41
|
// Keyed by the session so replacing the document remounts `AppRenderer` — a fresh
|
|
29
42
|
// `AppBridge`, a fresh sandbox iframe, a fresh handshake. `AppRenderer` itself provides no
|
|
30
43
|
// "replace the document in place" mode; remount via `key` is the supported way to start a
|
|
31
44
|
// new session, per `@mcp-ui/client`'s own docs.
|
|
32
|
-
, { ref: host.rendererRef, ...host.rendererProps },
|
|
45
|
+
, { ref: host.rendererRef, ...host.rendererProps }, sessionKey) }));
|
|
33
46
|
}
|
|
34
47
|
//# sourceMappingURL=McpUiHost.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module react/mcp-ui/useHostSurfaceTokens
|
|
3
|
+
*
|
|
4
|
+
* The live half of `features/mcp-ui/surfaces/host-theme.ts`: reads the host's resolved `--jini-*`
|
|
5
|
+
* theme where a surface is mounted, and reads it again whenever the theme can have changed, so an
|
|
6
|
+
* accent picked in a host's appearance settings reaches the cards already on screen.
|
|
7
|
+
*/
|
|
8
|
+
import { type RefObject } from 'react';
|
|
9
|
+
import { type HostSurfaceTokens } from '../../features/mcp-ui/surfaces/host-theme.js';
|
|
10
|
+
/**
|
|
11
|
+
* @param ref - The element the surface is mounted in; its computed style is where the host theme is read.
|
|
12
|
+
* @returns The host tokens, or `undefined` until the first read — a caller holds the surface back
|
|
13
|
+
* until then, so a themed card never mounts first in the default palette and then again in the host's.
|
|
14
|
+
*/
|
|
15
|
+
export declare function useHostSurfaceTokens(ref: RefObject<HTMLElement | null>): HostSurfaceTokens | undefined;
|
|
16
|
+
//# sourceMappingURL=useHostSurfaceTokens.d.ts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module react/mcp-ui/useHostSurfaceTokens
|
|
3
|
+
*
|
|
4
|
+
* The live half of `features/mcp-ui/surfaces/host-theme.ts`: reads the host's resolved `--jini-*`
|
|
5
|
+
* theme where a surface is mounted, and reads it again whenever the theme can have changed, so an
|
|
6
|
+
* accent picked in a host's appearance settings reaches the cards already on screen.
|
|
7
|
+
*/
|
|
8
|
+
import { useLayoutEffect, useState } from 'react';
|
|
9
|
+
import { readHostSurfaceTokens } from '../../features/mcp-ui/surfaces/host-theme.js';
|
|
10
|
+
/**
|
|
11
|
+
* The attributes a host switches a theme with: an inline `--jini-theme-*` input (`style`), a theme
|
|
12
|
+
* class, or one of the scheme attributes `variables.css` keys on. Watched on every ancestor, not the
|
|
13
|
+
* whole subtree — a streaming transcript changes attributes constantly, and none of those can move
|
|
14
|
+
* a value this card inherits.
|
|
15
|
+
*/
|
|
16
|
+
const THEME_ATTRIBUTES = ['style', 'class', 'data-theme', 'data-color-scheme', 'data-admin-theme'];
|
|
17
|
+
/**
|
|
18
|
+
* @param ref - The element the surface is mounted in; its computed style is where the host theme is read.
|
|
19
|
+
* @returns The host tokens, or `undefined` until the first read — a caller holds the surface back
|
|
20
|
+
* until then, so a themed card never mounts first in the default palette and then again in the host's.
|
|
21
|
+
*/
|
|
22
|
+
export function useHostSurfaceTokens(ref) {
|
|
23
|
+
const [tokens, setTokens] = useState();
|
|
24
|
+
useLayoutEffect(() => {
|
|
25
|
+
const element = ref.current;
|
|
26
|
+
const view = element?.ownerDocument.defaultView;
|
|
27
|
+
if (element === null || element === undefined || view === null || view === undefined) {
|
|
28
|
+
setTokens({});
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
let last;
|
|
32
|
+
const read = () => {
|
|
33
|
+
const next = readHostSurfaceTokens(view.getComputedStyle(element));
|
|
34
|
+
const key = JSON.stringify(next);
|
|
35
|
+
if (key === last)
|
|
36
|
+
return;
|
|
37
|
+
last = key;
|
|
38
|
+
setTokens(next);
|
|
39
|
+
};
|
|
40
|
+
read();
|
|
41
|
+
const observer = new view.MutationObserver(read);
|
|
42
|
+
for (let node = element.parentElement; node !== null; node = node.parentElement) {
|
|
43
|
+
observer.observe(node, { attributes: true, attributeFilter: THEME_ATTRIBUTES });
|
|
44
|
+
}
|
|
45
|
+
return () => observer.disconnect();
|
|
46
|
+
}, [ref]);
|
|
47
|
+
return tokens;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=useHostSurfaceTokens.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/ui",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Generic, product-neutral React UI primitives and feature domains. Framework-free types, decision rules, catalogs, and host port contracts live behind ./core (no React, no DOM). The Excalidraw and Lexical editors live behind ./sketch-editor and ./lexical-rich-text-editor; the GrapesJS-backed inline HTML editor lives behind ./html-editor.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -48,6 +48,9 @@
|
|
|
48
48
|
"./fetch-query/tanstack": "panel-kit"
|
|
49
49
|
},
|
|
50
50
|
"shared": [
|
|
51
|
+
"src/features/charts",
|
|
52
|
+
"src/features/interactive-ui/registry.ts",
|
|
53
|
+
"src/features/interactive-ui/table-data.ts",
|
|
51
54
|
"src/theme/default.ts",
|
|
52
55
|
"src/theme/stylesheet.ts",
|
|
53
56
|
"src/theme/validation.ts",
|
|
@@ -233,10 +236,10 @@
|
|
|
233
236
|
"shiki": "^1.24.4",
|
|
234
237
|
"tailwind-merge": "^2.6.0",
|
|
235
238
|
"zod": "^3.25.76",
|
|
236
|
-
"@jini-ai/platform": "^0.5.6",
|
|
237
|
-
"@jini-ai/agentic": "^0.4.4",
|
|
238
239
|
"@jini-ai/core": "^0.4.5",
|
|
239
|
-
"@jini-ai/
|
|
240
|
+
"@jini-ai/platform": "^0.5.6",
|
|
241
|
+
"@jini-ai/integrations": "^0.4.2",
|
|
242
|
+
"@jini-ai/agentic": "^0.4.4"
|
|
240
243
|
},
|
|
241
244
|
"dependencies": {
|
|
242
245
|
"@jini-ai/core": "^0.4.5"
|