@hosty-sdk/app 0.32.0 → 0.34.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +14 -0
  2. package/dist/activity-react.js +8 -8
  3. package/dist/app-code-exchange.d.ts +8 -0
  4. package/dist/app-code-exchange.js +31 -0
  5. package/dist/assets/jetbrains-mono-LICENSE.txt +93 -0
  6. package/dist/assets/jetbrains-mono-cyrillic.woff2 +0 -0
  7. package/dist/assets/jetbrains-mono-latin-ext.woff2 +0 -0
  8. package/dist/assets/jetbrains-mono-latin.woff2 +0 -0
  9. package/dist/assets/plex-sans-LICENSE.txt +93 -0
  10. package/dist/assets/plex-sans-cyrillic.woff2 +0 -0
  11. package/dist/assets/plex-sans-latin-ext.woff2 +0 -0
  12. package/dist/assets/plex-sans-latin.woff2 +0 -0
  13. package/dist/auth-notice.d.ts +4 -4
  14. package/dist/auth-notice.js +12 -44
  15. package/dist/browser-auth.d.ts +1 -1
  16. package/dist/browser-auth.js +1 -1
  17. package/dist/chrome-mark.d.ts +3 -0
  18. package/dist/chrome-mark.js +7 -0
  19. package/dist/core-mark-geometry.d.ts +17 -0
  20. package/dist/core-mark-geometry.js +8 -0
  21. package/dist/install-react.js +7 -3
  22. package/dist/overlay-react.js +41 -25
  23. package/dist/permissions-react.js +9 -3
  24. package/dist/react.d.ts +1 -1
  25. package/dist/react.js +10 -19
  26. package/dist/sandbox-react.js +20 -9
  27. package/dist/sandbox-server.d.ts +2 -1
  28. package/dist/sandbox-server.js +5 -4
  29. package/dist/sdk-chrome.d.ts +13 -0
  30. package/dist/sdk-chrome.js +102 -0
  31. package/dist/ui-composition.d.ts +2 -2
  32. package/dist/ui-frame-layer.js +2 -1
  33. package/dist/ui-frame.js +4 -3
  34. package/dist/ui-host.d.ts +2 -1
  35. package/dist/ui-host.js +24 -3
  36. package/dist/ui-navigation-react.js +11 -11
  37. package/dist/ui-react.js +22 -18
  38. package/package.json +4 -3
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { SdkChromeStyles } from "./sdk-chrome.js";
3
4
  import { createContext, useEffect, useLayoutEffect, useSyncExternalStore } from "react";
4
5
  import { HostyAppFrame } from "./ui-frame.js";
5
6
  import { createPortal } from "react-dom";
@@ -83,6 +84,6 @@ export function HostyPresentationLayer({ layer, host, active }) {
83
84
  return createPortal(_jsxs(_Fragment, { children: [viewport?.drawer && _jsx("span", { tabIndex: 0, "aria-hidden": "true", className: "hosty-panel-focus-guard", onFocus: () => focusControls(true) }), surface ? _jsx(HostyAppFrame, { surface: { ...surface, panelId: tool.target.panel.panelId }, view: { viewId: `tool-${tool.presentationId}`, workspaceId: tool.workspaceId, presentationId: tool.presentationId, chrome: { navigation: "hide", tools: { launcher: "hide" } } }, title: state.catalog.find(p => panelKey(p.panel) === tool.key)?.label ?? "Tool", standaloneHref: tool.resolved.standaloneHref, onAskAssistant: viewport?.onAskAssistant, onAuthRequired: viewport?.onAuthRequired, onAttention: count => host.setAttention(tool.presentationId, count), outbound: viewport?.outbound && panelKey(viewport.outbound.target.panel) === tool.key
84
85
  && (viewport.outbound.presentationId ? viewport.outbound.presentationId === tool.presentationId
85
86
  : workspace?.selectedId === tool.presentationId && viewport.outbound.target.route === tool.currentRoute) ? viewport.outbound : null })
86
- : _jsxs("div", { className: "hosty-panel-notice", role: "status", children: ["This tool is unavailable (", tool.resolved.state, ")."] }), viewport?.drawer && _jsx("span", { tabIndex: 0, "aria-hidden": "true", className: "hosty-panel-focus-guard", onFocus: () => focusControls(false) })] }), container, tool.presentationId);
87
+ : _jsxs("div", { "data-hosty-sdk": true, className: "hosty-panel-notice", role: "status", children: [_jsx(SdkChromeStyles, {}), "This tool is unavailable (", tool.resolved.state, ")."] }), viewport?.drawer && _jsx("span", { tabIndex: 0, "aria-hidden": "true", className: "hosty-panel-focus-guard", onFocus: () => focusControls(false) })] }), container, tool.presentationId);
87
88
  })] });
88
89
  }
package/dist/ui-frame.js CHANGED
@@ -1,5 +1,6 @@
1
1
  "use client";
2
- import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { SdkChromeStyles } from "./sdk-chrome.js";
3
4
  import { useContext, useEffect, useLayoutEffect, useRef, useState } from "react";
4
5
  import { UiHostContext } from "./ui-context.js";
5
6
  import { appendThemeLaunchParams, parseActiveFrameAskAssistant, parseActiveFrameAttention, parseActiveFrameAuthRequired, parseActiveFramePermissionReview } from "./embedder.js";
@@ -135,6 +136,6 @@ export function HostyAppFrame(props) {
135
136
  }, [outboundReady, connection.generation, props.outbound?.nonce, surface.origin]);
136
137
  const insecure = typeof window !== "undefined" && window.location.protocol === "https:" && new URL(surface.origin).protocol === "http:";
137
138
  if (insecure)
