@jini-ai/ui 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # @jini-ai/ui
2
2
 
3
+ ## 0.5.1 — 2026-10-13
4
+
5
+ - 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).
6
+ - HTML viewer imports `renderers/new-tab-preview` and `renderers/sandbox-bridge` directly instead of the renderer barrel (no Markdown renderers or srcdoc assembly in its closure).
7
+ - New universal `./i18n` subpath re-exporting `interpolate` from the existing template-i18n owner.
8
+ - TanStack fetch-query adapter: replacement writes win at TanStack's save boundary, so an older read that settles after `replace()` can no longer overwrite it (hook data, loaders and `peek()` keep their public types).
9
+
3
10
  ## 0.5.0 — 2026-10-13
4
11
 
5
12
  - Add framework-neutral chart contracts, browser/table and React hosts, provider-defined kinds and legacy chart wire aliases. Extract Recharts implementations and peers into the private browser artifact; chart component export wrappers are removed (no external consumers found).
@@ -1,7 +1,7 @@
1
1
  import type { FullscreenPort, HtmlViewerDependencies, NewTabPreviewPort } from './ports.js';
2
2
  /** Real browser Fullscreen API binding. Every method is SSR-guarded (`typeof document === 'undefined'`). */
3
3
  export declare function createBrowserFullscreenPort(): FullscreenPort;
4
- /** Reuses `@jini-ai/ui`'s own `./renderers` subpath's `openSandboxedPreviewInNewTab` — the sandboxed-iframe core this whole slice is the first consumer of within this package. */
4
+ /** Reuses `renderers/new-tab-preview.ts`, the owner of sandboxed new-tab previews, without loading the renderer barrel. */
5
5
  export declare function createBrowserNewTabPreviewPort(): NewTabPreviewPort;
6
6
  export declare function createDefaultHtmlViewerDependencies(): HtmlViewerDependencies;
7
7
  export declare const defaultHtmlViewerDependencies: HtmlViewerDependencies;
@@ -1,4 +1,4 @@
1
- import { openSandboxedPreviewInNewTab } from '../../renderers/index.js';
1
+ import { openSandboxedPreviewInNewTab } from '../../renderers/new-tab-preview.js';
2
2
  /** Real browser Fullscreen API binding. Every method is SSR-guarded (`typeof document === 'undefined'`). */
3
3
  export function createBrowserFullscreenPort() {
4
4
  return {
@@ -28,7 +28,7 @@ export function createBrowserFullscreenPort() {
28
28
  },
29
29
  };
30
30
  }
