@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
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { agentHandleProps, agentSubHandle } from '@jini-ai/agentic';
3
+ import { useT } from '../../../i18n/index.js';
4
+ import { CUSTOM_MODEL_SENTINEL } from '../../constants.js';
5
+ import { AgentCliEnvFields } from './AgentCliEnvFields.js';
6
+ import { SearchableModelSelect } from './SearchableModelSelect.js';
7
+ /**
8
+ * The selected CLI's settings, stacked inside its own card.
9
+ *
10
+ * Owner, 2026-10-09: both the full-row card and the separate settings strip
11
+ * spread selection beyond its own column. Keep full-width controls and a
12
+ * visible provenance sentence inside the card so only that card grows taller.
13
+ */
14
+ export function LocalCliAgentSettings({ agent, config, card, cliEnvFields, onModelChange, onEnvChange, agentHandle, }) {
15
+ const t = useT();
16
+ const { reasoning, source, sourceLabel, sourceHint, result, repair, repairField } = card;
17
+ const { hasModels, hasReasoning, reasoningOptions, reasoningValue, reasoningDisabled, allowCustomModel, customActive, modelChoices, selectValue, customModelInputValue, } = reasoning;
18
+ return (_jsxs("div", { className: "jini-agent-settings", role: "group", "aria-label": agent.label, "data-testid": `jini-agent-settings-${agent.id}`, children: [_jsxs("div", { className: "jini-agent-settings-row", children: [hasModels ? (_jsxs("div", { className: "jini-agent-settings-field jini-agent-settings-model", children: [_jsxs("div", { className: "jini-agent-settings-label", children: [_jsx("span", { className: "jini-field-label", children: t('Model') }), sourceLabel ? (_jsx("span", { className: `jini-agent-model-source ${source}`, children: sourceLabel })) : null] }), _jsx(SearchableModelSelect, { className: "jini-input jini-agent-model-select", ariaLabel: t('Model'), searchPlaceholder: t('Search models'), testId: `jini-agent-model-${agent.id}`, searchInputTestId: `jini-agent-model-search-${agent.id}`, ...(agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'model' }) } : {}), value: selectValue, models: modelChoices, additionalOptions: allowCustomModel ? [{ value: CUSTOM_MODEL_SENTINEL, label: t('Custom…') }] : undefined, onChange: card.selectModel }), sourceHint ? _jsx("p", { className: "jini-field-hint", children: sourceHint }) : null] })) : null, customActive ? (_jsxs("label", { className: "jini-agent-settings-field", children: [_jsx("span", { className: "jini-field-label", children: t('Custom model id') }), _jsx("input", { className: "jini-input jini-agent-settings-custom", type: "text", "data-testid": `jini-agent-model-custom-${agent.id}`, value: customModelInputValue, placeholder: t('e.g. my-fine-tuned-model'), spellCheck: false, onChange: (event) => onModelChange(agent.id, event.target.value.trim()), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'custom-model', role: 'field', label: t('Custom model id') }) }) })] })) : null, hasReasoning ? (_jsxs("label", { className: "jini-agent-settings-field", children: [_jsx("span", { className: "jini-field-label", children: t('Reasoning effort') }), _jsx("select", { className: "jini-input jini-agent-settings-reasoning", "data-testid": `jini-agent-reasoning-${agent.id}`, value: reasoningValue, disabled: reasoningDisabled, onChange: (event) => card.selectReasoning(event.target.value), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'reasoning', role: 'field', label: t('Reasoning effort') }) }), children: reasoningOptions.map((option) => (_jsx("option", { value: option.id, children: option.label }, option.id))) })] })) : null] }), result ? (_jsx("p", { className: 'jini-agent-card-test-result is-' + result.status, role: result.status === 'error' ? 'alert' : 'status', children: result.message ?? (result.status === 'ok' ? t('Agent is ready.') : t('Agent check failed')) })) : null, repair && repairField ? (_jsxs("div", { className: "jini-agent-card-path-repair", children: [_jsx("span", { className: "jini-field-hint", children: t('A custom path override did not work, so this used the automatically detected binary instead.') }), _jsxs("div", { className: "jini-agent-card-path-repair-actions", children: [repair.canUseDetected ? (_jsx("button", { type: "button", className: "jini-btn", "data-testid": `jini-agent-path-repair-use-${agent.id}`, onClick: () => onEnvChange(agent.id, repairField.envKey, repair.detectedPath), children: t('Use detected path') })) : null, _jsx("button", { type: "button", className: "jini-btn jini-btn-ghost", "data-testid": `jini-agent-path-repair-clear-${agent.id}`, onClick: () => onEnvChange(agent.id, repairField.envKey, ''), children: t('Clear custom path') })] })] })) : null, _jsx(AgentCliEnvFields, { agentId: agent.id, fields: cliEnvFields, config: config, onChange: onEnvChange })] }));
19
+ }
20
+ //# sourceMappingURL=LocalCliAgentSettings.js.map
@@ -0,0 +1,24 @@
1
+ import type { ByokConfig, ProviderPreset } from '../../types.js';
2
+ type Field = 'apiKey' | 'baseUrl' | 'model';
3
+ /** Keeps required-field gates immediate while optionally waiting for blur to paint errors.
4
+ * Stored keys satisfy only the key requirement; touching one provider never marks another.
5
+ * @complexity Time/space: O(1), bounded by the three required fields.
6
+ */
7
+ export declare function useByokFieldValidation(required: {
8
+ config: ByokConfig;
9
+ preset: ProviderPreset | null;
10
+ apiKeyStoredExternally: boolean;
11
+ }, optional?: {
12
+ deferMissingFeedback?: boolean;
13
+ }): {
14
+ missing: Set<import("../../types.js").ByokRequiredField>;
15
+ baseUrlInvalid: boolean;
16
+ isMissing: ({ field }: {
17
+ field: Field;
18
+ }, _options?: Record<string, never>) => boolean;
19
+ markTouched: ({ field }: {
20
+ field: Field;
21
+ }, _options?: Record<string, never>) => void;
22
+ };
23
+ export {};
24
+ //# sourceMappingURL=use-byok-field-validation.hooks.d.ts.map
@@ -0,0 +1,27 @@
1
+ import { useState } from 'react';
2
+ import { isBaseUrlInvalid, missingRequiredFields } from '../../rules.js';
3
+ /** Keeps required-field gates immediate while optionally waiting for blur to paint errors.
4
+ * Stored keys satisfy only the key requirement; touching one provider never marks another.
5
+ * @complexity Time/space: O(1), bounded by the three required fields.
6
+ */
7
+ export function useByokFieldValidation(required, optional = {}) {
8
+ const { config, preset, apiKeyStoredExternally } = required;
9
+ const [touched, setTouched] = useState({ providerId: config.providerId, fields: new Set() });
10
+ const missing = new Set(missingRequiredFields(config, preset));
11
+ if (apiKeyStoredExternally)
12
+ missing.delete('apiKey');
13
+ const baseUrlInvalid = isBaseUrlInvalid(config);
14
+ return {
15
+ missing,
16
+ baseUrlInvalid,
17
+ isMissing: ({ field }, _options = {}) => (missing.has(field) || (field === 'baseUrl' && baseUrlInvalid)) &&
18
+ (!optional.deferMissingFeedback || (touched.providerId === config.providerId && touched.fields.has(field))),
19
+ markTouched: ({ field }, _options = {}) => {
20
+ setTouched(current => ({
21
+ providerId: config.providerId,
22
+ fields: new Set([...(current.providerId === config.providerId ? current.fields : []), field]),
23
+ }));
24
+ },
25
+ };
26
+ }
27
+ //# sourceMappingURL=use-byok-field-validation.hooks.js.map
@@ -37,14 +37,17 @@ export function useExecutionTab({ port, autoDetect = true, describeProbeError =
37
37
  /** The host's describer as of the latest render — see `UseExecutionTabOptions.describeProbeError`. */
38
38
  const describeProbeErrorRef = useRef(describeProbeError);
39
39
  describeProbeErrorRef.current = describeProbeError;
40
+ const detectionTimer = useRef(undefined);
40
41
  const alive = useRef(true);
41
42
  useEffect(() => {
42
43
  alive.current = true;
43
44
  return () => {
44
45
  alive.current = false;
46
+ clearTimeout(detectionTimer.current);
45
47
  };
46
48
  }, []);
47
49
  const runDetection = useCallback((detect) => {
50
+ clearTimeout(detectionTimer.current);
48
51
  const ticket = ++detectionTicket.current;
49
52
  const isCurrent = () => alive.current && detectionTicket.current === ticket;
50
53
  setScan({ status: 'scanning' });
@@ -54,12 +57,18 @@ export function useExecutionTab({ port, autoDetect = true, describeProbeError =
54
57
  const sorted = sortDetectedAgents(found);
55
58
  setAgents(sorted);
56
59
  setScan({ status: 'ok', count: sorted.filter((agent) => agent.installed).length });
60
+ if (found.some(agent => agent.discoveryPending)) {
61
+ detectionTimer.current = setTimeout(() => {
62
+ if (isCurrent())
63
+ runDetection(() => port.detectLocalAgents());
64
+ }, 1000);
65
+ }
57
66
  }, (error) => {
58
67
  if (!isCurrent())
59
68
  return;
60
69
  setScan({ status: 'error', message: describeErrorMessage(error) });
61
70
  });
62
- }, []);
71
+ }, [port]);
63
72
  useEffect(() => {
64
73
  if (!autoDetect)
65
74
  return;
@@ -0,0 +1,48 @@
1
+ import { agentExecutableRepairState } from '../../rules.js';
2
+ import type { AgentCliEnvFieldSpec, AgentTestState, DetectedAgent, LocalCliConfig } from '../../types.js';
3
+ import { type UseReasoningControlResult } from './useReasoningControl.js';
4
+ type Translate = (key: string) => string;
5
+ export interface UseLocalCliAgentCardInput {
6
+ agent: DetectedAgent;
7
+ config: LocalCliConfig;
8
+ agentTest: AgentTestState;
9
+ cliEnvFields: readonly AgentCliEnvFieldSpec[];
10
+ onModelChange: (agentId: string, model: string) => void;
11
+ onReasoningChange: (agentId: string, reasoning: string) => void;
12
+ onRescan?: (() => void) | undefined;
13
+ /** Only its presence matters here: a Test button alone is enough to show the settings strip. */
14
+ onTest?: ((agent: DetectedAgent) => void) | undefined;
15
+ t: Translate;
16
+ }
17
+ export interface UseLocalCliAgentCardResult {
18
+ reasoning: UseReasoningControlResult;
19
+ /** Whether the selected card's settings strip has anything to show; without this it rendered
20
+ * as an empty bordered box for a CLI with no models, effort, Test or env fields. */
21
+ hasSettings: boolean;
22
+ /** `agent.modelsSource`, passed through for the badge's modifier class. */
23
+ source: DetectedAgent['modelsSource'];
24
+ sourceLabel: string;
25
+ sourceHint: string;
26
+ testing: boolean;
27
+ result: Extract<AgentTestState, {
28
+ status: 'ok' | 'error';
29
+ }> | null;
30
+ repair: ReturnType<typeof agentExecutableRepairState>;
31
+ repairField: AgentCliEnvFieldSpec | null;
32
+ diagnosticHandlers: {
33
+ onRescan: (() => void) | undefined;
34
+ onOpenInstall: (() => void) | undefined;
35
+ onOpenDocs: (() => void) | undefined;
36
+ };
37
+ /** The model picker's change handler, including its "Custom…" sentinel. */
38
+ selectModel: (nextValue: string) => void;
39
+ /** The reasoning picker's change handler, including the model-suffix shape. */
40
+ selectReasoning: (nextValue: string) => void;
41
+ }
42
+ /**
43
+ * Everything `LocalCliAgentCard` and its settings strip derive from one agent, kept out of the
44
+ * `.tsx` so both render from the same values.
45
+ */
46
+ export declare function useLocalCliAgentCard({ agent, config, agentTest, cliEnvFields, onModelChange, onReasoningChange, onRescan, onTest, t, }: UseLocalCliAgentCardInput): UseLocalCliAgentCardResult;
47
+ export {};
48
+ //# sourceMappingURL=useLocalCliAgentCard.d.ts.map
@@ -0,0 +1,110 @@
1
+ import { useState } from 'react';
2
+ import { CUSTOM_MODEL_SENTINEL } from '../../constants.js';
3
+ import { agentExecutableRepairState, binPathEnvField, cliEnvFieldsForAgent } from '../../rules.js';
4
+ import { useReasoningControl } from './useReasoningControl.js';
5
+ /**
6
+ * Everything `LocalCliAgentCard` and its settings strip derive from one agent, kept out of the
7
+ * `.tsx` so both render from the same values.
8
+ */
9
+ export function useLocalCliAgentCard({ agent, config, agentTest, cliEnvFields, onModelChange, onReasoningChange, onRescan, onTest, t, }) {
10
+ // Whether the operator explicitly opened the free-text "Custom" entry via
11
+ // the model picker's own "Custom…" option — distinct from "the saved model
12
+ // simply isn't in the known list" (`shouldShowCustomModelInput` covers
13
+ // both, but only THIS one needs to be remembered as a UI-local toggle,
14
+ // since it is not itself persisted config: see `LocalCliConfig`, which has
15
+ // nowhere for "the operator is mid-typing a custom id" to live). Scoped to
16
+ // this card instance, so switching to a different agent and back does not
17
+ // leak one agent's custom-mode toggle onto another.
18
+ const [explicitCustomMode, setExplicitCustomMode] = useState(false);
19
+ const reasoning = useReasoningControl({
20
+ agent,
21
+ config,
22
+ explicitCustomMode,
23
+ onReasoningChange: (next) => onReasoningChange(agent.id, next),
24
+ });
25
+ // `modelsSource` is optional in the contract, so an absent value means the
26
+ // host did not say where the list came from — NOT that it came from a
27
+ // built-in fallback. Defaulting to 'fallback' rendered "Built-in list" over
28
+ // a list that may well be live, and told the operator to Rescan to fix a
29
+ // problem they do not have. Unknown provenance shows no badge and no claim.
30
+ const source = agent.modelsSource;
31
+ const sourceLabel = source === 'live' ? t('Live from CLI') : source === 'fallback' ? t('Built-in list') : '';
32
+ const sourceHint = source === 'live'
33
+ ? t("Model list comes from this CLI. Default uses the CLI's own config.")
34
+ : source === 'fallback'
35
+ ? t('Showing built-in defaults. Click Rescan to pull live models from the CLI.')
36
+ : '';
37
+ // The test result belongs to whichever agent was probed, so a stale verdict
38
+ // from a previously-tested CLI never renders under this one.
39
+ const testing = agentTest.status === 'testing' && agentTest.agentId === agent.id;
40
+ const result = (agentTest.status === 'ok' || agentTest.status === 'error') && agentTest.agentId === agent.id
41
+ ? agentTest
42
+ : null;
43
+ // The "use the binary that actually ran" / "clear the override" repair
44
+ // affordance — only meaningful when a successful test silently fell back
45
+ // off a configured override, AND this agent's catalog names which field
46
+ // that override lives in. See `agentExecutableRepairState`'s doc.
47
+ //
48
+ // `AgentTestState`'s 'ok' variant spells success as `status: 'ok'`, not an
49
+ // `ok: true` field — `agentExecutableRepairState` takes the latter (it
50
+ // mirrors `ExecutionPort.testAgent`'s raw resolved shape, one layer below
51
+ // this state union), so the two must be translated explicitly here rather
52
+ // than passing `result` straight through.
53
+ const repair = result?.status === 'ok'
54
+ ? agentExecutableRepairState({
55
+ ok: true,
56
+ usedExecutableSource: result.usedExecutableSource,
57
+ detectedExecutablePath: result.detectedExecutablePath,
58
+ })
59
+ : null;
60
+ const repairField = repair ? binPathEnvField(cliEnvFields, agent.id) : null;
61
+ const openUrl = (url) => {
62
+ if (!url)
63
+ return;
64
+ window.open(url, '_blank', 'noopener,noreferrer');
65
+ };
66
+ const diagnosticHandlers = {
67
+ onRescan,
68
+ onOpenInstall: agent.installUrl ? () => openUrl(agent.installUrl) : undefined,
69
+ onOpenDocs: agent.docsUrl ? () => openUrl(agent.docsUrl) : undefined,
70
+ };
71
+ const selectModel = (nextValue) => {
72
+ if (nextValue === CUSTOM_MODEL_SENTINEL) {
73
+ setExplicitCustomMode(true);
74
+ onModelChange(agent.id, '');
75
+ return;
76
+ }
77
+ setExplicitCustomMode(false);
78
+ onModelChange(agent.id, reasoning.resolveNextModelId(nextValue));
79
+ };
80
+ const selectReasoning = (nextValue) => {
81
+ // A model-suffix agent has no reasoning value of its own to record: the choice
82
+ // is which model id runs, so it goes back out through `onModelChange`.
83
+ const suffixModelId = reasoning.resolveReasoningModelId(nextValue);
84
+ if (suffixModelId !== null) {
85
+ onModelChange(agent.id, suffixModelId);
86
+ return;
87
+ }
88
+ onReasoningChange(agent.id, nextValue);
89
+ };
90
+ const hasSettings = reasoning.hasModels ||
91
+ reasoning.hasReasoning ||
92
+ Boolean(onTest) ||
93
+ result !== null ||
94
+ cliEnvFieldsForAgent(cliEnvFields, agent.id).length > 0;
95
+ return {
96
+ reasoning,
97
+ hasSettings,
98
+ source,
99
+ sourceLabel,
100
+ sourceHint,
101
+ testing,
102
+ result,
103
+ repair,
104
+ repairField,
105
+ diagnosticHandlers,
106
+ selectModel,
107
+ selectReasoning,
108
+ };
109
+ }
110
+ //# sourceMappingURL=useLocalCliAgentCard.js.map
@@ -0,0 +1,19 @@
1
+ import type { DetectedAgent } from '../../types.js';
2
+ export interface UseLocalCliAgentListResult {
3
+ /** Installed CLIs plus the current pick: always rendered. */
4
+ visible: DetectedAgent[];
5
+ /** Not-installed CLIs: rendered only while `showCollapsed`. */
6
+ collapsed: DetectedAgent[];
7
+ showCollapsed: boolean;
8
+ toggleCollapsed: () => void;
9
+ }
10
+ /**
11
+ * Disclosure state for the CLI grid's not-installed tail. Collapsed by default: those cards
12
+ * cannot be picked until installed, and a dozen of them pushed every setting below the grid
13
+ * off-screen. See `partitionAgentsByInstalled` for what stays visible.
14
+ */
15
+ export declare function useLocalCliAgentList({ agents, selectedAgentId }: {
16
+ agents: readonly DetectedAgent[];
17
+ selectedAgentId: string | null;
18
+ }): UseLocalCliAgentListResult;
19
+ //# sourceMappingURL=useLocalCliAgentList.d.ts.map
@@ -0,0 +1,13 @@
1
+ import { useState } from 'react';
2
+ import { partitionAgentsByInstalled } from '../../rules.js';
3
+ /**
4
+ * Disclosure state for the CLI grid's not-installed tail. Collapsed by default: those cards
5
+ * cannot be picked until installed, and a dozen of them pushed every setting below the grid
6
+ * off-screen. See `partitionAgentsByInstalled` for what stays visible.
7
+ */
8
+ export function useLocalCliAgentList({ agents, selectedAgentId }) {
9
+ const [showCollapsed, setShowCollapsed] = useState(false);
10
+ const { visible, collapsed } = partitionAgentsByInstalled(agents, selectedAgentId);
11
+ return { visible, collapsed, showCollapsed, toggleCollapsed: () => setShowCollapsed((open) => !open) };
12
+ }
13
+ //# sourceMappingURL=useLocalCliAgentList.js.map
@@ -125,6 +125,19 @@ export declare function sortDetectedAgents<T extends {
125
125
  installed: boolean;
126
126
  label: string;
127
127
  }>(agents: readonly T[]): readonly T[];
128
+ /**
129
+ * Splits the CLI grid into what to show up front and what sits behind the "Show N not installed"
130
+ * toggle. Installed CLIs are the ones an operator can pick, so a long tail of uninstalled cards
131
+ * buried every setting below the grid. The selected agent always stays visible even when it is
132
+ * not installed (a stale pick after an uninstall), so the current choice is never hidden.
133
+ */
134
+ export declare function partitionAgentsByInstalled<T extends {
135
+ id: string;
136
+ installed: boolean;
137
+ }>(agents: readonly T[], selectedAgentId: string | null | undefined): {
138
+ visible: T[];
139
+ collapsed: T[];
140
+ };
128
141
  /**
129
142
  * Strips a redundant trailing agent name from a CLI's `--version` output, so
130
143
  * "1.2.3 (Claude Code)" renders as "1.2.3" beside a card already titled
@@ -282,6 +282,20 @@ export function sortDetectedAgents(agents) {
282
282
  return a.label.localeCompare(b.label);
283
283
  });
284
284
  }
285
+ /**
286
+ * Splits the CLI grid into what to show up front and what sits behind the "Show N not installed"
287
+ * toggle. Installed CLIs are the ones an operator can pick, so a long tail of uninstalled cards
288
+ * buried every setting below the grid. The selected agent always stays visible even when it is
289
+ * not installed (a stale pick after an uninstall), so the current choice is never hidden.
290
+ */
291
+ export function partitionAgentsByInstalled(agents, selectedAgentId) {
292
+ const visible = [];
293
+ const collapsed = [];
294
+ for (const agent of agents) {
295
+ (agent.installed || agent.id === selectedAgentId ? visible : collapsed).push(agent);
296
+ }
297
+ return { visible, collapsed };
298
+ }
285
299
  /**
286
300
  * Strips a redundant trailing agent name from a CLI's `--version` output, so
287
301
  * "1.2.3 (Claude Code)" renders as "1.2.3" beside a card already titled
@@ -182,6 +182,8 @@ export type AgentSupportsCustomModel = boolean;
182
182
  * the route boundary throws away data the card is built to render.
183
183
  */
184
184
  export interface DetectedAgent {
185
+ /** Cached inventory is usable now; retry a normal read until background discovery settles. */
186
+ discoveryPending?: boolean;
185
187
  id: string;
186
188
  label: string;
187
189
  installed: boolean;
@@ -36,7 +36,8 @@
36
36
  * Re-entrancy (defensive, mirroring `canvas-content-wrapper.ts`'s own `WRAPPER_ROOT_MARKER` guard): a
37
37
  * marker already sitting inside a placeholder card (`el.closest('[' + CARD_ROOT_MARKER + ']')`) is
38
38
  * skipped, so a second call — or a caller's own `load` handler firing twice — cannot nest a second card
39
- * around an already-decorated marker.
39
+ * around an already-decorated marker. Only a runtime card's direct marker is described again, to
40
+ * refresh its footprint after theme styles settle; card chrome is always skipped.
40
41
  */
41
42
  /** What to show on one embed's placeholder card — supplied per-element by the host's own `describe`
42
43
  * callback, since only the host knows the marker convention (attribute name, JSON shape, and how to
@@ -47,7 +48,18 @@ export interface CanvasEmbedPlaceholderDescriptor {
47
48
  /** Human-identifiable target, shown under the heading — e.g. a name, or a truncated id, or a note
48
49
  * that none was set. Never fetched by this module; the host decides what's cheap enough to show. */
49
50
  identityLabel: string;
51
+ /** Runtime blocks use the same card with a bounded view-only footprint and locked guidance. */
52
+ presentation?: 'embed' | 'runtime';
53
+ guidanceLabel?: string;
54
+ inline?: boolean | undefined;
55
+ declaredHeight?: number | undefined;
50
56
  }
57
+ /** Finite positive declared/native geometry wins, otherwise use the bounded runtime fallback. */
58
+ export declare function editorBlockHeight({ declaredHeight, nativeHeight, inline }: {
59
+ declaredHeight?: number | undefined;
60
+ nativeHeight?: number | undefined;
61
+ inline?: boolean | undefined;
62
+ }, _options?: object): number;
51
63
  /**
52
64
  * Scans every element under `body` once and, for each one `describe` recognizes (returns a descriptor
53
65
  * for), replaces its visible slot with an explicit placeholder card — see this file's header for the
@@ -36,12 +36,20 @@
36
36
  * Re-entrancy (defensive, mirroring `canvas-content-wrapper.ts`'s own `WRAPPER_ROOT_MARKER` guard): a
37
37
  * marker already sitting inside a placeholder card (`el.closest('[' + CARD_ROOT_MARKER + ']')`) is
38
38
  * skipped, so a second call — or a caller's own `load` handler firing twice — cannot nest a second card
39
- * around an already-decorated marker.
39
+ * around an already-decorated marker. Only a runtime card's direct marker is described again, to
40
+ * refresh its footprint after theme styles settle; card chrome is always skipped.
40
41
  */
42
+ /** Finite positive declared/native geometry wins, otherwise use the bounded runtime fallback. */
43
+ export function editorBlockHeight({ declaredHeight, nativeHeight, inline }, _options = {}) {
44
+ const height = [declaredHeight, nativeHeight].find(value => typeof value === 'number' && Number.isFinite(value) && value > 0);
45
+ return Math.max(inline ? 48 : 96, Math.min(inline ? 160 : 960, height ?? (inline ? 64 : 280)));
46
+ }
41
47
  /** Marks a placeholder card's own root, so a later pass (this function called again, or a re-entrant
42
48
  * `load`) can recognize an already-decorated marker via `el.closest` and skip it — see this file's
43
49
  * header. */
44
50
  const CARD_ROOT_MARKER = 'data-jini-embed-placeholder-root';
51
+ /** Canvas-only flag permitting footprint refreshes without describing embed cards or chrome again. */
52
+ const CARD_RUNTIME_MARKER = 'data-jini-embed-placeholder-runtime';
45
53
  /** Flags every child the card itself builds (icon, text), so the one unflagged child is always the
46
54
  * marker. {@link OVERLAY_RULE} and the stale-card check in `removeStaleCards` both rely on it. */
47
55
  const CARD_CHROME_MARKER = 'data-jini-embed-placeholder-chrome';
@@ -110,20 +118,22 @@ function buildPlaceholderIcon(doc) {
110
118
  * `textContent`, never string-concatenated markup, so a marker's own config values (which flow into
111
119
  * `descriptor.identityLabel`) can never be interpreted as HTML. */
112
120
  function buildPlaceholderCard(doc, descriptor) {
113
- const card = doc.createElement('div');
121
+ const card = doc.createElement(descriptor.inline ? 'span' : 'div');
114
122
  card.setAttribute(CARD_ROOT_MARKER, '');
123
+ if (descriptor.presentation === 'runtime')
124
+ card.setAttribute(CARD_RUNTIME_MARKER, '');
115
125
  card.setAttribute('style', CARD_STYLE);
116
126
  const text = doc.createElement('div');
117
127
  text.setAttribute(CARD_CHROME_MARKER, '');
118
128
  const heading = doc.createElement('div');
119
129
  heading.setAttribute('style', 'font-weight:600;');
120
- heading.textContent = `Placeholder — ${descriptor.kindLabel}`;
130
+ heading.textContent = descriptor.presentation === 'runtime' ? descriptor.kindLabel : `Placeholder — ${descriptor.kindLabel}`;
121
131
  const identity = doc.createElement('div');
122
132
  identity.setAttribute('style', 'opacity:.8;margin-top:2px;');
123
133
  identity.textContent = descriptor.identityLabel;
124
134
  const note = doc.createElement('div');
125
135
  note.setAttribute('style', 'opacity:.6;margin-top:4px;font-size:11px;');
126
- note.textContent = 'Shown here for editing only — the real content renders on the published page.';
136
+ note.textContent = descriptor.guidanceLabel ?? 'Shown here for editing only — the real content renders on the published page.';
127
137
  text.append(heading, identity, note);
128
138
  const icon = buildPlaceholderIcon(doc);
129
139
  icon.setAttribute(CARD_CHROME_MARKER, '');
@@ -156,9 +166,10 @@ function buildPlaceholderCard(doc, descriptor) {
156
166
  * behavior every existing caller of this module had before this function existed.
157
167
  */
158
168
  function applyResolvedMaxWidth(marker, card) {
159
- if (typeof getComputedStyle !== 'function')
169
+ const win = marker.ownerDocument.defaultView;
170
+ if (!win)
160
171
  return;
161
- const maxWidth = getComputedStyle(marker).maxWidth;
172
+ const maxWidth = win.getComputedStyle(marker).maxWidth;
162
173
  if (maxWidth && maxWidth !== 'none')
163
174
  card.style.setProperty('max-width', maxWidth);
164
175
  }
@@ -210,15 +221,20 @@ export function applyCanvasEmbedPlaceholders(body, describe) {
210
221
  // `body.childNodes` for the same reason.
211
222
  const candidates = Array.from(body.querySelectorAll('*'));
212
223
  for (const el of candidates) {
213
- // Already inside a placeholder card from an earlier pass over this same element — see this file's
214
- // header on re-entrancy. Checked via ancestor lookup rather than a marker attribute on `el` itself,
215
- // so a decorated marker's own attributes stay completely untouched by this function, not just its
216
- // children.
217
- if (el.closest(`[${CARD_ROOT_MARKER}]`))
224
+ // Skip existing embed cards and all chrome before calling the host. Runtime cards still
225
+ // re-describe their direct marker so theme changes can refresh its bounded footprint.
226
+ const existingCard = el.closest(`[${CARD_ROOT_MARKER}]`);
227
+ if (existingCard && (!existingCard.hasAttribute(CARD_RUNTIME_MARKER)
228
+ || el.parentElement !== existingCard || el.hasAttribute(CARD_CHROME_MARKER)))
218
229
  continue;
219
230
  const descriptor = describe(el);
220
231
  if (!descriptor)
221
232
  continue;
233
+ if (existingCard) {
234
+ if (descriptor.presentation === 'runtime')
235
+ applyRuntimeFootprint(el, existingCard, descriptor);
236
+ continue;
237
+ }
222
238
  const parent = el.parentNode;
223
239
  if (!parent)
224
240
  continue; // Unreachable for a live `body` descendant; defensive only.
@@ -229,12 +245,45 @@ export function applyCanvasEmbedPlaceholders(body, describe) {
229
245
  // author set that `CARD_STYLE` doesn't touch (like `max-width`) still applies unopposed. Guarded
230
246
  // on presence so a marker with no `style` attribute at all is a no-op, not a leading `;`.
231
247
  const authoredStyle = el.getAttribute('style');
232
- if (authoredStyle)
248
+ if (authoredStyle && descriptor.presentation !== 'runtime')
233
249
  card.setAttribute('style', `${authoredStyle};${card.getAttribute('style')}`);
234
250
  applyResolvedMaxWidth(el, card);
251
+ if (descriptor.presentation === 'runtime')
252
+ applyRuntimeFootprint(el, card, descriptor);
235
253
  parent.insertBefore(card, el);
236
254
  card.appendChild(el); // Moves the SAME node (auto-detaches from `parent`) — never cloned.
237
255
  ensureOverlayRule(doc);
238
256
  }
239
257
  }
258
+ /** Geometry belongs to canvas DOM only; arbitrary transforms/min-height cannot defeat the clamp. */
259
+ function applyRuntimeFootprint(el, card, descriptor) {
260
+ // Re-measure the authored shell after theme links settle, without our selection overlay's
261
+ // !important auto-height masking its declarations. No component/model styles are changed.
262
+ const overlay = el.ownerDocument.head.querySelector(`style[${OVERLAY_STYLE_MARKER}]`);
263
+ const sheet = overlay?.sheet;
264
+ const wasDisabled = sheet?.disabled;
265
+ let resolved;
266
+ try {
267
+ if (sheet)
268
+ sheet.disabled = true;
269
+ resolved = el.ownerDocument.defaultView?.getComputedStyle(el).height;
270
+ }
271
+ finally {
272
+ if (sheet)
273
+ sheet.disabled = wasDisabled ?? false;
274
+ }
275
+ const native = el.getAttribute('height');
276
+ const declared = descriptor.declaredHeight ?? (resolved?.endsWith('px') ? Number.parseFloat(resolved) : undefined);
277
+ card.style.height = `${editorBlockHeight({ declaredHeight: declared, nativeHeight: native ? Number(native) : undefined, inline: descriptor.inline }, {})}px`;
278
+ card.style.minHeight = '0';
279
+ card.style.maxHeight = descriptor.inline ? '160px' : '960px';
280
+ card.style.overflow = 'hidden';
281
+ if (descriptor.inline) {
282
+ card.style.display = 'inline-flex';
283
+ card.style.verticalAlign = 'middle';
284
+ }
285
+ // The marker is the selectable hit surface, never an authored layout/transform or media view.
286
+ el.style.setProperty('transform', 'none', 'important');
287
+ el.style.setProperty('overflow', 'hidden', 'important');
288
+ }
240
289
  //# sourceMappingURL=canvas-embed-placeholders.js.map
@@ -12,4 +12,9 @@
12
12
  * that checks attributes should use this instead of `el.hasAttribute` directly.
13
13
  */
14
14
  export declare function hasAttributeOnAnyNodeShape(el: Element, attr: string): boolean;
15
+ /** DOM-free compatibility view for the existing host attribute describer/predicate seam. */
16
+ export declare function sourceElementAttributeView({ tagName, attributes }: {
17
+ tagName: string;
18
+ attributes: Readonly<Record<string, string>>;
19
+ }, _options?: object): Element;
15
20
  //# sourceMappingURL=dom.d.ts.map
@@ -17,4 +17,14 @@ export function hasAttributeOnAnyNodeShape(el, attr) {
17
17
  const attributes = el.attributes;
18
18
  return !!attributes && Object.prototype.hasOwnProperty.call(attributes, attr);
19
19
  }
20
+ /** DOM-free compatibility view for the existing host attribute describer/predicate seam. */
21
+ export function sourceElementAttributeView({ tagName, attributes }, _options = {}) {
22
+ return {
23
+ tagName: tagName.toUpperCase(),
24
+ nodeType: 1,
25
+ attributes,
26
+ getAttribute: (name) => attributes[name.toLowerCase()] ?? null,
27
+ hasAttribute: (name) => Object.prototype.hasOwnProperty.call(attributes, name.toLowerCase()),
28
+ };
29
+ }
20
30
  //# sourceMappingURL=dom.js.map
@@ -0,0 +1,71 @@
1
+ import type { CanvasEmbedPlaceholderDescriptor } from './canvas-embed-placeholders.js';
2
+ import { type ElementOuterRange, type SourceElement } from './source-splice.js';
3
+ export type EditorBlockReason = 'kuinetic' | 'canvas' | 'autoplay-video' | 'scripted' | 'embed';
4
+ export interface EditorBlock extends ElementOuterRange {
5
+ readonly tagName: string;
6
+ readonly id?: string | undefined;
7
+ readonly reason: EditorBlockReason;
8
+ readonly descriptor: CanvasEmbedPlaceholderDescriptor;
9
+ readonly element: SourceElement;
10
+ }
11
+ export interface EditorBlockOptions {
12
+ /** Attribute-only compatibility view, matching the existing DOM/parsed-node host seam. */
13
+ describeHostElement?: ((el: Element) => CanvasEmbedPlaceholderDescriptor | undefined) | undefined;
14
+ isProtectedElement?: ((el: Element) => boolean) | undefined;
15
+ translate?: ((key: string) => string) | undefined;
16
+ }
17
+ export interface EditorHtmlTarget {
18
+ /** Zero-based occurrence among source elements sharing tagName and (when supplied) id. */
19
+ occurrenceIndex: number;
20
+ tagName: string;
21
+ id?: string;
22
+ }
23
+ export type RequestEditHtml = (target: EditorHtmlTarget, options: object) => void | Promise<void>;
24
+ /** Outermost disjoint roots; explicit live islands veto aggregation but never host embed safety. */
25
+ export declare function detectEditorBlocks({ html }: {
26
+ html: string;
27
+ }, options?: EditorBlockOptions): {
28
+ ok: true;
29
+ blocks: EditorBlock[];
30
+ elements: SourceElement[];
31
+ } | {
32
+ ok: false;
33
+ problem: string;
34
+ };
35
+ export interface RawSourceInstance extends ElementOuterRange {
36
+ readonly originalHtml: string;
37
+ readonly openingTag: string;
38
+ readonly tagName: string;
39
+ readonly id?: string | undefined;
40
+ readonly nonvisual: boolean;
41
+ readonly descriptor?: CanvasEmbedPlaceholderDescriptor;
42
+ }
43
+ export interface PreparedEditorSource {
44
+ readonly ok: true;
45
+ readonly html: string;
46
+ readonly markerAttribute: string;
47
+ readonly instances: readonly RawSourceInstance[];
48
+ readonly canvasCss: string;
49
+ }
50
+ export type EditorSourcePlan = PreparedEditorSource | {
51
+ readonly ok: false;
52
+ readonly html: string;
53
+ readonly problem: string;
54
+ };
55
+ /**
56
+ * Capture before GrapesJS parses. Only a mount-private lookup token reaches the parser; authored
57
+ * source stays in model metadata. Style/script atoms use inert spans, never executable source tags.
58
+ */
59
+ export declare function prepareEditorSource({ html }: {
60
+ html: string;
61
+ }, options?: EditorBlockOptions): EditorSourcePlan;
62
+ /** Exact authored opening tag and its source offsets; no regex-based HTML parsing or id guessing. */
63
+ export declare function findEditorBlockOpening({ html, target }: {
64
+ html: string;
65
+ target: EditorHtmlTarget;
66
+ }, _options?: object): {
67
+ start: number;
68
+ end: number;
69
+ openingTag: string;
70
+ } | undefined;
71
+ //# sourceMappingURL=editor-blocks.d.ts.map