@jini-ai/ui 0.4.7 → 0.4.9

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.
Files changed (128) hide show
  1. package/README.md +2 -0
  2. package/dist/features/admin-widgets/components/InfoTip.d.ts +23 -2
  3. package/dist/features/admin-widgets/components/InfoTip.hooks.d.ts +22 -2
  4. package/dist/features/admin-widgets/components/InfoTip.hooks.js +75 -28
  5. package/dist/features/admin-widgets/components/InfoTip.js +12 -4
  6. package/dist/features/admin-widgets/components/InfoTip.rules.d.ts +72 -0
  7. package/dist/features/admin-widgets/components/InfoTip.rules.js +68 -0
  8. package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.d.ts +36 -0
  9. package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.hooks.d.ts +58 -0
  10. package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.hooks.js +66 -0
  11. package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.js +24 -0
  12. package/dist/features/admin-widgets/components/useDebouncedHtmlPreview.hooks.d.ts +23 -0
  13. package/dist/features/admin-widgets/components/useDebouncedHtmlPreview.hooks.js +28 -0
  14. package/dist/features/admin-widgets/index.d.ts +9 -0
  15. package/dist/features/admin-widgets/index.js +5 -0
  16. package/dist/features/execution/constants.js +11 -10
  17. package/dist/features/execution/http-adapter.d.ts +3 -1
  18. package/dist/features/execution/http-adapter.js +9 -4
  19. package/dist/features/execution/index.d.ts +1 -1
  20. package/dist/features/execution/index.js +1 -1
  21. package/dist/features/execution/react/components/ByokProviderForm.d.ts +18 -1
  22. package/dist/features/execution/react/components/ByokProviderForm.js +14 -16
  23. package/dist/features/execution/react/components/LocalCliAgentCard.d.ts +5 -1
  24. package/dist/features/execution/react/components/LocalCliAgentCard.js +12 -87
  25. package/dist/features/execution/react/components/LocalCliAgentList.d.ts +4 -1
  26. package/dist/features/execution/react/components/LocalCliAgentList.js +13 -4
  27. package/dist/features/execution/react/components/LocalCliAgentSettings.d.ts +20 -0
  28. package/dist/features/execution/react/components/LocalCliAgentSettings.js +20 -0
  29. package/dist/features/execution/react/hooks/use-byok-field-validation.hooks.d.ts +24 -0
  30. package/dist/features/execution/react/hooks/use-byok-field-validation.hooks.js +27 -0
  31. package/dist/features/execution/react/hooks/useExecutionTab.js +10 -1
  32. package/dist/features/execution/react/hooks/useLocalCliAgentCard.d.ts +48 -0
  33. package/dist/features/execution/react/hooks/useLocalCliAgentCard.js +110 -0
  34. package/dist/features/execution/react/hooks/useLocalCliAgentList.d.ts +19 -0
  35. package/dist/features/execution/react/hooks/useLocalCliAgentList.js +13 -0
  36. package/dist/features/execution/rules.d.ts +13 -0
  37. package/dist/features/execution/rules.js +14 -0
  38. package/dist/features/execution/types.d.ts +2 -0
  39. package/dist/features/html-editor/canvas-embed-placeholders.d.ts +13 -1
  40. package/dist/features/html-editor/canvas-embed-placeholders.js +61 -12
  41. package/dist/features/html-editor/dom.d.ts +5 -0
  42. package/dist/features/html-editor/dom.js +10 -0
  43. package/dist/features/html-editor/editor-blocks.d.ts +71 -0
  44. package/dist/features/html-editor/editor-blocks.js +185 -0
  45. package/dist/features/html-editor/editor-serialization.d.ts +24 -0
  46. package/dist/features/html-editor/editor-serialization.js +110 -0
  47. package/dist/features/html-editor/index.d.ts +5 -0
  48. package/dist/features/html-editor/index.js +3 -0
  49. package/dist/features/html-editor/protected-elements.d.ts +64 -0
  50. package/dist/features/html-editor/protected-elements.js +189 -0
  51. package/dist/features/html-editor/react/components/InteractiveHtmlEditor.d.ts +6 -4
  52. package/dist/features/html-editor/react/components/InteractiveHtmlEditor.js +3 -3
  53. package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.d.ts +9 -1
  54. package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.js +107 -196
  55. package/dist/features/html-editor/source-splice.d.ts +23 -0
  56. package/dist/features/html-editor/source-splice.js +45 -0
  57. package/dist/features/i18n/dictionaries/settings-dialog.ar.js +4 -0
  58. package/dist/features/i18n/dictionaries/settings-dialog.bn.js +4 -0
  59. package/dist/features/i18n/dictionaries/settings-dialog.de.js +4 -0
  60. package/dist/features/i18n/dictionaries/settings-dialog.en.js +4 -0
  61. package/dist/features/i18n/dictionaries/settings-dialog.es.js +4 -0
  62. package/dist/features/i18n/dictionaries/settings-dialog.fa.js +4 -0
  63. package/dist/features/i18n/dictionaries/settings-dialog.fr.js +4 -0
  64. package/dist/features/i18n/dictionaries/settings-dialog.hi.js +4 -0
  65. package/dist/features/i18n/dictionaries/settings-dialog.hu.js +4 -0
  66. package/dist/features/i18n/dictionaries/settings-dialog.id.js +4 -0
  67. package/dist/features/i18n/dictionaries/settings-dialog.it.js +4 -0
  68. package/dist/features/i18n/dictionaries/settings-dialog.ja.js +4 -0
  69. package/dist/features/i18n/dictionaries/settings-dialog.ko.js +4 -0
  70. package/dist/features/i18n/dictionaries/settings-dialog.pl.js +4 -0
  71. package/dist/features/i18n/dictionaries/settings-dialog.pt-BR.js +4 -0
  72. package/dist/features/i18n/dictionaries/settings-dialog.ru.js +4 -0
  73. package/dist/features/i18n/dictionaries/settings-dialog.th.js +4 -0
  74. package/dist/features/i18n/dictionaries/settings-dialog.tr.js +4 -0
  75. package/dist/features/i18n/dictionaries/settings-dialog.uk.js +4 -0
  76. package/dist/features/i18n/dictionaries/settings-dialog.ur.js +4 -0
  77. package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +4 -0
  78. package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +4 -0
  79. package/dist/features/interactive-ui/interactive-ui.css +1 -1
  80. package/dist/features/interactive-ui/providers/recharts/bar-chart.js +1 -1
  81. package/dist/features/interactive-ui/providers/recharts/line-chart.js +1 -1
  82. package/dist/features/interactive-ui/providers/shadcn/button.d.ts +1 -1
  83. package/dist/features/mcp-ui/escape.js +2 -6
  84. package/dist/features/mcp-ui/surfaces/bridge.js +4 -2
  85. package/dist/features/mcp-ui/surfaces/document.d.ts +11 -4
  86. package/dist/features/mcp-ui/surfaces/document.js +25 -3
  87. package/dist/features/mcp-ui/surfaces/outcome.js +1 -1
  88. package/dist/features/media-providers/constants.d.ts +0 -7
  89. package/dist/features/media-providers/constants.js +7 -15
  90. package/dist/features/media-providers/react/components/MediaProvidersTab.js +3 -2
  91. package/dist/features/media-providers/react/hooks/useMediaProvidersTab.js +6 -3
  92. package/dist/features/memory/dependencies.d.ts +2 -0
  93. package/dist/features/memory/dependencies.js +9 -25
  94. package/dist/features/memory/index.d.ts +1 -0
  95. package/dist/features/memory/index.js +1 -0
  96. package/dist/features/memory/react/MemoryPorts.d.ts +17 -0
  97. package/dist/features/memory/react/MemoryPorts.js +15 -0
  98. package/dist/features/memory/react/hooks/useMemoryConfig.hooks.d.ts +2 -2
  99. package/dist/features/memory/react/hooks/useMemoryConfig.hooks.js +7 -7
  100. package/dist/features/memory/react/hooks/useMemoryConnectors.hooks.d.ts +2 -3
  101. package/dist/features/memory/react/hooks/useMemoryConnectors.hooks.js +4 -5
  102. package/dist/features/memory/react/hooks/useMemoryEntries.hooks.d.ts +2 -3
  103. package/dist/features/memory/react/hooks/useMemoryEntries.hooks.js +4 -5
  104. package/dist/features/memory/react/hooks/useMemoryExtractions.hooks.js +2 -2
  105. package/dist/features/panel-kit/fetch-query/adapter.react.d.ts +2 -2
  106. package/dist/features/panel-kit/fetch-query/adapter.react.js +2 -2
  107. package/dist/features/panel-kit/fetch-query/adapter.tanstack.d.ts +1 -1
  108. package/dist/features/panel-kit/fetch-query/adapter.tanstack.js +35 -13
  109. package/dist/features/panel-kit/fetch-query/binding.react.d.ts +1 -1
  110. package/dist/features/panel-kit/fetch-query/environment.browser.d.ts +1 -1
  111. package/dist/features/panel-kit/fetch-query/index.d.ts +4 -2
  112. package/dist/features/panel-kit/fetch-query/tanstack.d.ts +4 -2
  113. package/dist/features/panel-kit/helpers/standing-draft-recovery-message.d.ts +3 -1
  114. package/dist/features/panel-kit/helpers/standing-draft-recovery-message.js +9 -2
  115. package/dist/features/settings/dialog/styles/settings-dialog.css +169 -21
  116. package/dist/features/tabbed-dialog/styles/tabbed-dialog.css +91 -0
  117. package/dist/react/components/RemixIcon.d.ts +1 -41
  118. package/dist/react/components/RemixIcon.js +3 -176
  119. package/dist/react/components/RemixIcon.stylesheet.d.ts +84 -0
  120. package/dist/react/components/RemixIcon.stylesheet.js +208 -0
  121. package/dist/react/components/remixicon-font-recovery.d.ts +55 -0
  122. package/dist/react/components/remixicon-font-recovery.js +110 -0
  123. package/dist/styles/admin.css +8 -2
  124. package/package.json +12 -7
  125. package/dist/features/panel-kit/fetch-query/cache.d.ts +0 -125
  126. package/dist/features/panel-kit/fetch-query/cache.js +0 -232
  127. package/dist/features/panel-kit/fetch-query/types.d.ts +0 -93
  128. package/dist/features/panel-kit/fetch-query/types.js +0 -2