138
- return _jsxs("div", { role: "status", className: "hosty-panel-notice", children: [_jsxs("strong", { children: [title, " needs HTTPS to appear here"] }), _jsx("p", { children: "This app publishes an HTTP address. Configure its public HTTPS origin in Hosty." }), /^(localhost|127\.|\[::1\])/.test(new URL(surface.origin).hostname) && _jsx("p", { children: "This local address is reachable only from the Hosty computer." }), standaloneHref && _jsx("a", { href: standaloneHref, target: "_blank", rel: "noopener noreferrer", children: "Open separately" })] });
139
- return _jsxs(_Fragment, { children: [review && _jsx("a", { className: "hosty-panel-review", href: review, target: "_blank", rel: "noopener noreferrer", children: "Review access in Core" }), _jsx("iframe", { ref: ref, className: className, title: title, src: launch, sandbox: frameSandbox(surface.framePolicy), allow: surface.framePolicy.microphone ? "clipboard-write; microphone 'src'" : "clipboard-write", style: { border: 0, height: "100%", width: "100%", colorScheme: resolvedTheme }, onLoad: () => setLoaded(true) })] });
139
+ return _jsxs("div", { "data-hosty-sdk": true, role: "status", className: "hosty-panel-notice", children: [_jsx(SdkChromeStyles, {}), _jsxs("strong", { children: [title, " needs HTTPS to appear here"] }), _jsx("p", { children: "This app publishes an HTTP address. Configure its public HTTPS origin in Hosty." }), /^(localhost|127\.|\[::1\])/.test(new URL(surface.origin).hostname) && _jsx("p", { children: "This local address is reachable only from the Hosty computer." }), standaloneHref && _jsx("a", { href: standaloneHref, target: "_blank", rel: "noopener noreferrer", children: "Open separately" })] });
140
+ return _jsxs(_Fragment, { children: [review && _jsxs(_Fragment, { children: [_jsx(SdkChromeStyles, {}), _jsx("a", { "data-hosty-sdk": true, className: "hosty-panel-review", href: review, target: "_blank", rel: "noopener noreferrer", children: "Review access in Core" })] }), _jsx("iframe", { ref: ref, className: className, title: title, src: launch, sandbox: frameSandbox(surface.framePolicy), allow: surface.framePolicy.microphone ? "clipboard-write; microphone 'src'" : "clipboard-write", style: { border: 0, height: "100%", width: "100%", colorScheme: resolvedTheme }, onLoad: () => setLoaded(true) })] });
140
141
  }
package/dist/ui-host.d.ts CHANGED
@@ -112,7 +112,8 @@ export declare class UiHost implements UiClient {
112
112
  signal?: AbortSignal;
113
113
  }) => Promise<PresentationResult>;
114
114
  private status;
115
- selectPanel: (ref: PanelRef, workspaceId?: string) => Promise<PresentationResult>;
115
+ selectPanel: (ref: PanelRef, workspaceId?: string, signal?: AbortSignal) => Promise<PresentationResult>;
116
+ private selectPanelWithSignal;
116
117
  setExpanded: (expanded: boolean, workspaceId?: string) => void;
117
118
  setWidth: (width: number, workspaceId?: string) => void;
118
119
  setAttention: (panel: PanelRef | string, attention: number) => void;
package/dist/ui-host.js CHANGED
@@ -449,8 +449,10 @@ export class UiHost {
449
449
  return operation;
450
450
  };
451
451
  openSurface = (target, options) => waitForPresentation(this.open(target, options, "local"), options.requestId, options.signal);