31
- /** Reuses `@jini-ai/ui`'s own `./renderers` subpath's `openSandboxedPreviewInNewTab` — the sandboxed-iframe core this whole slice is the first consumer of within this package. */
31
+ /** Reuses `renderers/new-tab-preview.ts`, the owner of sandboxed new-tab previews, without loading the renderer barrel. */
32
32
  export function createBrowserNewTabPreviewPort() {
33
33
  return {
34
34
  openInNewTab(html, title) {
@@ -14,8 +14,8 @@ export interface DeckNavigationController {
14
14
  goTo(index: number): void;
15
15
  }
16
16
  /**
17
- * Drives deck/slide navigation over `@jini-ai/ui`'s own `./renderers`
18
- * subpath's `useSandboxBridge`, speaking the neutral `jini:deck-*` protocol from
17
+ * Drives deck/slide navigation over `renderers/sandbox-bridge.ts`'s
18
+ * `useSandboxBridge`, speaking the neutral `jini:deck-*` protocol from
19
19
  * `constants.ts`. The sandboxed content itself must implement the deck
20
20
  * framework that responds to these messages — this hook only sends
21
21
  * navigation requests and tracks the state the content reports back.
@@ -1,10 +1,10 @@
1
1
  import { useCallback, useMemo, useState } from 'react';
2
- import { useSandboxBridge } from '../../../../renderers/index.js';
2
+ import { useSandboxBridge } from '../../../../renderers/sandbox-bridge.js';
3
3
  import { DECK_NAVIGATE_MESSAGE_TYPE, DECK_STATE_MESSAGE_TYPE } from '../../constants.js';
4
4
  import { canGoNext, canGoPrev, parseDeckStateMessage, slideCounterLabel } from '../../rules.js';
5
5
  /**
6
- * Drives deck/slide navigation over `@jini-ai/ui`'s own `./renderers`
7
- * subpath's `useSandboxBridge`, speaking the neutral `jini:deck-*` protocol from
6
+ * Drives deck/slide navigation over `renderers/sandbox-bridge.ts`'s
7
+ * `useSandboxBridge`, speaking the neutral `jini:deck-*` protocol from
8
8
  * `constants.ts`. The sandboxed content itself must implement the deck
9
9
  * framework that responds to these messages — this hook only sends
10
10
  * navigation requests and tracks the state the content reports back.
@@ -0,0 +1,3 @@
1
+ /** Universal template helpers; implementation stays with the existing template-i18n owner. */
2
+ export { interpolate } from './template-i18n.js';
3
+ //# sourceMappingURL=universal.d.ts.map
@@ -0,0 +1,3 @@
1
+ /** Universal template helpers; implementation stays with the existing template-i18n owner. */
2
+ export { interpolate } from './template-i18n.js';
3
+ //# sourceMappingURL=universal.js.map
@@ -7,7 +7,7 @@
7
7
  import * as React from 'react';
8
8
  import { type VariantProps } from 'class-variance-authority';
9
9
  declare const buttonVariants: (props?: ({
10
- variant?: "link" | "default" | "outline" | "ghost" | "destructive" | "secondary" | null | undefined;
10
+ variant?: "link" | "default" | "ghost" | "outline" | "destructive" | "secondary" | null | undefined;
11
11
  size?: "default" | "icon" | "sm" | "lg" | null | undefined;
12
12
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
13
13
  export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  /** Optional TanStack translation layer. No library types escape the fetch-query contract. */
3
3
  import { createContext, useCallback, useContext, useEffect, useMemo, useSyncExternalStore } from 'react';
4
- import { isCancelledError, QueryClient, QueryClientProvider, useMutation, useQuery } from '@tanstack/react-query';
4
+ import { isCancelledError, replaceEqualDeep, QueryClient, QueryClientProvider, useMutation, useQuery } from '@tanstack/react-query';
5
5
  import { DEFAULT_STALE_TIME, FetchQueryCache, IDLE_RETENTION, toQueryError } from '@jini-ai/core/fetch';
6
6
  import { browserFetchQueryEnvironment } from './environment.browser.js';
7
7
  import { resolveFetchQueryError, resolveFetchQueryStatus } from './adapter.react.js';
@@ -64,8 +64,8 @@ function historyFor({ scope, query }) {
64
64
  }
65
65
  return history;
66
66
  }
67
- /** Replacement writes win over either outcome of an older transport, across all loaders
68
- * and reactive reads. Weak keys follow TanStack's own query eviction lifecycle. */
67
+ /** Tag transport outcomes; final arbitration belongs to TanStack's synchronous save boundary.
68
+ * Weak query keys follow TanStack's own eviction lifecycle. */
69
69
  async function read({ scope, key, fetch }) {
70
70
  const query = scope.client.getQueryCache().find({ queryKey: key, exact: true });
71
71
  const history = historyFor({ scope, query });
@@ -77,21 +77,36 @@ async function read({ scope, key, fetch }) {
77
77
  return scope.client.getQueryData(key);
78
78
  if (value === undefined)
79
79
  throw new Error(`${JSON.stringify(key)} data is undefined`);
80
- if (request === history.request)
81
- history.error = null;
82
- return value;
80
+ return { generation, request, value };
83
81
  }
84
82
  catch (error) {
85
83
  if (generation !== history.replacement)
86
84
  return scope.client.getQueryData(key);
87
- // Earlier data remains useful, but a failed refresh is not a fresh cache hit.
88
- // The next loader/mount must retry even when its staleTime would retain that data.
89
- if (request === history.request) {
90
- history.error = toQueryError(error);
85
+ return { generation, request, error };
86
+ }
87
+ }
88
+ /** Structural sharing runs inside setData, immediately before publication. A check in read()
89
+ * alone leaves a promise-continuation gap, even when the retryer has already resolved.
90
+ * Current failures throw here; stale failures resolve to the newer replacement like stale data.
91
+ * @returns The accepted envelope, reusing TanStack's normal deep structural sharing.
92
+ */
93
+ function saveResult({ scope, key, previous, incoming }) {
94
+ const query = scope.client.getQueryCache().find({ queryKey: key, exact: true });
95
+ const history = historyFor({ scope, query });
96
+ const result = incoming;
97
+ if (result.generation !== history.replacement)
98
+ return scope.client.getQueryData(key);
99
+ if ('error' in result) {
100
+ // Earlier data stays useful, but a failed refresh is not a fresh cache hit.
101
+ if (result.request === history.request) {
102
+ history.error = toQueryError(result.error);
91
103
  query.invalidate();
92
104
  }
93
- throw error;
105
+ throw result.error;
94
106
  }
107
+ if (result.request === history.request)
108
+ history.error = null;
109
+ return replaceEqualDeep(previous, result);
95
110
  }
96
111
  /** Match the built-in cache's idle retention. TanStack's default gcTime (5 min) would
97
112
  * otherwise evict an unobserved entry before a longer staleTime says it is stale. */
@@ -102,7 +117,8 @@ function gcTimeFor({ staleTime }) {
102
117
  function useFetchQuery({ key, fetch }, { enabled = true, staleTime = DEFAULT_STALE_TIME, refetchOnWindowFocus = false } = {}) {
103
118
  const scope = useScope();
104
119
  const online = useOnline(scope);
105
- const query = useQuery({ queryKey: key, queryFn: () => read({ scope, key, fetch }), enabled: enabled && online, staleTime, gcTime: gcTimeFor({ staleTime }) }, scope.client);
120
+ const query = useQuery({ queryKey: key, queryFn: () => read({ scope, key, fetch }),
121
+ structuralSharing: (previous, incoming) => saveResult({ scope, key, previous, incoming }), enabled: enabled && online, staleTime, gcTime: gcTimeFor({ staleTime }) }, scope.client);
106
122
  const { refetch: refresh } = query;
107
123
  const refetch = useCallback(() => {
108
124
  if (scope.environment.isOnline()) {
@@ -127,9 +143,10 @@ function useFetchQuery({ key, fetch }, { enabled = true, staleTime = DEFAULT_STA
127
143
  // recovery, so a retry is never mistaken for evidence that the last failure is gone.
128
144
  const cached = scope.client.getQueryCache().find({ queryKey: key, exact: true });
129
145
  const history = historyFor({ scope, query: cached });
130
- const hasData = query.data !== undefined;
146
+ const data = query.data && 'value' in query.data ? query.data.value : undefined;
147
+ const hasData = data !== undefined;
131
148
  return {
132
- data: query.data,
149
+ data,
133
150
  status: resolveFetchQueryStatus({ disabled: !enabled, hasData, status: query.status }),
134
151
  error: resolveFetchQueryError({ error: history.error ?? query.error, disabled: !enabled, hasData }),
135
152
  isFetching: online && query.isFetching, refetch,
@@ -161,11 +178,13 @@ function useFetchMutation({ run }, { invalidates } = {}) {
161
178
  function useCachedLoader({ key, fetch }, { staleTime = DEFAULT_STALE_TIME } = {}) {
162
179
  const scope = useScope();
163
180
  return useMemo(() => {
164
- const options = { queryKey: key, queryFn: () => read({ scope, key, fetch }), staleTime, gcTime: gcTimeFor({ staleTime }) };
181
+ const options = { queryKey: key, queryFn: () => read({ scope, key, fetch }),
182
+ structuralSharing: (previous, incoming) => saveResult({ scope, key, previous, incoming }), staleTime, gcTime: gcTimeFor({ staleTime }) };
165
183
  return {
166
- peek: () => scope.client.getQueryData(key),
184
+ peek: () => scope.client.getQueryData(key)?.value,
167
185
  load: (required = { key, fetch }) => {
168
- const selected = { ...options, queryKey: required.key, queryFn: () => read({ scope, key: required.key, fetch: required.fetch }) };
186
+ const selected = { ...options, queryKey: required.key, queryFn: () => read({ scope, key: required.key, fetch: required.fetch }),
187
+ structuralSharing: (previous, incoming) => saveResult({ scope, key: required.key, previous, incoming }) };
169
188
  return scope.client.fetchQuery(selected).catch(error => {
170
189
  // A forced refresh can supersede a loader's request without aborting its transport.
171
190
  // Follow the new request instead of exposing TanStack's cancellation vocabulary.
@@ -177,6 +196,16 @@ function useCachedLoader({ key, fetch }, { staleTime = DEFAULT_STALE_TIME } = {}
177
196
  if (query.state.isInvalidated)
178
197
  return scope.client.fetchQuery(selected);
179
198
  return (query.promise ?? scope.client.getQueryData(required.key));
199
+ }).then(result => {
200
+ const query = scope.client.getQueryCache().find({ queryKey: required.key, exact: true });
201
+ const history = historyFor({ scope, query });
202
+ // Query.fetch returns the transport envelope, not structuralSharing's accepted value.
203
+ // Unwrap against the current generation so awaiting loaders agree with observers.
204
+ if (result.generation !== history.replacement)
205
+ return scope.client.getQueryData(required.key).value;
206
+ if ('error' in result)
207
+ throw result.error;
208
+ return result.value;
180
209
  });
181
210
  },
182
211
  replace: ({ value }) => {
@@ -184,7 +213,7 @@ function useCachedLoader({ key, fetch }, { staleTime = DEFAULT_STALE_TIME } = {}
184
213
  const history = historyFor({ scope, query });
185
214
  history.replacement += 1;
186
215
  history.error = null;
187
- scope.client.setQueryData(key, value);
216
+ scope.client.setQueryData(key, { generation: history.replacement, request: history.request, value });
188
217
  },
189
218
  };
190
219
  }, [scope, key, fetch, staleTime]);
@@ -1718,9 +1718,11 @@ select.jini-input {
1718
1718
  position: relative;
1719
1719
  }
1720
1720
 
1721
+ /* The right padding keeps the absolutely positioned copy button off the wrapped code; on a phone
1722
+ that much side padding would halve the line, so the code starts below the button instead. */
1721
1723
  .jini-snippet-pre {
1722
1724
  margin: 0;
1723
- padding: 14px 16px;
1725
+ padding: 14px 104px 14px 16px;
1724
1726
  overflow-x: auto;
1725
1727
  background: var(--jini-text-strong);
1726
1728
  border-radius: var(--jini-radius);
@@ -1748,6 +1750,12 @@ select.jini-input {
1748
1750
  background: color-mix(in srgb, var(--jini-text-strong) 75%, transparent);
1749
1751
  }
1750
1752
 
1753
+ @media (max-width: 640px) {
1754
+ .jini-snippet-pre {
1755
+ padding: 56px 16px 14px;
1756
+ }
1757
+ }
1758
+
1751
1759
  /* ============================================================
1752
1760
  Connectors tab — PORTED from OD's real source, not authored by eye.
1753
1761
  Sources: `foundry/integrations/open-design/reference/od-web-src.orig/
@@ -3585,13 +3593,21 @@ button.connector-action.is-loading { cursor: wait; }
3585
3593
  flex-wrap: wrap;
3586
3594
  }
3587
3595
 
3596
+ /* A wrapping row, not a fixed column grid: a card holds two or three fields (the model field only
3597
+ when the provider lists models) plus Clear, and the old three-column grid squeezed each input to
3598
+ a few characters on a phone and pushed Clear onto its own full-column row everywhere. Each field
3599
+ asks for 11rem, so a phone gets one field per line, wider screens fit the whole row, and the cap
3600
+ keeps a desktop field from stretching across the card. */
3588
3601
  .jini-media-provider-fields {
3589
- display: grid;
3590
- grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
3602
+ display: flex;
3603
+ flex-wrap: wrap;
3604
+ align-items: center;
3591
3605
  gap: 6px;
3592
3606
  }
3593
3607
 
3594
3608
  .jini-media-provider-field {
3609
+ flex: 1 1 11rem;
3610
+ max-width: 28rem;
3595
3611
  min-width: 0;
3596
3612
  }
3597
3613
 
@@ -1,4 +1,4 @@
1
- import type { KeyboardEvent as ReactKeyboardEvent } from "react";
1
+ import { type KeyboardEvent as ReactKeyboardEvent, type RefObject } from "react";
2
2
  import type { TabBarTab } from "./TabBar.js";
3
3
  /**
4
4
  * @file TabBar's derived props and WAI-ARIA automatic-activation keyboard behavior.
@@ -74,4 +74,19 @@ export declare function useTabBarKeyboard({ tabs, activeId, onChange }: {
74
74
  activeId: string;
75
75
  onChange: (id: string) => void;
76
76
  }, _optional?: Record<string, never>): TabBarKeyboardHandlers;
77
+ /**
78
+ * Keeps the selected tab visible when the strip scrolls sideways — a host may turn the wrapping
79
+ * row into one swipeable row on phones, and a deep link to a later tab (`?tab=…`) otherwise opens
80
+ * with its own label cut off at the edge. Only the strip's own `scrollLeft` moves: `scrollIntoView`
81
+ * would also scroll the page vertically to the strip, a jump on every tab change below the fold.
82
+ * A strip that wraps (no horizontal overflow) is left alone.
83
+ *
84
+ * @param input.activeId - The currently active tab's id; the strip re-checks when it changes.
85
+ * @returns A ref for the tablist container.
86
+ * @complexity O(n) per active-id change in the strip's tab count (one selector query).
87
+ * @example const stripRef = useActiveTabInView({ activeId });
88
+ */
89
+ export declare function useActiveTabInView({ activeId }: {
90
+ activeId: string;
91
+ }, _optional?: Record<string, never>): RefObject<HTMLDivElement | null>;
77
92
  //# sourceMappingURL=TabBar.hooks.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { useEffect, useRef } from "react";
1
2
  import { agentHandle } from "@jini-ai/agentic";
2
3
  /**
3
4
  * @file TabBar's derived props and WAI-ARIA automatic-activation keyboard behavior.
@@ -112,4 +113,34 @@ export function useTabBarKeyboard({ tabs, activeId, onChange }, _optional = {})
112
113
  }
113
114
  return { onKeyDown };
114
115
  }
116
+ /**
117
+ * Keeps the selected tab visible when the strip scrolls sideways — a host may turn the wrapping
118
+ * row into one swipeable row on phones, and a deep link to a later tab (`?tab=…`) otherwise opens
119
+ * with its own label cut off at the edge. Only the strip's own `scrollLeft` moves: `scrollIntoView`
120
+ * would also scroll the page vertically to the strip, a jump on every tab change below the fold.
121
+ * A strip that wraps (no horizontal overflow) is left alone.
122
+ *
123
+ * @param input.activeId - The currently active tab's id; the strip re-checks when it changes.
124
+ * @returns A ref for the tablist container.
125
+ * @complexity O(n) per active-id change in the strip's tab count (one selector query).
126
+ * @example const stripRef = useActiveTabInView({ activeId });
127
+ */
128
+ export function useActiveTabInView({ activeId }, _optional = {}) {
129
+ const stripRef = useRef(null);
130
+ useEffect(() => {
131
+ const strip = stripRef.current;
132
+ const tab = strip?.querySelector('[role="tab"][aria-selected="true"]');
133
+ if (!strip || !tab || strip.scrollWidth <= strip.clientWidth)
134
+ return;
135
+ const stripBox = strip.getBoundingClientRect();
136
+ const tabBox = tab.getBoundingClientRect();
137
+ // A host's end padding (e.g. room for an edge fade) counts as off-screen.
138
+ const visibleRight = stripBox.right - (parseFloat(getComputedStyle(strip).paddingRight) || 0);
139
+ if (tabBox.left < stripBox.left)
140
+ strip.scrollLeft += tabBox.left - stripBox.left;
141
+ else if (tabBox.right > visibleRight)
142
+ strip.scrollLeft += tabBox.right - visibleRight;
143
+ }, [activeId]);
144
+ return stripRef;
145
+ }
115
146
  //# sourceMappingURL=TabBar.hooks.js.map
@@ -1,7 +1,7 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { TabStripFrame } from "./TabStripFrame.js";
3
3
  import { agentHandle } from "@jini-ai/agentic";
4
- import { resolveTabBarTabIndex, tabHandleProps, useTabBarKeyboard } from "./TabBar.hooks.js";
4
+ import { resolveTabBarTabIndex, tabHandleProps, useActiveTabInView, useTabBarKeyboard } from "./TabBar.hooks.js";
5
5
  /** The visually-hidden accessible-name SUFFIX {@link TabBarTab.dot} adds — e.g. "GitHub Pages,
6
6
  * Connected", never "Connected GitHub Pages". Deliberately its own function rather than folded into
7
7
  * the visual dot span rendered before the label (`TabBarButton` below): the visual dot is
@@ -24,6 +24,7 @@ function TabBarButton({ tab, active, tabIndex, onChange, }) {
24
24
  /** Controlled button-based selection strip, including roving focus and optional agent handles. */
25
25
  export function TabBar({ tabs, activeId, onChange, ariaLabel, containerHandle }, _optional = {}) {
26
26
  const { onKeyDown } = useTabBarKeyboard({ tabs: tabs, activeId: activeId, onChange: onChange });
27
- return (_jsx(TabStripFrame, { className: "tab-bar", role: "tablist", "aria-label": ariaLabel, onKeyDown: onKeyDown, ...(containerHandle ? agentHandle({ handle: containerHandle }, { role: "region", label: ariaLabel }) : {}), children: tabs.map((tab) => (_jsx(TabBarButton, { tab: tab, active: activeId === tab.id, tabIndex: resolveTabBarTabIndex({ tabs: tabs, activeId: activeId, tab: tab }), onChange: onChange }, tab.id))) }));
27
+ const stripRef = useActiveTabInView({ activeId: activeId });
28
+ return (_jsx(TabStripFrame, { ref: stripRef, className: "tab-bar", role: "tablist", "aria-label": ariaLabel, onKeyDown: onKeyDown, ...(containerHandle ? agentHandle({ handle: containerHandle }, { role: "region", label: ariaLabel }) : {}), children: tabs.map((tab) => (_jsx(TabBarButton, { tab: tab, active: activeId === tab.id, tabIndex: resolveTabBarTabIndex({ tabs: tabs, activeId: activeId, tab: tab }), onChange: onChange }, tab.id))) }));
28
29
  }
29
30
  //# sourceMappingURL=TabBar.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jini-ai/ui",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
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": {
@@ -15,6 +15,7 @@
15
15
  "entries": {
16
16
  ".": "browser",
17
17
  "./core": "universal",
18
+ "./i18n": "universal",
18
19
  "./sketch-editor": "browser",
19
20
  "./lexical-rich-text-editor": "browser",
20
21
  "./html-editor": "browser",
@@ -135,6 +136,11 @@
135
136
  "import": "./dist/features/admin-widgets/index.js",
136
137
  "default": "./dist/features/admin-widgets/index.js"
137
138
  },
139
+ "./i18n": {
140
+ "types": "./dist/features/i18n/universal.d.ts",
141
+ "import": "./dist/features/i18n/universal.js",
142
+ "default": "./dist/features/i18n/universal.js"
143
+ },
138
144
  "./panel-kit": {
139
145
  "types": "./dist/features/panel-kit/index.d.ts",
140
146
  "import": "./dist/features/panel-kit/index.js",
@@ -227,13 +233,13 @@
227
233
  "shiki": "^1.24.4",
228
234
  "tailwind-merge": "^2.6.0",
229
235
  "zod": "^3.25.76",
230
- "@jini-ai/integrations": "^0.4.2",
231
236
  "@jini-ai/platform": "^0.5.6",
232
237
  "@jini-ai/agentic": "^0.4.4",
233
- "@jini-ai/core": "^0.4.4"
238
+ "@jini-ai/core": "^0.4.5",
239
+ "@jini-ai/integrations": "^0.4.2"
234
240
  },
235
241
  "dependencies": {
236
- "@jini-ai/core": "^0.4.4"
242
+ "@jini-ai/core": "^0.4.5"
237
243
  },
238
244
  "peerDependencies": {
239
245
  "@jini-ai/integrations": "^0.4.2",