@@ -2,7 +2,7 @@ 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
4
  import { isCancelledError, QueryClient, QueryClientProvider, useMutation, useQuery } from '@tanstack/react-query';
5
- import { DEFAULT_STALE_TIME, FetchQueryCache, IDLE_RETENTION, toQueryError } from './cache.js';
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';
8
8
  import { FetchQueryBinding } from './binding.react.js';
@@ -106,13 +106,14 @@ function useFetchQuery({ key, fetch }, { enabled = true, staleTime = DEFAULT_STA
106
106
  const { refetch: refresh } = query;
107
107
  const refetch = useCallback(() => {
108
108
  if (scope.environment.isOnline()) {
109
+ supersedeInitialRead({ scope, key });
109
110
  void refresh();
110
111
  return;
111
112
  }
112
113
  // Reconnect may already have started a fetch (re-enabled observer or provider refetch);
113
114
  // that request postdates this one, so join it instead of cancelling it into a duplicate.
114
115
  void scope.gate.runOnline({ run: () => refresh({ cancelRefetch: false }) });
115
- }, [scope, refresh]);
116
+ }, [scope, refresh, JSON.stringify(key)]);
116
117
  useEffect(() => {
117
118
  if (!enabled || !refetchOnWindowFocus)
118
119
  return;
@@ -143,7 +144,7 @@ function useFetchMutation({ run }, { invalidates } = {}) {
143
144
  // Do not await dependent reads: the mutation promise means the write succeeded,
144
145
  // not that every list finished reloading. Refetches proceed on their own schedule.
145
146
  for (const key of invalidates ?? [])
146
- void scope.client.invalidateQueries({ queryKey: key });
147
+ invalidateResource({ scope, key });
147
148
  },
148
149
  }, scope.client);