452
- open(target, options, source, frame, sourceWorkspace) {
452
+ open(target, options, source, frame, sourceWorkspace, selectionSignal) {
453
453
  const id = options.requestId;
454
+ if (selectionSignal?.aborted)
455
+ return Promise.resolve({ status: "blocked", requestId: id, code: "presentation_cancelled" });
454
456
  if (!this.active || !this.activityReady || this.disposed)
455
457
  return Promise.resolve({ status: "blocked", requestId: id, code: "activity_required" });
456
458
  if (!validId(id) || !validTarget(target) || options.placement !== "side" || options.reuse !== undefined && !["exact", "new"].includes(options.reuse))
@@ -503,6 +505,8 @@ export class UiHost {
503
505
  try {
504
506
  const own = await this.bootstrap(generation);
505
507
  const check = () => {
508
+ if (selectionSignal?.aborted)
509
+ throw new UiError("presentation_cancelled", "This selection was cancelled.");
506
510
  this.checkActivity(generation);
507
511
  this.requireTools(frame);
508
512
  if (!own.canHost)
@@ -763,7 +767,21 @@ export class UiHost {
763
767
  return failure(id, error);
764
768
  }
765
769
  }
766
- selectPanel = async (ref, workspaceId = DEFAULT_WORKSPACE) => {
770
+ // Host-owned selections may cancel before presentation without superseding another selection.
771
+ // PresentationOptions.signal still cancels only a protocol caller's wait.
772
+ selectPanel = async (ref, workspaceId = DEFAULT_WORKSPACE, signal) => {
773
+ const cancel = () => this.invalidateRendererWork();
774
+ signal?.addEventListener("abort", cancel);
775
+ try {
776
+ return await this.selectPanelWithSignal(ref, workspaceId, signal);
777
+ }
778
+ finally {
779
+ signal?.removeEventListener("abort", cancel);
780
+ }
781
+ };
782
+ selectPanelWithSignal = async (ref, workspaceId, signal) => {
783
+ if (signal?.aborted)
784
+ return { status: "blocked", requestId: createPresentationRequestId(), code: "presentation_cancelled" };
767
785
  const opened = this.snapshot.tools.filter(t => t.workspaceId === workspaceId && t.key === panelKey(ref)).sort((a, b) => b.focusedAt - a.focusedAt)[0];
768
786
  if (opened?.resolved.surface) {
769
787
  const requestId = createPresentationRequestId();
@@ -778,6 +796,8 @@ export class UiHost {
778
796
  const resolved = await uiRequest(this.path, "panels/resolve", opened.target);
779
797
  this.checkActivity(generation);
780
798
  this.requireTools();
799
+ if (signal?.aborted)
800
+ throw new UiError("presentation_cancelled", "This selection was cancelled.");
781
801
  if (selection !== state.generation)
782
802
  throw new UiError("presentation_superseded", "A newer action replaced this opening.");
783
803
  if (!resolved.available || !resolved.surface)
@@ -802,7 +822,8 @@ export class UiHost {
802
822
  else if (opened)
803
823
  this.closePresentation(opened.presentationId);
804
824
  const requestId = createPresentationRequestId();
805
- return waitForPresentation(this.open({ panel: ref }, { placement: "side", requestId }, "local", undefined, workspaceId), requestId);
825
+ const opening = this.open({ panel: ref }, { placement: "side", requestId }, "local", undefined, workspaceId, signal);
826
+ return signal ? opening : waitForPresentation(opening, requestId);
806
827
  };
807
828
  setExpanded = (expanded, workspaceId = DEFAULT_WORKSPACE) => {
808
829
  if (!expanded)
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useContext, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
4
+ import { SdkChromeStyles, SdkIcon } from "./sdk-chrome.js";
4
5
  import { ui, createPresentationRequestId } from "./ui.js";
5
6
  import { INITIAL_CHROME, INITIAL_NAVIGATION } from "./ui-composition.js";
6
7
  import { UiCompositionContext } from "./ui-context.js";
@@ -22,7 +23,7 @@ export function HostyToolLauncher() {
22
23
  if (chrome.tools.owner === "local" || !chrome.tools.enabled || chrome.tools.launcher === "hide"
23
24
  || !chrome.capabilities.includes(UI_COMPOSITION_CAPABILITY))
24
25
  return null;
25
- return _jsxs("div", { className: "hosty-child-tools", children: [_jsx("button", { type: "button", disabled: chrome.tools.owner !== "host", onClick: async () => {
26
+ return _jsxs("div", { "data-hosty-sdk": true, className: "hosty-child-tools", children: [_jsx(SdkChromeStyles, {}), _jsx("style", { children: navigationStyles }), _jsx("button", { type: "button", disabled: chrome.tools.owner !== "host", onClick: async () => {
26
27
  const result = await ui.openToolPicker({ requestId: createPresentationRequestId() });
27
28
  setError(result.status === "accepted" ? null : "Tools are unavailable. Reconnect to your host or open the app separately.");
28
29
  }, children: "Tools" }), error && _jsx("span", { role: "status", children: error })] });
@@ -71,19 +72,18 @@ export function HostyNavigation({ className = "" }) {
71
72
  }, children: [entry.icon && _jsx("img", { src: entry.icon, alt: "", width: 18, height: 18 }), _jsx("span", { children: entry.label })] }, entry.key)), error && _jsx("p", { role: "status", children: error })] });
72
73
  if (empty)
73
74
  return null;
74
- return _jsxs(HostyNavigationBoundary, { children: [_jsx("style", { children: navigationStyles }), _jsx("div", { className: `hosty-navigation ${className}`, children: narrow ? _jsxs(_Fragment, { children: [_jsx("button", { ref: opener, type: "button", "aria-label": "Page navigation", "aria-expanded": expanded, onClick: () => setExpanded(true), children: "\u2630" }), _jsxs("dialog", { ref: dialog, className: "hosty-navigation-dialog", "aria-label": "Page navigation", onCancel: () => setExpanded(false), children: [_jsx("button", { type: "button", onClick: () => setExpanded(false), children: "Close navigation" }), _jsx("nav", { "aria-label": "App pages", children: links })] })] }) : _jsx("nav", { "aria-label": "App pages", children: links }) })] });
75
+ return _jsxs(HostyNavigationBoundary, { children: [_jsx(SdkChromeStyles, {}), _jsx("style", { children: navigationStyles }), _jsx("div", { "data-hosty-sdk": true, className: `hosty-navigation ${className}`, children: narrow ? _jsxs(_Fragment, { children: [_jsx("button", { ref: opener, type: "button", "aria-label": "Page navigation", "aria-expanded": expanded, onClick: () => setExpanded(true), children: _jsx(SdkIcon, { name: "menu" }) }), _jsxs("dialog", { "data-hosty-sdk": true, ref: dialog, className: "hosty-navigation-dialog", "aria-label": "Page navigation", onCancel: () => setExpanded(false), children: [_jsx("button", { type: "button", onClick: () => setExpanded(false), children: "Close navigation" }), _jsx("nav", { "aria-label": "App pages", children: links })] })] }) : _jsx("nav", { "aria-label": "App pages", children: links }) })] });
75
76
  }
76
77
  const navigationStyles = `
77
78
  [data-hosty-navigation=hide] [data-hosty-navigation-boundary]{display:none!important}
78
- .hosty-app-layout{display:flex;width:100%;height:100%;min-height:0;min-width:0}
79
- .hosty-app-content{flex:1;min-width:0;min-height:0;overflow:auto;outline:none}
80
- .hosty-navigation{flex:0 0 190px;box-sizing:border-box;padding:12px;border-right:1px solid color-mix(in srgb,currentColor 14%,transparent);font:14px system-ui}
79
+ .hosty-app-layout{display:flex;width:100%;height:100%;min-height:0;min-width:0}.hosty-app-content{flex:1;min-width:0;min-height:0;overflow:auto;outline:none}
80
+ .hosty-navigation{flex:0 0 190px;box-sizing:border-box;padding:12px;border-right:1px solid var(--hs-border);background:var(--hs-bg)}
81
81
  .hosty-navigation nav,.hosty-navigation-dialog nav{display:flex;flex-direction:column;gap:4px}
82
- .hosty-navigation a,.hosty-navigation-dialog a{display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:6px;text-decoration:none;color:inherit}
83
- .hosty-navigation a[aria-current=page],.hosty-navigation-dialog a[aria-current=page]{background:color-mix(in srgb,currentColor 10%,transparent);font-weight:600}
84
- .hosty-navigation a[aria-disabled=true]{opacity:.55;cursor:not-allowed}
85
- .hosty-navigation button,.hosty-child-tools button,.hosty-navigation-dialog button{font:inherit;color:inherit;background:transparent;border:1px solid color-mix(in srgb,currentColor 20%,transparent);border-radius:6px;padding:7px 10px;cursor:pointer}
86
- .hosty-navigation-dialog{position:fixed;inset:0 auto 0 0;margin:0;height:100dvh;width:min(300px,85vw);box-sizing:border-box;border:0;padding:16px;background:Canvas;color:CanvasText}
87
- .hosty-navigation-dialog::backdrop{background:#0007}.hosty-child-tools{padding:6px 10px}
82
+ .hosty-navigation a,.hosty-navigation-dialog a{display:flex;align-items:center;gap:8px;padding:8px 10px;border-radius:8px;text-decoration:none;color:inherit;font:500 14px/20px var(--hs-font);min-width:0;transition:background-color 120ms ease-out}.hosty-navigation a>span,.hosty-navigation-dialog a>span{min-width:0;overflow-wrap:anywhere}.hosty-navigation a>img,.hosty-navigation-dialog a>img{flex:none}
83
+ .hosty-navigation a:hover,.hosty-navigation-dialog a:hover{background:var(--hs-brand-soft)}.hosty-navigation a[aria-current=page],.hosty-navigation-dialog a[aria-current=page]{background:var(--hs-brand-soft);font-weight:500}
84
+ .hosty-navigation a[aria-disabled=true],.hosty-navigation-dialog a[aria-disabled=true]{opacity:.5;cursor:not-allowed}
85
+ .hosty-navigation button,.hosty-child-tools button,.hosty-navigation-dialog button{display:inline-flex;align-items:center;justify-content:center;gap:8px;font:500 14px/20px var(--hs-font);color:inherit;background:var(--hs-card);border:1px solid var(--hs-input);border-radius:8px;padding:7px 10px;cursor:pointer}.hosty-navigation button:hover,.hosty-child-tools button:hover,.hosty-navigation-dialog button:hover{background:var(--hs-brand-soft)}.hosty-child-tools button:disabled{opacity:.5;cursor:not-allowed}
86
+ .hosty-navigation-dialog{position:fixed;inset:0 auto 0 0;margin:0;height:100dvh;max-height:none;width:min(300px,85vw);box-sizing:border-box;border:0;border-right:1px solid var(--hs-border);padding:16px;background:var(--hs-card);color:var(--hs-fg)}.hosty-navigation-dialog>button{margin-bottom:16px}
87
+ .hosty-navigation-dialog::backdrop{background:#0F1B2D57}.hosty-child-tools{padding:6px 10px}.hosty-child-tools>[role=status]{display:inline-block;margin:0 8px;color:var(--hs-muted);font-size:13px}
88
88
  @media(max-width:899px){.hosty-app-layout{flex-direction:column}.hosty-navigation{flex:0 0 auto;border-right:0;padding:6px 10px}}
89
89
  `;
package/dist/ui-react.js CHANGED
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { SdkChromeStyles, SdkIcon } from "./sdk-chrome.js";
3
4
  import { useContext, useEffect, useId, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
4
5
  import { bindUiClient, ui } from "./ui.js";
5
6
  import { createUiHost } from "./ui-host.js";
@@ -52,7 +53,7 @@ export function HostyUiRoot({ children, uiPath, panelLayout = "auto", active = t
52
53
  if (allowed)
53
54
  mounted.current = true;
54
55
  const content = _jsxs("div", { className: "hosty-app-layout", children: [navigation?.renderer !== "custom" && _jsx(HostyNavigation, {}), _jsxs("div", { className: "hosty-app-content", "data-hosty-content": true, tabIndex: -1, children: [_jsx(HostyToolLauncher, {}), children] })] });
55
- return _jsx(UiHostContext.Provider, { value: host, children: _jsx(UiCompositionContext.Provider, { value: clientRef.current?.composition ?? null, children: _jsxs(UiFrameLayerContext.Provider, { value: layer, children: [_jsx("style", { children: `.hosty-app-layout{display:flex;width:100%;height:100%;min-width:0;min-height:0}.hosty-app-content{flex:1;min-width:0;min-height:0;overflow:auto;outline:none}` }), _jsx("div", { hidden: !allowed, inert: !allowed, style: { display: allowed ? "contents" : "none" }, children: mounted.current && (panelLayout === "auto" && host ? _jsx(HostyPanelWorkspace, { children: content }) : content) }), !allowed && _jsx("div", { role: "status", className: "hosty-panel-notice", style: { padding: 24, font: "14px system-ui" }, children: environment?.connection === "blocked" ? _jsxs(_Fragment, { children: [_jsx("strong", { children: environment.code === "actor_mismatch" ? "Use the same Hosty account in this app and its host" : "Cannot verify this app’s host" }), _jsx("p", { children: "Check your account and access in Hosty Core, then reconnect. Your existing discussion is unchanged." }), _jsx("button", { type: "button", onClick: () => window.location.reload(), children: "Reconnect" })] }) : "Connecting to the workspace…" }), host && _jsx(HostyPresentationLayer, { layer: layer, host: host, active: !!allowed })] }) }) });
56
+ return _jsx(UiHostContext.Provider, { value: host, children: _jsx(UiCompositionContext.Provider, { value: clientRef.current?.composition ?? null, children: _jsxs(UiFrameLayerContext.Provider, { value: layer, children: [_jsx("style", { children: `.hosty-app-layout{display:flex;width:100%;height:100%;min-width:0;min-height:0}.hosty-app-content{flex:1;min-width:0;min-height:0;overflow:auto;outline:none}` }), _jsx("div", { hidden: !allowed, inert: !allowed, style: { display: allowed ? "contents" : "none" }, children: mounted.current && (panelLayout === "auto" && host ? _jsx(HostyPanelWorkspace, { children: content }) : content) }), !allowed && _jsxs("div", { "data-hosty-sdk": true, role: "status", className: "hosty-panel-notice", children: [_jsx(SdkChromeStyles, {}), environment?.connection === "blocked" ? _jsxs(_Fragment, { children: [_jsx("strong", { children: environment.code === "actor_mismatch" ? "Use the same Hosty account in this app and its host" : "Cannot verify this app’s host" }), _jsx("p", { children: "Check your account and access in Hosty Core, then reconnect. Your existing discussion is unchanged." }), _jsx("button", { type: "button", onClick: () => window.location.reload(), children: "Reconnect" })] }) : "Connecting to the workspace…"] }), host && _jsx(HostyPresentationLayer, { layer: layer, host: host, active: !!allowed })] }) }) });
56
57
  }
57
58
  const empty = { bootstrap: null, catalog: [], tools: [], selected: null, expanded: false, width: 360, error: null };
58
59
  const noop = () => () => { };
@@ -243,14 +244,14 @@ export function HostyPanelWorkspace({ children, workspaceId = DEFAULT_WORKSPACE,
243
244
  handle.addEventListener("pointerup", end);
244
245
  handle.addEventListener("lostpointercapture", end);
245
246
  };
246
- return _jsxs("div", { ref: root, className: `hosty-panel-workspace ${className}`, "data-hosty-panel-layout": drawer ? "drawer" : "dock", style: { "--hosty-panel-width": `${width}px`, ...(unsupported ? { display: "contents" } : {}) }, children: [_jsx("style", { children: styles }), _jsx("div", { ref: content, className: "hosty-panel-document", style: unsupported ? { display: "contents" } : undefined, inert: expanded && drawer, children: children }), _jsxs("nav", { className: "hosty-panel-rail", "aria-label": "Tools", style: unsupported || !chrome.tools.enabled || chrome.tools.launcher === "hide" ? { display: "none" } : undefined, inert: unsupported || expanded && drawer, children: [tabs.map((panel, index) => _jsxs("button", { type: "button", title: `${panel.appName}: ${panel.label}`, "aria-label": panel.label, "aria-controls": bodyId, "aria-expanded": expanded && selectedKey === panelKey(panel.panel), onClick: () => void select(panel), onKeyDown: event => {
247
+ return _jsxs("div", { ref: root, className: `hosty-panel-workspace ${className}`, "data-hosty-panel-layout": drawer ? "drawer" : "dock", style: { "--hosty-panel-width": `${width}px`, ...(unsupported ? { display: "contents" } : {}) }, children: [_jsx(SdkChromeStyles, {}), _jsx("style", { children: styles }), _jsx("div", { ref: content, className: "hosty-panel-document", style: unsupported ? { display: "contents" } : undefined, inert: expanded && drawer, children: children }), _jsxs("nav", { "data-hosty-sdk": true, className: "hosty-panel-rail", "aria-label": "Tools", style: unsupported || !chrome.tools.enabled || chrome.tools.launcher === "hide" ? { display: "none" } : undefined, inert: unsupported || expanded && drawer, children: [tabs.map((panel, index) => _jsxs("button", { type: "button", title: `${panel.appName}: ${panel.label}`, "aria-label": panel.label, "aria-controls": bodyId, "aria-expanded": expanded && selectedKey === panelKey(panel.panel), onClick: () => void select(panel), onKeyDown: event => {
247
248
  const delta = event.key === "ArrowDown" ? 1 : event.key === "ArrowUp" ? -1 : 0;
248
249
  const next = event.key === "Home" ? 0 : event.key === "End" ? tabs.length - 1 : delta ? (index + delta + tabs.length) % tabs.length : null;
249
250
  if (next !== null) {
250
251
  event.preventDefault();
251
252
  event.currentTarget.parentElement?.children[next]?.focus();
252
253
  }
253
- }, children: [renderIcon?.(panel) ?? panel.label.slice(0, 2), workspaceTools.some(t => t.key === panelKey(panel.panel) && t.attention > 0) ? _jsx("span", { className: "hosty-panel-attention", "aria-label": "Needs attention", children: "\u2022" }) : null] }, panelKey(panel.panel))), _jsx("button", { type: "button", "aria-label": "Add tool", title: "Add tool", onClick: () => { setError(null); setChooser(true); void host?.refresh(); }, children: "+" })] }), expanded && drawer && _jsx("button", { type: "button", className: "hosty-panel-backdrop", "aria-label": "Return to document", onClick: () => host?.setExpanded(false, workspaceId) }), _jsxs("aside", { ref: body, id: bodyId, className: "hosty-panel-body", hidden: !expanded, inert: !expanded, role: drawer ? "dialog" : "complementary", "aria-modal": drawer && expanded ? true : undefined, "aria-label": selected?.label ?? "Tool", onKeyDown: event => {
254
+ }, children: [renderIcon?.(panel) ?? panel.label.slice(0, 2), workspaceTools.some(t => t.key === panelKey(panel.panel) && t.attention > 0) ? _jsx("span", { className: "hosty-panel-attention", "aria-label": "Needs attention", children: _jsx(SdkIcon, { name: "warning" }) }) : null] }, panelKey(panel.panel))), _jsx("button", { type: "button", "aria-label": "Add tool", title: "Add tool", onClick: () => { setError(null); setChooser(true); void host?.refresh(); }, children: _jsx(SdkIcon, { name: "plus" }) })] }), expanded && drawer && _jsx("button", { type: "button", className: "hosty-panel-backdrop", "aria-label": "Return to document", onClick: () => host?.setExpanded(false, workspaceId) }), _jsxs("aside", { "data-hosty-sdk": true, ref: body, id: bodyId, className: "hosty-panel-body", hidden: !expanded, inert: !expanded, role: drawer ? "dialog" : "complementary", "aria-modal": drawer && expanded ? true : undefined, "aria-label": selected?.label ?? "Tool", onKeyDown: event => {
254
255
  if (!drawer)
255
256
  return;
256
257
  if (event.key === "Escape") {
@@ -279,24 +280,27 @@ export function HostyPanelWorkspace({ children, workspaceId = DEFAULT_WORKSPACE,
279
280
  event.preventDefault();
280
281
  host?.setWidth(width + (event.key === "ArrowLeft" ? 16 : -16), workspaceId);
281
282
  }
282
- } }), _jsxs("header", { children: [_jsx("strong", { children: selected?.label ?? "Tool" }), tool?.resolved.standaloneHref && _jsx("a", { href: tool.resolved.standaloneHref, target: "_blank", rel: "noopener noreferrer", title: "Open separately", "aria-label": "Open separately", children: "\u2197" }), (drawer || minimizeControl === "always") && _jsx("button", { ref: minimize, type: "button", onClick: () => host?.setExpanded(false, workspaceId), "aria-label": "Minimize panel", title: "Minimize panel (keep tool open)", children: _jsx("span", { "aria-hidden": "true", children: "\u2212" }) })] }), tool?.resolved.state === "degraded" && _jsx("div", { className: "hosty-panel-state", role: "status", children: "This app\u2019s health check is failing. Its page may still work." }), tool?.readiness === "unreported" && _jsxs("div", { className: "hosty-panel-state", role: "status", children: ["This tool has not confirmed that it loaded. If the page is blank or refuses to connect, use ", _jsx("a", { href: tool.resolved.standaloneHref ?? undefined, target: "_blank", rel: "noopener noreferrer", children: "Open separately" }), "."] }), tool?.readiness === "auth-required" && _jsx("div", { className: "hosty-panel-state", role: "status", children: "Check access in the tool below, then retry opening the discussion." }), tool && workspaceTools.filter(t => t.key === tool.key).length > 1 && _jsxs("label", { className: "hosty-panel-state", children: ["Open views", _jsx("select", { "aria-label": "Tool instance", value: tool.presentationId, onChange: event => host?.focusPresentation(event.target.value), children: workspaceTools.filter(t => t.key === tool.key).map((item, index) => _jsxs("option", { value: item.presentationId, children: [selected?.label ?? "Tool", " ", index + 1] }, item.presentationId)) })] }), _jsx("div", { ref: viewport, className: "hosty-panel-viewport", "aria-owns": tool ? `hosty-presentation-${tool.presentationId}` : undefined }), drawer && expanded && _jsx("span", { tabIndex: 0, "aria-hidden": "true", className: "hosty-panel-focus-guard", onFocus: () => minimize.current?.focus() })] }), _jsxs("dialog", { ref: chooserRef, className: "hosty-tool-chooser", onCancel: () => setChooser(false), "aria-label": "Add tool", children: [_jsxs("header", { children: [_jsx("strong", { children: "Add tool" }), _jsx("button", { type: "button", onClick: () => setChooser(false), "aria-label": "Close tool chooser", children: "\u00D7" })] }), _jsx("p", { children: "Open a tool beside your document." }), error && _jsx("p", { role: "status", children: error }), state.error && _jsx("p", { role: "status", children: state.error.message }), !state.bootstrap?.canHost && _jsx("p", { children: "This app needs optional panel access in Hosty Core to show tools here. You can still use the app." }), state.bootstrap?.canHost && !state.catalog.length && _jsx("p", { children: "No tools are available for your account." }), _jsx("ul", { children: state.catalog.map(panel => _jsxs("li", { children: [_jsxs("button", { type: "button", onClick: () => void select(panel), children: [_jsx("strong", { children: panel.label }), _jsxs("span", { children: [panel.appName, panel.available ? "" : ` · ${panel.state}`] })] }), !panel.available && panel.canStart && onStartApp && _jsx("button", { type: "button", onClick: () => void Promise.resolve(onStartApp(panel.panel.appId)).then(() => host?.refresh()), children: "Start app" })] }, panelKey(panel.panel))) }), _jsx("button", { type: "button", onClick: () => void host?.refresh(), children: "Refresh tools" })] }), error && !chooser && _jsxs("div", { className: "hosty-panel-error", role: "status", children: [error, " ", _jsx("button", { type: "button", onClick: () => { setChooser(true); setError(null); }, children: "Choose tool" })] })] });
283
+ } }), _jsxs("header", { children: [_jsx("strong", { children: selected?.label ?? "Tool" }), tool?.resolved.standaloneHref && _jsx("a", { href: tool.resolved.standaloneHref, target: "_blank", rel: "noopener noreferrer", title: "Open separately", "aria-label": "Open separately", children: _jsx(SdkIcon, { name: "external" }) }), (drawer || minimizeControl === "always") && _jsx("button", { ref: minimize, type: "button", onClick: () => host?.setExpanded(false, workspaceId), "aria-label": "Minimize panel", title: "Minimize panel (keep tool open)", children: _jsx(SdkIcon, { name: "minus" }) })] }), tool?.resolved.state === "degraded" && _jsx("div", { className: "hosty-panel-state", role: "status", children: "This app\u2019s health check is failing. Its page may still work." }), tool?.readiness === "unreported" && _jsxs("div", { className: "hosty-panel-state", role: "status", children: ["This tool has not confirmed that it loaded. If the page is blank or refuses to connect, use ", _jsx("a", { href: tool.resolved.standaloneHref ?? undefined, target: "_blank", rel: "noopener noreferrer", children: "Open separately" }), "."] }), tool?.readiness === "auth-required" && _jsx("div", { className: "hosty-panel-state", role: "status", children: "Check access in the tool below, then retry opening the discussion." }), tool && workspaceTools.filter(t => t.key === tool.key).length > 1 && _jsxs("label", { className: "hosty-panel-state", children: ["Open views", _jsx("select", { "aria-label": "Tool instance", value: tool.presentationId, onChange: event => host?.focusPresentation(event.target.value), children: workspaceTools.filter(t => t.key === tool.key).map((item, index) => _jsxs("option", { value: item.presentationId, children: [selected?.label ?? "Tool", " ", index + 1] }, item.presentationId)) })] }), _jsx("div", { ref: viewport, className: "hosty-panel-viewport", "aria-owns": tool ? `hosty-presentation-${tool.presentationId}` : undefined }), drawer && expanded && _jsx("span", { tabIndex: 0, "aria-hidden": "true", className: "hosty-panel-focus-guard", onFocus: () => minimize.current?.focus() })] }), _jsxs("dialog", { "data-hosty-sdk": true, ref: chooserRef, className: "hosty-tool-chooser", onCancel: () => setChooser(false), "aria-label": "Add tool", children: [_jsxs("header", { children: [_jsx("strong", { children: "Add tool" }), _jsx("button", { type: "button", onClick: () => setChooser(false), "aria-label": "Close tool chooser", children: _jsx(SdkIcon, { name: "close" }) })] }), _jsx("p", { children: "Open a tool beside your document." }), error && _jsx("p", { role: "status", children: error }), state.error && _jsx("p", { role: "status", children: state.error.message }), !state.bootstrap?.canHost && _jsx("p", { children: "This app needs optional panel access in Hosty Core to show tools here. You can still use the app." }), state.bootstrap?.canHost && !state.catalog.length && _jsx("p", { children: "No tools are available for your account." }), _jsx("ul", { children: state.catalog.map(panel => _jsxs("li", { children: [_jsxs("button", { type: "button", onClick: () => void select(panel), children: [_jsx("strong", { children: panel.label }), _jsxs("span", { children: [panel.appName, panel.available ? "" : ` · ${panel.state}`] })] }), !panel.available && panel.canStart && onStartApp && _jsx("button", { type: "button", onClick: () => void Promise.resolve(onStartApp(panel.panel.appId)).then(() => host?.refresh()), children: "Start app" })] }, panelKey(panel.panel))) }), _jsx("button", { type: "button", onClick: () => void host?.refresh(), children: "Refresh tools" })] }), error && !chooser && _jsxs("div", { "data-hosty-sdk": true, className: "hosty-panel-error", role: "status", children: [error, " ", _jsx("button", { type: "button", onClick: () => { setChooser(true); setError(null); }, children: "Choose tool" })] })] });
283
284
  }
284
285
  const styles = `
285
286
  .hosty-panel-viewport{flex:1;min-height:0;min-width:0}
286
287
  .hosty-app-layout{display:flex;width:100%;height:100%;min-width:0;min-height:0}.hosty-app-content{flex:1;min-width:0;min-height:0;overflow:auto}
287
-
288
- .hosty-panel-workspace{--hp-bg:var(--hosty-panel-background,#f8f9fb);--hp-fg:var(--hosty-panel-foreground,#20242c);--hp-muted:var(--hosty-panel-muted,#606875);--hp-border:var(--hosty-panel-border,#e0e4eb);display:flex;position:relative;min-width:0;min-height:0;width:100%;height:100dvh;overflow:hidden;background:var(--hp-bg);color:var(--hp-fg)}
289
- html.dark .hosty-panel-workspace,html[data-hosty-theme=dark] .hosty-panel-workspace{--hp-bg:var(--hosty-panel-background,#14171c);--hp-fg:var(--hosty-panel-foreground,#f0f2f5);--hp-muted:var(--hosty-panel-muted,#a2aab7);--hp-border:var(--hosty-panel-border,#323843)}
290
- .hosty-panel-document{flex:1;min-width:0;min-height:0;overflow:auto}.hosty-panel-rail{order:3;flex:0 0 48px;display:flex;flex-direction:column;align-items:center;gap:8px;padding:10px 0;border-left:1px solid var(--hp-border);overflow:auto}
291
- .hosty-panel-workspace button,.hosty-panel-workspace a{font:inherit}.hosty-panel-rail button,.hosty-panel-body header button,.hosty-panel-body header a,.hosty-tool-chooser header button{position:relative;display:grid;place-items:center;flex-shrink:0;border:1px solid transparent;border-radius:8px;width:32px;height:32px;padding:0;background:transparent;color:inherit;font:500 13px system-ui;cursor:pointer;text-decoration:none}
292
- .hosty-panel-rail button:hover,.hosty-panel-rail button[aria-expanded=true]{border-color:var(--hp-border);background:color-mix(in srgb,var(--hp-fg) 8%,transparent)}
293
- .hosty-panel-workspace :focus-visible{outline:2px solid #5286ee;outline-offset:2px}.hosty-panel-attention{position:absolute;top:0;right:1px;color:#e9a52b;font-size:20px}
294
- .hosty-panel-body{order:2;display:flex;flex:0 0 var(--hosty-panel-width);flex-direction:column;position:relative;min-width:0;min-height:0;margin-left:12px;border-left:1px solid var(--hp-border);background:var(--hp-bg)}
295
- .hosty-panel-body[hidden],.hosty-panel-frame[hidden]{display:none}.hosty-panel-body header,.hosty-tool-chooser header{display:flex;align-items:center;gap:6px;min-height:44px;padding:0 10px;border-bottom:1px solid var(--hp-border);font:500 13px system-ui}.hosty-panel-body header strong,.hosty-tool-chooser header strong{flex:1}
288
+ .hosty-panel-workspace{display:flex;position:relative;min-width:0;min-height:0;width:100%;height:100dvh;overflow:hidden}
289
+ :is(.hosty-panel-rail,.hosty-panel-body,.hosty-tool-chooser,.hosty-panel-error){--hp-bg:var(--hosty-panel-background,var(--hs-bg));--hp-fg:var(--hosty-panel-foreground,var(--hs-fg));--hp-muted:var(--hosty-panel-muted,var(--hs-muted));--hp-border:var(--hosty-panel-border,var(--hs-border));background:var(--hp-bg);color:var(--hp-fg)}
290
+ .hosty-panel-document{flex:1;min-width:0;min-height:0;overflow:auto}
291
+ .hosty-panel-rail{order:3;flex:0 0 48px;display:flex;flex-direction:column;align-items:center;gap:8px;padding:10px 0;border-left:1px solid var(--hp-border);overflow:auto}
292
+ .hosty-panel-rail button,.hosty-panel-body header button,.hosty-panel-body header a,.hosty-tool-chooser header button{position:relative;display:grid;place-items:center;flex-shrink:0;border:1px solid transparent;border-radius:8px;width:32px;height:32px;padding:0;background:transparent;color:inherit;font:500 13px var(--hs-font);cursor:pointer;text-decoration:none;transition:background-color 120ms ease-out,color 120ms ease-out}
293
+ .hosty-panel-rail button:hover,.hosty-panel-body header :is(button,a):hover,.hosty-tool-chooser header button:hover{background:var(--hs-brand-soft);color:var(--hs-fg)}
294
+ .hosty-panel-rail button[aria-expanded=true]{background:var(--hs-brand);color:var(--hs-brand-fg)}
295
+ .hosty-panel-attention{position:absolute;top:-2px;right:-2px;display:block;background:var(--hp-bg);border-radius:50%;color:var(--hs-warning)}.hosty-panel-attention>.hosty-sdk-icon{width:12px;height:12px}
296
+ .hosty-panel-body{order:2;display:flex;flex:0 0 var(--hosty-panel-width);flex-direction:column;position:relative;min-width:0;min-height:0;margin-left:12px;border-left:1px solid var(--hp-border)}
297
+ .hosty-panel-body[hidden],.hosty-panel-frame[hidden]{display:none}
298
+ .hosty-panel-body header,.hosty-tool-chooser header{display:flex;align-items:center;gap:6px;min-height:44px;padding:0 10px;border-bottom:1px solid var(--hp-border);font:500 14px/20px var(--hs-font)}.hosty-panel-body header strong,.hosty-tool-chooser header strong{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
296
299
  .hosty-panel-focus-guard{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
297
- .hosty-panel-frame{position:relative;flex:1;min-height:0;overflow:hidden}.hosty-panel-frame iframe{display:block}.hosty-panel-resize{position:absolute;left:-12px;top:0;bottom:0;width:12px;cursor:col-resize;touch-action:none}.hosty-panel-resize:hover{background:color-mix(in srgb,var(--hp-fg) 8%,transparent)}
298
- .hosty-panel-state{padding:8px 12px;font:12px/1.4 system-ui;border-bottom:1px solid var(--hp-border);color:var(--hp-muted)}.hosty-panel-state a{text-decoration:underline}
299
- .hosty-panel-workspace[data-hosty-panel-layout=drawer] .hosty-panel-body{position:absolute;inset:0 0 0 auto;width:min(440px,100%);z-index:102;margin:0;box-shadow:-8px 0 30px #0002}.hosty-panel-workspace[data-hosty-panel-layout=drawer] .hosty-panel-resize{display:none}
300
- .hosty-panel-backdrop{position:absolute;inset:0;border:0;background:#0005;z-index:101;cursor:pointer}.hosty-panel-notice{padding:24px;font:14px/1.5 system-ui}.hosty-panel-review{position:absolute;top:8px;right:8px;z-index:2;padding:10px;background:var(--hp-bg);border:1px solid var(--hp-border);border-radius:8px}
301
- .hosty-tool-chooser{width:min(440px,calc(100vw - 32px));max-height:80dvh;border:1px solid var(--hp-border);border-radius:12px;padding:16px;background:var(--hp-bg);color:var(--hp-fg);font:14px/1.5 system-ui}.hosty-tool-chooser::backdrop{background:#0005}.hosty-tool-chooser ul{list-style:none;padding:0;display:grid;gap:8px}.hosty-tool-chooser li{display:flex;gap:8px}.hosty-tool-chooser li>button{flex:1;border:1px solid var(--hp-border);background:transparent;color:inherit;border-radius:8px;padding:12px;text-align:left;cursor:pointer}.hosty-tool-chooser li span{display:block;color:var(--hp-muted);font-size:12px}.hosty-panel-error{position:absolute;bottom:16px;right:60px;z-index:4;background:var(--hp-bg);border:1px solid var(--hp-border);border-radius:8px;padding:12px;font:13px system-ui}
300
+ .hosty-panel-frame{position:relative;flex:1;min-height:0;overflow:hidden}.hosty-panel-frame iframe{display:block}
301
+ .hosty-panel-resize{position:absolute;left:-12px;top:0;bottom:0;width:12px;cursor:col-resize;touch-action:none}.hosty-panel-resize:hover{background:var(--hs-brand-soft)}
302
+ .hosty-panel-state{padding:8px 12px;font:400 13px/20px var(--hs-font);border-bottom:1px solid var(--hp-border);color:var(--hp-muted)}.hosty-panel-state a{color:var(--hs-info);text-decoration:underline;text-underline-offset:3px}.hosty-panel-state select{display:block;max-width:100%;margin-top:6px;padding:6px 10px;border:1px solid var(--hp-border);border-radius:8px;background:var(--hp-bg);color:var(--hp-fg)}
303
+ .hosty-panel-workspace[data-hosty-panel-layout=drawer] .hosty-panel-body{position:absolute;inset:0 0 0 auto;width:min(440px,100%);z-index:102;margin:0;box-shadow:-8px 0 30px #0F1B2D29}.hosty-panel-workspace[data-hosty-panel-layout=drawer] .hosty-panel-resize{display:none}
304
+ .hosty-panel-backdrop{position:absolute;inset:0;border:0;background:#0F1B2D57;z-index:101;cursor:pointer}.hosty-panel-backdrop:focus-visible{outline:2px solid #A3E635;outline-offset:-2px}
305
+ .hosty-tool-chooser{width:min(440px,calc(100vw - 32px));max-height:calc(100dvh - 32px);margin:auto;border:1px solid var(--hp-border);border-radius:14px;padding:20px;background:var(--hp-bg);color:var(--hp-fg);overflow:auto;box-shadow:0 14px 34px -8px #0F1B2D3D}.hosty-tool-chooser::backdrop{background:#0F1B2D57}.hosty-tool-chooser header{border:0;padding:0;min-height:32px}.hosty-tool-chooser>p{margin:12px 0;color:var(--hp-muted);font-size:13px}.hosty-tool-chooser ul{list-style:none;padding:0;margin:16px 0;display:grid;gap:8px}.hosty-tool-chooser li{display:flex;gap:8px}.hosty-tool-chooser li>button,.hosty-tool-chooser>button,.hosty-panel-error>button{font:500 14px/20px var(--hs-font);border:1px solid var(--hp-border);background:var(--hs-card);color:inherit;border-radius:8px;padding:8px 12px;cursor:pointer}.hosty-tool-chooser li>button{flex:1;text-align:left;padding:12px}.hosty-tool-chooser li>button:hover,.hosty-tool-chooser>button:hover,.hosty-panel-error>button:hover{background:var(--hs-brand-soft)}.hosty-tool-chooser li span{display:block;color:var(--hp-muted);font:400 13px/20px var(--hs-font)}.hosty-panel-error{position:absolute;bottom:16px;right:60px;max-width:calc(100% - 76px);z-index:4;border:1px solid var(--hp-border);border-radius:10px;padding:12px;font:400 13px/20px var(--hs-font);box-shadow:0 4px 14px -4px #0F1B2D29}.hosty-panel-error>button{margin:8px 0 0 8px}
302
306
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hosty-sdk/app",
3
- "version": "0.32.0",
3
+ "version": "0.34.2",
4
4
  "description": "Hosty app auth: session state machine, recovery bridge, Core revalidation, and the embedder responder.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",
@@ -132,7 +132,7 @@
132
132
  },
133
133
  "scripts": {
134
134
  "test": "vitest run",
135
- "build": "tsc -p tsconfig.build.json && node scripts/fix-dist-extensions.mjs"
135
+ "build": "tsc -p tsconfig.build.json && node scripts/fix-dist-extensions.mjs && node scripts/copy-chrome-assets.mjs"
136
136
  },
137
137
  "dependencies": {
138
138
  "@noble/hashes": "^2.4.0",
@@ -162,7 +162,8 @@
162
162
  "react": "^19.3.0",
163
163
  "react-dom": "^19.3.0",
164
164
  "typescript": "^6",
165
- "vitest": "^5.0.3"
165
+ "vitest": "^5.0.3",
166
+ "vite": "^8.3.5"
166
167
  },
167
168
  "repository": {
168
169
  "type": "git",