149
150
  const { mutateAsync, reset } = mutation;
@@ -163,14 +164,21 @@ function useCachedLoader({ key, fetch }, { staleTime = DEFAULT_STALE_TIME } = {}
163
164
  const options = { queryKey: key, queryFn: () => read({ scope, key, fetch }), staleTime, gcTime: gcTimeFor({ staleTime }) };
164
165
  return {
165
166
  peek: () => scope.client.getQueryData(key),
166
- load: () => scope.client.fetchQuery(options).catch(error => {
167
- // A forced refresh can supersede a loader's request without aborting its transport.
168
- // Follow the new request instead of exposing TanStack's cancellation vocabulary.
169
- if (!isCancelledError(error))
170
- throw error;
171
- const query = scope.client.getQueryCache().find({ queryKey: key, exact: true });
172
- return (query.promise ?? scope.client.getQueryData(key));
173
- }),
167
+ load: (required = { key, fetch }) => {
168
+ const selected = { ...options, queryKey: required.key, queryFn: () => read({ scope, key: required.key, fetch: required.fetch }) };
169
+ return scope.client.fetchQuery(selected).catch(error => {
170
+ // A forced refresh can supersede a loader's request without aborting its transport.
171
+ // Follow the new request instead of exposing TanStack's cancellation vocabulary.
172
+ if (!isCancelledError(error))
173
+ throw error;
174
+ // Invalidation also supersedes loader-only reads. Join (or start) the post-write
175
+ // request rather than returning an obsolete value while the key is invalidated.
176
+ const query = scope.client.getQueryCache().find({ queryKey: required.key, exact: true });
177
+ if (query.state.isInvalidated)
178
+ return scope.client.fetchQuery(selected);
179
+ return (query.promise ?? scope.client.getQueryData(required.key));
180
+ });
181
+ },
174
182
  replace: ({ value }) => {
175
183
  const query = scope.client.getQueryCache().build(scope.client, options);
176
184
  const history = historyFor({ scope, query });
@@ -181,10 +189,24 @@ function useCachedLoader({ key, fetch }, { staleTime = DEFAULT_STALE_TIME } = {}
181
189
  };
182
190
  }, [scope, key, fetch, staleTime]);
183
191
  }
192
+ /** A host write/refresh postdates the initial GET as well as a background GET. TanStack
193
+ * normally joins an initial fetch with no data, which would let that pre-write snapshot win.
194
+ * Cancel its cache ownership before requesting the replacement; transports need no abort API. */
195
+ function supersedeInitialRead({ scope, key }, { exact = true } = {}) {
196
+ void scope.client.cancelQueries({ queryKey: key, exact, predicate: query => query.isActive() && query.state.data === undefined && query.state.fetchStatus === 'fetching' });
197
+ }
198
+ /** One invalidation path serves both imperative refreshes and accepted writes. */
199
+ function invalidateResource({ scope, key }, _optional = {}) {
200
+ // A loader-only detail request may feed an active parent query. It is just as stale
201
+ // after a write as an observed read, even though TanStack considers it inactive.
202
+ // Silent cancellation follows the replacement promise instead of resolving old cached data.
203
+ void scope.client.cancelQueries({ queryKey: key, predicate: query => query.state.fetchStatus === 'fetching' }, { silent: true });
204
+ void scope.client.invalidateQueries({ queryKey: key });
205
+ }
184
206
  /** Prefix invalidation also reaches child keys read by other mounted consumers. */
185
207
  function useInvalidate() {
186
- const { client } = useScope();
187
- return useCallback(({ key }) => { void client.invalidateQueries({ queryKey: key }); }, [client]);
208
+ const scope = useScope();
209
+ return useCallback(({ key }) => { invalidateResource({ scope, key }); }, [scope]);
188
210
  }
189
211
  const adapter = { useFetchQuery, useFetchMutation, useCachedLoader, useInvalidate };
190
212
  //# sourceMappingURL=adapter.tanstack.js.map
@@ -1,5 +1,5 @@
1
1
  import { type ReactNode } from 'react';
2
- import type { FetchQueryAdapter } from './types.js';
2
+ import type { FetchQueryAdapter } from '@jini-ai/core/fetch';
3
3
  /** Internal composition seam: hooks and imperative loaders always use the same provider.
4
4
  * Keep the adapter fixed for this provider's lifetime to preserve React hook ordering. */
5
5
  export declare function FetchQueryBinding({ adapter, children }: {
@@ -1,4 +1,4 @@
1
- import type { FetchQueryEnvironmentPort } from './cache.js';
1
+ import type { FetchQueryEnvironmentPort } from '@jini-ai/core/fetch';
2
2
  /** Browser-only adapter: subscriptions are owned by the provider effect, never module scope. */
3
3
  export declare const browserFetchQueryEnvironment: FetchQueryEnvironmentPort;
4
4
  //# sourceMappingURL=environment.browser.d.ts.map
@@ -1,5 +1,7 @@
1
1
  export { FetchQueryProvider } from './adapter.react.js';
2
2
  export { useFetchQuery, useFetchMutation, useCachedLoader, useInvalidate } from './binding.react.js';
3
- export type { FetchQueryEnvironmentPort } from './cache.js';
4
- export type { QueryKey, QueryStatus, QueryResult, FetchQueryOptions, MutationStatus, MutationResult, FetchMutationOptions, CachedLoaderOptions, CachedLoader, FetchQueryAdapter } from './types.js';
3
+ /** @deprecated Import the framework-neutral environment port from @jini-ai/core/fetch. */
4
+ export type { FetchQueryEnvironmentPort } from '@jini-ai/core/fetch';
5
+ /** @deprecated Import framework-neutral query contracts from @jini-ai/core/fetch. */
6
+ export type { QueryKey, QueryStatus, QueryResult, FetchQueryOptions, MutationStatus, MutationResult, FetchMutationOptions, CachedLoaderOptions, CachedLoader, FetchQueryAdapter } from '@jini-ai/core/fetch';
5
7
  //# sourceMappingURL=index.d.ts.map
@@ -1,6 +1,8 @@
1
1
  /** Optional adapter entry. Importing the default entry never loads TanStack. */
2
2
  export { FetchQueryProvider } from './adapter.tanstack.js';
3
3
  export { useFetchQuery, useFetchMutation, useCachedLoader, useInvalidate } from './binding.react.js';
4
- export type { FetchQueryEnvironmentPort } from './cache.js';
5
- export type { QueryKey, QueryStatus, QueryResult, FetchQueryOptions, MutationStatus, MutationResult, FetchMutationOptions, CachedLoaderOptions, CachedLoader, FetchQueryAdapter } from './types.js';
4
+ /** @deprecated Import the framework-neutral environment port from @jini-ai/core/fetch. */
5
+ export type { FetchQueryEnvironmentPort } from '@jini-ai/core/fetch';
6
+ /** @deprecated Import framework-neutral query contracts from @jini-ai/core/fetch. */
7
+ export type { QueryKey, QueryStatus, QueryResult, FetchQueryOptions, MutationStatus, MutationResult, FetchMutationOptions, CachedLoaderOptions, CachedLoader, FetchQueryAdapter } from '@jini-ai/core/fetch';
6
8
  //# sourceMappingURL=tanstack.d.ts.map
@@ -4,5 +4,7 @@ export declare function standingDraftRecoveryMessage({ draft, t, serverUpdatedAt
4
4
  draft: StandingDraftAutosaveSnapshot;
5
5
  t: (key: string) => string;
6
6
  serverUpdatedAt?: string;
7
- }, _options?: {}): string;
7
+ }, { formatSavedAt }?: {
8
+ formatSavedAt?: (iso: string) => string;
9
+ }): string;
8
10
  //# sourceMappingURL=standing-draft-recovery-message.d.ts.map
@@ -1,10 +1,17 @@
1
+ import { formatTimestamp } from "./format-timestamp.js";
2
+ /** The viewer's wall-clock time of the capture, `YYYY-MM-DD HH:MM`. The raw ISO string read as
3
+ * "from 2026-10-11T19:26:00.665Z" in the restore banner (2026-10-11): UTC and milliseconds the
4
+ * operator never thinks in. The draft was captured in this browser, so local time is the honest one. */
5
+ function formatSavedAtLocal(iso) {
6
+ return formatTimestamp({ iso }, { timeZone: "local" });
7
+ }
1
8
  /** Compare timestamps explicitly; restoration only changes the working copy, never the server. */
2
- export function standingDraftRecoveryMessage({ draft, t, serverUpdatedAt }, _options = {}) {
9
+ export function standingDraftRecoveryMessage({ draft, t, serverUpdatedAt }, { formatSavedAt = formatSavedAtLocal } = {}) {
3
10
  const captured = Date.parse(draft.savedAt);
4
11
  const server = Date.parse(serverUpdatedAt ?? draft.serverUpdatedAt ?? "");
5
12
  const comparison = server > captured ? "Server content is newer."
6
13
  : server < captured ? "Local changes are newer."
7
14
  : server === captured ? "Both copies have the same timestamp." : "";
8
- return `${t("Restore unsaved changes from")} ${draft.savedAt}${comparison ? ` · ${t(comparison)}` : ""}`;
15
+ return `${t("Restore unsaved changes from")} ${formatSavedAt(draft.savedAt)}${comparison ? ` · ${t(comparison)}` : ""}`;
9
16
  }
10
17
  //# sourceMappingURL=standing-draft-recovery-message.js.map
@@ -95,6 +95,8 @@
95
95
  }
96
96
 
97
97
  .jini-input {
98
+ min-height: var(--jini-control-h, 37px);
99
+ box-sizing: border-box;
98
100
  width: 100%;
99
101
  min-width: 0;
100
102
  padding: 8px 10px;
@@ -107,6 +109,17 @@
107
109
  transition: border-color 120ms var(--jini-ease-out), box-shadow 120ms var(--jini-ease-out);
108
110
  }
109
111
 
112
+ /* Native selects share one affordance across execution settings and host forms. */
113
+ select.jini-input {
114
+ appearance: none;
115
+ padding-right: 2rem;
116
+ cursor: pointer;
117
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%236b7280' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
118
+ background-repeat: no-repeat;
119
+ background-position: right 0.7rem center;
120
+ background-size: 10px 6px;
121
+ }
122
+
110
123
  .jini-input:focus {
111
124
  outline: none;
112
125
  border-color: var(--jini-accent);
@@ -127,6 +140,8 @@
127
140
  .jini-settings-section .jini-btn,
128
141
  .jini-settings-section .jini-button,
129
142
  .jini-settings-section a.jini-button {
143
+ min-height: var(--jini-control-h, 37px);
144
+ box-sizing: border-box;
130
145
  display: inline-flex;
131
146
  align-items: center;
132
147
  justify-content: center;
@@ -394,6 +409,21 @@
394
409
 
395
410
  `align-items: start` so a field with a two-line hint (an invalid base URL) does not stretch its
396
411
  neighbour's input to match. */
412
+ /* `ByokProviderForm`'s `collapseAdvanced`: Base URL + Max tokens behind one disclosure after Model. */
413
+ .jini-byok-advanced > summary {
414
+ cursor: pointer;
415
+ width: max-content;
416
+ font-size: 12px;
417
+ font-weight: 600;
418
+ color: var(--jini-text-muted);
419
+ }
420
+ .jini-byok-advanced > summary:hover { color: var(--jini-text-strong); }
421
+ .jini-byok-advanced[open] > summary { margin-bottom: 12px; }
422
+
423
+ /* `ByokProviderForm`'s `apiKeyActions`: the host's button row directly under the key input (Show
424
+ stays on the input's line). Wraps on a narrow card. */
425
+ .jini-byok-key-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 2px; }
426
+
397
427
  .jini-byok-field-row {
398
428
  display: flex;
399
429
  flex-wrap: wrap;
@@ -479,13 +509,25 @@
479
509
 
480
510
  /* Agent cards. The origin drew a responsive grid of selectable vendor cards;
481
511
  `auto-fill` keeps it one column inside a narrow settings panel and two or
482
- more when the host gives the dialog room. */
512
+ more when the host gives the dialog room.
513
+
514
+ Settings grow inside the selected card's own column; top alignment keeps
515
+ its neighbour at its normal height. */
483
516
  .jini-agent-grid {
484
517
  display: grid;
518
+ align-items: start;
485
519
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
486
520
  gap: 10px;
487
521
  }
488
522
 
523
+ /* "Show N not installed" — the uninstalled tail of the grid sits behind this toggle. */
524
+ .jini-local-cli-more {
525
+ display: block;
526
+ width: auto;
527
+ align-self: flex-start;
528
+ margin: 10px 0;
529
+ }
530
+
489
531
  .jini-agent-card {
490
532
  display: flex;
491
533
  flex-direction: column;
@@ -500,6 +542,7 @@
500
542
  }
501
543
 
502
544
  .jini-agent-card.is-selected {
545
+ grid-column: span 1;
503
546
  border-color: var(--jini-accent);
504
547
  box-shadow: 0 0 0 1px var(--jini-accent);
505
548
  }
@@ -532,30 +575,26 @@
532
575
 
533
576
  /* The whole card is the click target, not just the text at the top of it.
534
577
  *
535
- * `.jini-agent-grid` is a grid, so selecting one CLI stretches every card in
536
- * that row to match the expanded one — while `-select` keeps its natural
537
- * content height. The space the row-mate's expansion added was therefore
538
- * inert: clicking the lower two-thirds of a stretched-but-unselected card did
539
- * nothing, which reads as a broken card rather than as "aim higher".
578
+ * The select button owns the whole header, including its padding, when no
579
+ * other controls need that space.
540
580
  *
541
581
  * `:last-child` is load-bearing. A card renders its diagnostics, model config,
542
582
  * test result and path-repair actions as SIBLINGS after `-main`, and those
543
583
  * blocks contain their own buttons; growing `-main` unconditionally would push
544
584
  * them to the card's bottom edge, and a full-card overlay would swallow their
545
585
  * clicks outright. `-main:last-child` is true exactly when nothing rendered
546
- * after it — the unselected, no-diagnostics case this bug is about.
586
+ * after it — the no-diagnostics case this bug is about.
547
587
  *
548
- * `:not(.is-selected)` guards the padding move specifically: a SELECTED card
549
- * can render its Test button inside `-main`, and zeroing the padding there
550
- * would shove that button flush against the card edge. */
551
- .jini-agent-card:not(.is-selected) .jini-agent-card-main:last-child {
588
+ * Test stays in the selected card's header; settings follow `-main`, keeping
589
+ * both their controls and the header's Test button independently clickable. */
590
+ .jini-agent-card .jini-agent-card-main:last-child {
552
591
  flex: 1 1 auto;
553
592
  /* Handed to the button below — left here it would ring the card in 12px of
554
593
  unclickable margin, a smaller version of the same bug. */
555
594
  padding: 0;
556
595
  }
557
596
 
558
- .jini-agent-card:not(.is-selected) .jini-agent-card-main:last-child > .jini-agent-card-select {
597
+ .jini-agent-card .jini-agent-card-main:last-child > .jini-agent-card-select {
559
598
  /* The row is `align-items: flex-start` so the Test button never stretches;
560
599
  this opts the select button alone into filling the card. Its own
561
600
  `align-items: flex-start` keeps icon and text at the top — only the hit
@@ -605,15 +644,15 @@
605
644
  color: var(--jini-text);
606
645
  }
607
646
 
608
- .jini-agent-card-name-divider {
609
- color: var(--jini-text-disabled);
610
- }
611
-
612
647
  .jini-agent-card-tagline {
613
648
  font-size: 12px;
614
649
  color: var(--jini-text-muted);
615
650
  }
616
651
 
652
+ .jini-agent-card.is-selected .jini-agent-card-tagline:not(:empty)::before {
653
+ content: '·\00a0';
654
+ }
655
+
617
656
  .jini-agent-card-badges {
618
657
  display: flex;
619
658
  flex-wrap: wrap;
@@ -657,14 +696,98 @@
657
696
  opacity: 0.7;
658
697
  }
659
698
 
660
- .jini-agent-card-config {
699
+ /* Selection reads as the accent ring plus this check, never as a bigger card. */
700
+ .jini-agent-card-check {
701
+ flex: 0 0 auto;
702
+ display: grid;
703
+ place-items: center;
704
+ width: 18px;
705
+ height: 18px;
706
+ margin-left: auto;
707
+ border-radius: 50%;
708
+ background: var(--jini-accent);
709
+ color: var(--jini-accent-ink);
710
+ font-size: 11px;
711
+ font-weight: 700;
712
+ line-height: 1;
713
+ }
714
+
715
+ /* Settings stay inside the selected card, with each label above its control
716
+ and the model's provenance sentence visible below the picker. */
717
+ .jini-agent-settings {
661
718
  display: flex;
662
719
  flex-direction: column;
663
- gap: 4px;
664
- padding: 0 12px 12px;
720
+ gap: 8px;
721
+ padding: 10px 12px;
665
722
  border-top: 1px solid var(--jini-border);
666
- margin-top: 2px;
667
- padding-top: 10px;
723
+ }
724
+
725
+ .jini-agent-settings-row,
726
+ .jini-agent-settings-tail {
727
+ display: flex;
728
+ flex-direction: column;
729
+ align-items: stretch;
730
+ gap: 8px 16px;
731
+ }
732
+
733
+ .jini-agent-settings-tail {
734
+ gap: 8px 10px;
735
+ }
736
+
737
+ /* Wide enough for "Testing…", so starting a test never reflows its controls. */
738
+ .jini-agent-settings-tail > .jini-agent-card-test {
739
+ min-width: 5.5rem;
740
+ }
741
+
742
+ .jini-agent-settings-field {
743
+ display: flex;
744
+ flex-direction: column;
745
+ align-items: stretch;
746
+ gap: 4px;
747
+ min-width: 0;
748
+ }
749
+
750
+ .jini-agent-settings-label {
751
+ display: flex;
752
+ align-items: center;
753
+ flex-wrap: wrap;
754
+ gap: 6px;
755
+ }
756
+
757
+ .jini-agent-settings-field > .jini-field-label {
758
+ white-space: nowrap;
759
+ }
760
+
761
+ .jini-agent-settings .jini-searchable-select,
762
+ .jini-agent-settings select.jini-input,
763
+ .jini-agent-settings .jini-agent-settings-custom {
764
+ width: 100%;
765
+ min-width: 0;
766
+ max-width: none;
767
+ }
768
+
769
+ .jini-agent-settings-source {
770
+ display: inline-flex;
771
+ align-items: center;
772
+ gap: 2px;
773
+ }
774
+
775
+ .jini-agent-settings-source > .jini-agent-model-source {
776
+ margin-left: 0;
777
+ white-space: nowrap;
778
+ }
779
+
780
+ .jini-agent-settings .jini-agent-card-test-result {
781
+ padding: 0;
782
+ }
783
+
784
+ .jini-agent-settings .jini-agent-card-path-repair {
785
+ margin: 0;
786
+ }
787
+
788
+ .jini-agent-settings .jini-agent-cli-env {
789
+ margin: 0;
790
+ padding-top: 8px;
668
791
  }
669
792
 
670
793
  /* The source badge is load-bearing, not decoration: a built-in list can be
@@ -895,7 +1018,21 @@
895
1018
  width: 100%;
896
1019
  }
897
1020
 
1021
+ /* Hosts may pass the input class to the wrapper; only the trigger paints the control. */
1022
+ .jini-select.jini-input {
1023
+ padding: 0;
1024
+ border: 0;
1025
+ background: none;
1026
+ box-shadow: none;
1027
+ min-height: 0;
1028
+ }
1029
+ .jini-select.is-missing > .jini-select-trigger {
1030
+ border-color: color-mix(in srgb, var(--jini-danger) 55%, var(--jini-border));
1031
+ }
1032
+
898
1033
  .jini-select-trigger {
1034
+ min-height: var(--jini-control-h, 37px);
1035
+ box-sizing: border-box;
899
1036
  display: flex;
900
1037
  align-items: center;
901
1038
  justify-content: space-between;
@@ -912,6 +1049,17 @@
912
1049
  cursor: pointer;
913
1050
  }
914
1051
 
1052
+ .jini-select-trigger:focus-visible {
1053
+ outline: none;
1054
+ border-color: var(--jini-accent);
1055
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--jini-accent) 18%, transparent);
1056
+ }
1057
+
1058
+ @media (max-width: 640px) {
1059
+ .jini-input,
1060
+ .jini-select-trigger { font-size: 16px; min-height: 40px; }
1061
+ }
1062
+
915
1063
  .jini-select-trigger:hover:not(:disabled) {
916
1064
  border-color: var(--jini-border-strong);
917
1065
  }
@@ -35,6 +35,10 @@
35
35
  border-radius: var(--jini-radius-lg);
36
36
  box-shadow: var(--jini-shadow-lg);
37
37
  overflow: hidden;
38
+ /* Layout follows the dialog's OWN width, not the viewport's: an inline settings page beside an
39
+ expanded nav and an open assistant dock is narrow on a wide screen, and a 375px modal is narrow
40
+ on any screen. See the `@container jini-tabbed-dialog` block at the end. */
41
+ container: jini-tabbed-dialog / inline-size;
38
42
  }
39
43
 
40
44
  /* Page (non-modal) variant: the shell renders without `onClose`, so it should
@@ -271,3 +275,90 @@
271
275
  .jini-tabbed-dialog-sidebar-toggle:focus-visible {
272
276
  opacity: 1;
273
277
  }
278
+
279
+ /* ============================================================
280
+ Narrow dialog: the side nav becomes one top tab row
281
+ ============================================================
282
+ A 240px side nav inside a ~500px dialog left the form a thin column with
283
+ its controls spilling past the edge (Chat settings at 500px, Chrome check
284
+ 2026-10-11). Below 600px of DIALOG width (a 40rem dialog, e.g. Sites
285
+ settings, keeps its desktop look) the nav turns into a horizontal,
286
+ swipeable tab row over a full-width panel. The collapse chevron has
287
+ nothing to reclaim there, so it hides, and a collapsed nav reappears as
288
+ the row: a narrow dialog never loses its tabs. Modal-only spacing skips
289
+ the inline (page) variant, whose host owns its page insets. */
290
+ @container jini-tabbed-dialog (max-width: 600px) {
291
+ .jini-tabbed-dialog:not(.jini-tabbed-dialog--inline) .jini-tabbed-dialog-head {
292
+ padding: 20px 56px 8px 16px;
293
+ }
294
+
295
+ .jini-tabbed-dialog:not(.jini-tabbed-dialog--inline) .jini-tabbed-dialog-chrome {
296
+ top: 16px;
297
+ right: 16px;
298
+ }
299
+
300
+ .jini-tabbed-dialog:not(.jini-tabbed-dialog--inline) .jini-tabbed-dialog-head-line h2,
301
+ .jini-tabbed-dialog:not(.jini-tabbed-dialog--inline) .jini-tabbed-dialog-head h2 {
302
+ font-size: 22px;
303
+ }
304
+
305
+ .jini-tabbed-dialog-body,
306
+ .jini-tabbed-dialog-sidebar-collapsed .jini-tabbed-dialog-body {
307
+ grid-template-columns: minmax(0, 1fr);
308
+ grid-template-rows: auto minmax(0, 1fr);
309
+ }
310
+
311
+ .jini-tabbed-dialog-sidebar,
312
+ .jini-tabbed-dialog-sidebar-collapsed .jini-tabbed-dialog-sidebar {
313
+ display: flex;
314
+ flex-direction: row;
315
+ gap: 2px;
316
+ padding: 0 16px;
317
+ background: transparent;
318
+ border-right: 0;
319
+ box-shadow: inset 0 -1px 0 var(--jini-border);
320
+ overflow-x: auto;
321
+ overflow-y: hidden;
322
+ overscroll-behavior-x: contain;
323
+ scrollbar-width: none;
324
+ }
325
+
326
+ .jini-tabbed-dialog-sidebar::-webkit-scrollbar {
327
+ display: none;
328
+ }
329
+
330
+ .jini-tabbed-dialog-nav-item {
331
+ flex: 0 0 auto;
332
+ width: auto;
333
+ grid-template-columns: auto auto;
334
+ gap: 6px;
335
+ padding: 9px 10px;
336
+ border: 0;
337
+ border-bottom: 2px solid transparent;
338
+ border-radius: 0;
339
+ white-space: nowrap;
340
+ }
341
+
342
+ .jini-tabbed-dialog-nav-item:hover {
343
+ background: transparent;
344
+ color: var(--jini-text);
345
+ }
346
+
347
+ .jini-tabbed-dialog-nav-item.active,
348
+ .jini-tabbed-dialog-nav-item.active:hover {
349
+ background: transparent;
350
+ border-bottom-color: var(--jini-accent);
351
+ }
352
+
353
+ .jini-tabbed-dialog-nav-text small {
354
+ display: none;
355
+ }
356
+
357
+ .jini-tabbed-dialog-sidebar-toggle {
358
+ display: none;
359
+ }
360
+
361
+ .jini-tabbed-dialog:not(.jini-tabbed-dialog--inline) .jini-tabbed-dialog-content {
362
+ padding: 14px 16px 18px;
363
+ }
364
+ }
@@ -1,50 +1,11 @@
1
1
  import type { CSSProperties } from 'react';
2
+ export { installRemixIconStylesheet, REMIXICON_STYLESHEET_MARKER } from './RemixIcon.stylesheet.js';
2
3
  interface RemixIconProps {
3
4
  name: string;
4
5
  size?: number;
5
6
  className?: string;
6
7
  style?: CSSProperties;
7
8
  }
8
- /**
9
- * Marks whichever stylesheet is currently serving `.ri-*` glyphs — this package's own default, or
10
- * a host override that got inserted first. `ensureRemixIconStylesheet` checks for this attribute
11
- * (not a specific `href`) before injecting, so a host that wants its own icon set/font just needs
12
- * to add a `<link data-jini-remixicon rel="stylesheet" href="...">` (or an equivalent `<style
13
- * data-jini-remixicon>`) anywhere in `<head>` before the first `RemixIcon` mounts, and this
14
- * package's default never loads.
15
- *
16
- * Exported because every host that has needed the override so far re-declared this string
17
- * literally rather than importing it -- three copies outside this file, across two apps of the
18
- * one downstream checkout inspected (2026-09-06), and they do not agree on element type: one
19
- * installs a `<style>`, the other a `<link>`. A copied magic string is a rename away from
20
- * silently double-loading the font; importing the constant is not.
21
- */
22
- export declare const REMIXICON_STYLESHEET_MARKER = "data-jini-remixicon";
23
- /**
24
- * Installs a caller-supplied stylesheet as the one serving `.ri-*` glyphs, claiming
25
- * {@link REMIXICON_STYLESHEET_MARKER} so this package's own default never loads.
26
- *
27
- * This is the supported opt-in for any host whose bundler defeats the default's `import.meta.url`
28
- * resolution (see {@link ensureRemixIconStylesheet}) — point it at
29
- * `@jini-ai/ui/remixicon.css`, which is a normal subpath export a bundler resolves and rewrites
30
- * like any other asset. Call it before the first `RemixIcon` mounts; module top level in the
31
- * host's entry file is the usual place, since the default injection runs from a `useEffect` and
32
- * therefore only has to lose a race against React's commit phase.
33
- *
34
- * Idempotent and safe to call unconditionally: it no-ops if any marked stylesheet is already
35
- * present (including one a previous call installed), and no-ops outside a DOM.
36
- *
37
- * @param options.href URL of a stylesheet defining the `.ri-*` classes and its own `@font-face`.
38
- * Resolved by the browser against the document, so relative values follow the page's base URL —
39
- * pass a bundler-produced URL rather than a hand-written relative path.
40
- * @returns `true` if this call inserted the stylesheet, `false` if it no-opped.
41
- * @sideEffects Appends one `<link>` to `document.head`.
42
- * @complexity O(1) time (one `querySelector` on an attribute selector), O(1) space.
43
- * @overallScore 100
44
- */
45
- export declare function installRemixIconStylesheet(options: {
46
- href: string;
47
- }): boolean;
48
9
  /**
49
10
  * Thin wrapper around a RemixIcon webfont glyph (`ri-<name>`). Loads this package's own default
50
11
  * RemixIcon CSS/font the first time any `RemixIcon` mounts — see
@@ -52,5 +13,4 @@ export declare function installRemixIconStylesheet(options: {
52
13
  * {@link installRemixIconStylesheet} for what a bundled host should call instead.
53
14
  */
54
15
  export declare function RemixIcon({ name, size, className, style }: RemixIconProps): import("react").JSX.Element;
55
- export {};
56
16
  //# sourceMappingURL=RemixIcon.d.ts.map