@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
@@ -7,6 +7,7 @@ import { useAppActivity } from "./activity-react.js";
7
7
  import { APP_SESSION_ENDED, APP_SESSION_RESTORED, APP_ACTIVITY_RENEWED, acceptAppAuthProtocol } from "./browser-auth.js";
8
8
  import { buildCoreOpenUrl, readRecoveryParams } from "./index.js";
9
9
  import { permissionReviewUrl, requestPermissionReview } from "./permissions.js";
10
+ import { SdkBrand, SdkChromeStyles, SdkLoader } from "./sdk-chrome.js";
10
11
  import { HostyUiRoot } from "./ui-react.js";
11
12
  function snapshot(body) {
12
13
  const value = body;
@@ -33,6 +34,14 @@ function sessionReady(session) {
33
34
  export function HostyOverlay({ children, probePath = "/api/hosty/session", appCodePath = "/api/auth/app-code", uiPath, panelLayout = "auto", localContent, chrome, navigation }) {
34
35
  const actor = useRef(null);
35
36
  const changingActor = useRef(false);
37
+ const restoration = useRef({ established: false, ready: false, announced: false });
38
+ const announceRestoration = useRef(() => {
39
+ const state = restoration.current;
40
+ if (!state.established || state.ready || state.announced || changingActor.current)
41
+ return;
42
+ state.announced = true;
43
+ window.dispatchEvent(new CustomEvent(APP_SESSION_RESTORED, { detail: { userId: actor.current } }));
44
+ }).current;
36
45
  const [session, setSession] = useState(null);
37
46
  const [reviewing, setReviewing] = useState(false);
38
47
  const renewedAwaitingReadiness = useRef(false);
@@ -53,7 +62,7 @@ export function HostyOverlay({ children, probePath = "/api/hosty/session", appCo
53
62
  const next = snapshot(body);
54
63
  if (renewedAwaitingReadiness.current && next && sessionReady(next) && next.userId === actor.current && !changingActor.current) {
55
64
  renewedAwaitingReadiness.current = false;
56
- window.dispatchEvent(new CustomEvent(APP_SESSION_RESTORED, { detail: { userId: next.userId } }));
65
+ announceRestoration();
57
66
  window.dispatchEvent(new Event(APP_ACTIVITY_RENEWED));
58
67
  }
59
68
  return body;
@@ -65,7 +74,7 @@ export function HostyOverlay({ children, probePath = "/api/hosty/session", appCo
65
74
  renewedAwaitingReadiness.current = !sessionReady(next);
66
75
  if (renewedAwaitingReadiness.current)
67
76
  return false;
68
- window.dispatchEvent(new CustomEvent(APP_SESSION_RESTORED, { detail: { userId: next.userId } }));
77
+ announceRestoration();
69
78
  return true;
70
79
  }).current;
71
80
  const activity = useAppActivity(identity.activity ? { ...identity.activity, probePath, appCodePath, validateSession: validate } : null);
@@ -75,6 +84,18 @@ export function HostyOverlay({ children, probePath = "/api/hosty/session", appCo
75
84
  const mounted = useRef(false);
76
85
  if (ready)
77
86
  mounted.current = true;
87
+ useLayoutEffect(() => {
88
+ const state = restoration.current;
89
+ if (!ready) {
90
+ if (state.ready)
91
+ state.announced = false;
92
+ state.ready = false;
93
+ return;
94
+ }
95
+ announceRestoration();
96
+ state.established = true;
97
+ state.ready = true;
98
+ }, [ready, announceRestoration]);
78
99
  useEffect(() => {
79
100
  if (session?.setup === "ready")
80
101
  setReviewing(false);
@@ -168,7 +189,7 @@ export function HostyOverlay({ children, probePath = "/api/hosty/session", appCo
168
189
  }
169
190
  const localOffline = (identity.ui.kind === "unavailable" || (identity.ui.kind === "active" && session?.setup === "unavailable"))
170
191
  && localContent !== undefined;
171
- return _jsxs(_Fragment, { children: [localOffline && _jsxs("div", { "data-hosty-local-content": true, children: [_jsx("p", { role: "status", children: "Hosty is unavailable. Local content remains available." }), localContent] }), _jsx("div", { "data-hosty-content": true, hidden: !ready, inert: !ready, style: ready ? { display: "contents" } : { display: "none" }, children: _jsx(HostyUiRoot, { chrome: chrome, navigation: navigation, uiPath: uiPath, panelLayout: panelLayout, active: ready, children: mounted.current ? children : null }) }), !ready && !localOffline && _jsx(OverlaySurface, { pending: pending, title: title, message: message, action: action, actionLabel: actionLabel, reviewUrl: reviewUrl })] });
192
+ return _jsxs(_Fragment, { children: [localOffline && _jsxs("div", { "data-hosty-local-content": true, children: [_jsxs("div", { "data-hosty-sdk": true, className: "hosty-overlay-offline", role: "status", children: [_jsx(SdkChromeStyles, {}), _jsx("style", { children: styles }), _jsx(SdkBrand, {}), _jsx("span", { children: "Hosty is unavailable. Local content remains available." }), _jsx("button", { type: "button", className: "hosty-sdk-button", onClick: () => { void refreshRef.current(); }, children: "Retry" })] }), localContent] }), _jsx("div", { "data-hosty-content": true, hidden: !ready, inert: !ready, style: ready ? { display: "contents" } : { display: "none" }, children: _jsx(HostyUiRoot, { chrome: chrome, navigation: navigation, uiPath: uiPath, panelLayout: panelLayout, active: ready, children: mounted.current ? children : null }) }), !ready && !localOffline && _jsx(OverlaySurface, { pending: pending, title: title, message: message, action: action, actionLabel: actionLabel, reviewUrl: reviewUrl })] });
172
193
  }
173
194
  function OverlaySurface({ pending, title, message, action, actionLabel, reviewUrl }) {
174
195
  const [portal, setPortal] = useState(null);
@@ -223,33 +244,28 @@ function OverlaySurface({ pending, title, message, action, actionLabel, reviewUr
223
244
  return () => { if (element.open && typeof element.close === "function")
224
245
  element.close(); };
225
246
  }, [portal]);
226
- const content = _jsxs(_Fragment, { children: [_jsx("style", { children: styles }), _jsx("dialog", { ref: dialog, className: "hosty-overlay-dialog", "aria-label": "Hosty access", tabIndex: -1, onCancel: event => event.preventDefault(), "data-hosty-activity-control": true, children: _jsx("section", { className: "hosty-overlay-card", "aria-live": "polite", "aria-busy": pending, children: pending ? _jsx("div", { className: "hosty-overlay-loading", role: "status", "aria-label": title, children: showProgress && _jsxs(_Fragment, { children: [_jsx("span", { className: "hosty-overlay-spinner" }), _jsx("p", { children: message })] }) }) : _jsxs(_Fragment, { children: [_jsxs("span", { className: "hosty-overlay-brand", children: [_jsx("span", { className: "hosty-overlay-brand-mark", children: _jsxs("svg", { viewBox: "0 0 100 100", "aria-hidden": "true", focusable: "false", fill: "none", stroke: "currentColor", strokeWidth: 6, strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M25 36 V64 M75 36 V64 M25 50 H39 M61 50 H75" }), _jsx("rect", { x: "17", y: "17", width: "16", height: "16", rx: "4.5" }), _jsx("rect", { x: "67", y: "17", width: "16", height: "16", rx: "4.5" }), _jsx("rect", { x: "17", y: "67", width: "16", height: "16", rx: "4.5" }), _jsx("rect", { x: "67", y: "67", width: "16", height: "16", rx: "4.5" }), _jsx("rect", { x: "42", y: "42", width: "16", height: "16", rx: "4.5" })] }) }), _jsx("span", { children: "Hosty" })] }), _jsx("h1", { children: title }), _jsx("p", { children: message }), action && _jsx("button", { type: "button", onClick: action, children: actionLabel }), reviewUrl && _jsx("a", { href: reviewUrl, target: "_blank", rel: "noopener noreferrer", children: "Open Core permissions" })] }) }) })] });
227
- return portal ? createPortal(content, portal) : _jsx("div", { className: "hosty-overlay-boot", role: "status", "aria-label": "Connecting to Hosty", children: _jsx("style", { children: styles }) });
247
+ const content = _jsxs(_Fragment, { children: [_jsx(SdkChromeStyles, {}), _jsx("style", { children: styles }), _jsx("dialog", { ref: dialog, "data-hosty-sdk": true, className: "hosty-overlay-dialog", "aria-label": "Hosty access", tabIndex: -1, onCancel: event => event.preventDefault(), "data-hosty-activity-control": true, children: _jsx("section", { className: "hosty-overlay-card", "aria-live": "polite", "aria-busy": pending, children: pending ? _jsx("div", { className: "hosty-overlay-loading", role: "status", "aria-label": title, children: showProgress && _jsxs(_Fragment, { children: [_jsx(SdkBrand, {}), _jsx(SdkLoader, {}), _jsx("h1", { children: title }), _jsx("p", { children: message })] }) }) : _jsxs(_Fragment, { children: [_jsx(SdkBrand, {}), _jsx("h1", { children: title }), _jsx("p", { children: message }), action && _jsx("button", { type: "button", className: "hosty-sdk-button hosty-sdk-primary", onClick: action, children: actionLabel }), reviewUrl && _jsx("a", { className: "hosty-overlay-review", href: reviewUrl, target: "_blank", rel: "noopener noreferrer", children: "Open Core permissions" })] }) }) })] });
248
+ return portal ? createPortal(content, portal) : _jsxs("div", { "data-hosty-sdk": true, className: "hosty-overlay-boot", role: "status", "aria-label": "Connecting to Hosty", children: [_jsx(SdkChromeStyles, {}), _jsx("style", { children: styles })] });
228
249
  }
229
250
  const styles = `
230
- .hosty-overlay-boot,.hosty-overlay-dialog{--hosty-bg:#f8f9fb;--hosty-fg:#20242c;--hosty-muted:#606875;--hosty-border:#e0e4eb;color-scheme:light}
231
- html.dark .hosty-overlay-dialog,html[data-hosty-theme=dark] .hosty-overlay-dialog,html.dark .hosty-overlay-boot{--hosty-bg:#14171c;--hosty-fg:#f0f2f5;--hosty-muted:#a2aab7;--hosty-border:#323843;color-scheme:dark}
232
- .hosty-overlay-boot{position:fixed;inset:0;background:var(--hosty-bg);z-index:2147483647}
251
+ .hosty-overlay-boot{position:fixed;inset:0;background:var(--hs-bg);z-index:2147483647}
233
252
  html[data-hosty-overlay-blocked] body>:not([data-hosty-overlay-portal]){visibility:hidden!important}
234
- html[data-hosty-overlay-blocked],html[data-hosty-overlay-blocked] body{background:#f8f9fb!important}
235
- html.dark[data-hosty-overlay-blocked],html.dark[data-hosty-overlay-blocked] body{background:#14171c!important}
253
+ html[data-hosty-overlay-blocked],html[data-hosty-overlay-blocked] body{background:#F6F8FB!important}
254
+ html.dark[data-hosty-overlay-blocked],html.dark[data-hosty-overlay-blocked] body,html[data-hosty-theme=dark][data-hosty-overlay-blocked],html[data-hosty-theme=dark][data-hosty-overlay-blocked] body{background:#0F1B2D!important}
236
255
  html[data-hosty-launch=embedded][data-hosty-overlay-blocked],html[data-hosty-launch=embedded][data-hosty-overlay-blocked] body{background:transparent!important}
237
- .hosty-overlay-dialog{position:fixed;inset:0;width:100%;height:100%;max-width:none;max-height:none;box-sizing:border-box;margin:0;padding:24px;border:0;background:var(--hosty-bg);color:var(--hosty-fg);font:400 14px/1.55 system-ui,sans-serif;outline:0}
256
+ .hosty-overlay-dialog{position:fixed;inset:0;width:100%;height:100%;max-width:none;max-height:none;margin:0;padding:24px;border:0;background:var(--hs-bg);outline:0}
238
257
  .hosty-overlay-dialog[open]{display:grid;place-items:center}
239
258
  .hosty-overlay-dialog::backdrop{background:transparent}
240
259
  html[data-hosty-launch=embedded] .hosty-overlay-dialog,html[data-hosty-launch=embedded] .hosty-overlay-boot{background:transparent}
241
- .hosty-overlay-card{width:min(100%,360px);text-align:center;animation:hosty-overlay-enter 160ms ease-out}
242
- .hosty-overlay-brand{display:flex;align-items:center;justify-content:center;gap:8px;color:var(--hosty-fg);font-size:14px;font-weight:600;text-transform:uppercase;margin-bottom:22px}
243
- .hosty-overlay-brand-mark{display:flex;align-items:center;justify-content:center;width:20px;height:20px;flex-shrink:0}
244
- .hosty-overlay-brand-mark svg{width:24px;height:24px;flex-shrink:0}
245
- .hosty-overlay-card h1{font:600 22px/1.3 system-ui,sans-serif;letter-spacing:-.025em;margin:0 0 12px;color:var(--hosty-fg)}
246
- .hosty-overlay-card p{margin:0 0 24px;color:var(--hosty-muted)}
247
- .hosty-overlay-card button{appearance:none;border:1px solid var(--hosty-border);border-radius:9px;background:var(--hosty-fg);color:var(--hosty-bg);padding:10px 18px;font:600 14px/1.4 system-ui,sans-serif;cursor:pointer}
248
- .hosty-overlay-card button:focus-visible,.hosty-overlay-card a:focus-visible{outline:2px solid var(--hosty-muted);outline-offset:4px}
249
- .hosty-overlay-card a{display:block;color:var(--hosty-muted);font-size:13px;margin-top:18px;text-decoration:underline}
250
- .hosty-overlay-loading{min-height:100px;color:var(--hosty-muted)}
251
- .hosty-overlay-spinner{display:inline-block;width:20px;height:20px;margin:0 0 16px;border:2px solid var(--hosty-border);border-top-color:var(--hosty-muted);border-radius:50%;animation:hosty-overlay-spin .8s linear infinite}
252
- @keyframes hosty-overlay-spin{to{transform:rotate(360deg)}}
253
- @keyframes hosty-overlay-enter{from{opacity:0;transform:translateY(3px)}to{opacity:1;transform:translateY(0)}}
254
- @media(prefers-reduced-motion:reduce){.hosty-overlay-card,.hosty-overlay-spinner{animation:none}}
260
+ .hosty-overlay-card{box-sizing:border-box;width:min(100%,360px);padding:24px;border:1px solid var(--hs-border);border-radius:14px;background:var(--hs-card);box-shadow:0 14px 34px -8px #0F1B2D3D;text-align:left;overflow-wrap:anywhere;animation:hosty-overlay-enter 160ms ease-out}
261
+ .hosty-overlay-card .hosty-sdk-brand{margin-bottom:16px}
262
+ .hosty-overlay-card h1{font:600 20px/26px var(--hs-font);letter-spacing:-.02em;margin:0 0 10px;color:var(--hs-fg)}
263
+ .hosty-overlay-card p{font:400 14px/22px var(--hs-font);margin:0 0 20px;color:var(--hs-muted)}
264
+ .hosty-overlay-card .hosty-sdk-button{width:100%}
265
+ .hosty-overlay-review{display:inline-block;color:var(--hs-info);font-size:13px;margin-top:16px;text-decoration:underline;text-underline-offset:3px}
266
+ .hosty-overlay-loading{min-height:64px}.hosty-overlay-loading .hosty-sdk-loader{margin:0 0 16px}.hosty-overlay-loading p{margin-bottom:0}
267
+ .hosty-overlay-offline{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin:8px;padding:12px 16px;border:1px solid var(--hs-border);border-radius:10px;background:var(--hs-card);box-shadow:0 4px 14px -4px #0F1B2D29}.hosty-overlay-offline>span:not(.hosty-sdk-brand){flex:1;min-width:180px}
268
+ @keyframes hosty-overlay-enter{from{opacity:0}to{opacity:1}}
269
+ @media(max-width:480px){.hosty-overlay-dialog{padding:16px}.hosty-overlay-card{padding:20px}.hosty-overlay-card h1{font-size:18px;line-height:24px}}
270
+ @media(prefers-reduced-motion:reduce){.hosty-overlay-card{animation:none}}
255
271
  `;
@@ -1,6 +1,7 @@
1
1
  "use client";
2
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useState } from "react";
4
+ import { SdkBrand, SdkChromeStyles, SdkIcon, SdkSpinner } from "./sdk-chrome.js";
4
5
  import { appFetch } from "./browser-auth.js";
5
6
  import { permissionNotice, requestPermissionReview } from "./permissions.js";
6
7
  /** App-owned GET endpoint authenticates the viewer and calls readOwnPermissionNotice. */
@@ -41,8 +42,13 @@ export function MissingPermissionsNotice({ endpoint = "/api/hosty/permissions" }
41
42
  const kind = state && permissionNotice(state);
42
43
  if (dismissed || !kind || !state)
43
44
  return null;
44
- return _jsxs("section", { "data-hosty-activity-control": true, role: "alert", "aria-label": "Required permissions", style: { border: "1px solid currentColor", padding: 12, margin: 8, borderRadius: 8, color: "#c2410c" }, children: [kind === "unsupported"
45
+ return _jsxs("section", { "data-hosty-sdk": true, "data-hosty-activity-control": true, role: "alert", "aria-label": "Required permissions", className: "hosty-permissions-notice", children: [_jsx(SdkChromeStyles, {}), _jsx("style", { children: styles }), _jsxs("header", { children: [_jsx(SdkBrand, {}), _jsx(SdkIcon, { name: "warning" }), _jsx("strong", { children: "Permissions needed" })] }), kind === "unsupported"
45
46
  ? _jsxs("p", { children: ["Core does not support required permissions: ", state.permissions.unsupportedRequired.join(", "), ". Update the app or Core."] })
46
47
  : _jsx("p", { children: "This app needs required permissions. Operations using them are unavailable until approval." }), kind === "missing" && state.permissions?.reviewAvailable !== false && state.corePublicOrigin &&
47
- _jsx("button", { type: "button", onClick: () => { requestPermissionReview(state); setPending(true); }, children: "Request permissions" }), pending && _jsx("p", { children: "Review permissions in Core, then return here. You can leave this app installed without approving." }), _jsx("button", { type: "button", onClick: () => { setDismissed(true); setPending(false); }, style: { marginLeft: 12 }, children: "Dismiss" })] });
48
+ _jsx("button", { type: "button", className: "hosty-sdk-button", onClick: () => { requestPermissionReview(state); setPending(true); }, children: "Request permissions" }), pending && _jsxs("p", { className: "hosty-sdk-status", children: [_jsx(SdkSpinner, {}), "Review permissions in Core, then return here. You can leave this app installed without approving."] }), _jsx("button", { type: "button", onClick: () => { setDismissed(true); setPending(false); }, className: "hosty-sdk-button hosty-sdk-ghost", children: "Dismiss" })] });
48
49
  }
50
+ const styles = `
51
+ .hosty-permissions-notice{margin:8px;padding:12px 16px;border:1px solid var(--hs-warning-border);border-radius:10px;background:var(--hs-warning-soft);color:var(--hs-fg)}
52
+ .hosty-permissions-notice header{display:flex;align-items:center;flex-wrap:wrap;gap:8px}.hosty-permissions-notice header>.hosty-sdk-icon{color:var(--hs-warning)}
53
+ .hosty-permissions-notice p{margin:8px 0 12px;font-size:13px;line-height:20px}.hosty-permissions-notice>button{margin:0 8px 0 0}
54
+ `;
package/dist/react.d.ts CHANGED
@@ -77,7 +77,7 @@ export declare function useAppIdentity({ probePath, appCodePath, onSession, }?:
77
77
  hasBeenActive: boolean;
78
78
  refresh: () => Promise<unknown>;
79
79
  };
80
- export declare function AppIdentityBridge({ renderState, children, ...options }?: AppIdentityBridgeProps): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
80
+ export declare function AppIdentityBridge({ renderState, children, ...options }?: AppIdentityBridgeProps): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined;
81
81
  export interface HostThemeBridgeProps {
82
82
  /**
83
83
  * Whether the operating system decides the theme while no shell has spoken — `true` unless the
package/dist/react.js CHANGED
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { exchangeAppCode } from "./app-code-exchange.js";
3
4
  import { captureChromeHints } from "./ui-composition.js";
4
5
  import { replaceConsumedUrl } from "./consumed-url.js";
5
6
  // React slice: the client identity bridge — probe, silent recovery, and the fallback
@@ -8,7 +9,7 @@ import { replaceConsumedUrl } from "./consumed-url.js";
8
9
  import { readAppContinuity } from "./continuity.js";
9
10
  import { configureAppContinuity } from "./continuity-browser.js";
10
11
  import { AppActivityBridge } from "./activity-react.js";
11
- import { AuthNotice, AuthNoticeButton, authNoticeActionStyle } from "./auth-notice.js";
12
+ import { AuthNotice, AuthNoticeButton } from "./auth-notice.js";
12
13
  import { useEffect, useRef, useState } from "react";
13
14
  import { appFetch, appSessionActive, appGrantRevision, rememberAppGrant, restoreAppGrant, forgetRejectedAppGrant, openAppSignIn, APP_SESSION_ENDED } from "./browser-auth.js";
14
15
  import { acceptAppAuthProtocol, createAppAuthAttempt, persistAppAuthAttempt, takeAppAuthAttempt, submitAppAuthIntent, appAuthNavigationUrl, APP_AUTH_ATTEMPT_PREFIX } from "./browser-auth.js";
@@ -359,12 +360,8 @@ export function useAppIdentity({ probePath = "/api/auth/identity", appCodePath =
359
360
  const result = openAppSignIn(openUrl, controller.signal, protocol, mode === "native", probePath);
360
361
  setUi({ kind: "recovering" });
361
362
  void result.then(async (proof) => {
362
- const response = await appFetch(appCodePath, {
363
- method: "POST", headers: { "Content-Type": "application/json" },
364
- body: JSON.stringify(proof),
365
- }, false);
366
- const body = await response.json();
367
- if (!response.ok)
363
+ const { ok, body } = await exchangeAppCode(appCodePath, proof, controller.signal);
364
+ if (!ok)
368
365
  throw new Error("Hosty could not establish this app session.");
369
366
  if (cancelled)
370
367
  return;
@@ -372,7 +369,7 @@ export function useAppIdentity({ probePath = "/api/auth/identity", appCodePath =
372
369
  await probeAndRecover();
373
370
  return;
374
371
  }
375
- if (typeof body.accessToken === "string" && body.accessToken)
372
+ if (typeof body?.accessToken === "string" && body.accessToken)
376
373
  rememberAppGrant(body.accessToken);
377
374
  silentError.current = null;
378
375
  writeGuard(false);
@@ -410,24 +407,18 @@ export function useAppIdentity({ probePath = "/api/auth/identity", appCodePath =
410
407
  exchangeRef.current = {
411
408
  path: appCodePath,
412
409
  revision: appGrantRevision(),
413
- response: fetch(appCodePath, {
414
- method: "POST",
415
- headers: { "Content-Type": "application/json" },
416
- body: JSON.stringify({ code, codeVerifier: attempt.codeVerifier }),
417
- credentials: "same-origin", redirect: "error",
418
- }),
410
+ response: exchangeAppCode(appCodePath, { code, codeVerifier: attempt.codeVerifier }),
419
411
  };
420
412
  }
421
413
  const exchange = exchangeRef.current;
422
414
  if (exchange && exchange.path === appCodePath) {
423
415
  void exchange.response
424
- .then(async (response) => {
416
+ .then(async ({ ok, body }) => {
425
417
  if (cancelled) {
426
418
  return;
427
419
  }
428
- if (response.ok) {
420
+ if (ok) {
429
421
  writeGuard(false);
430
- const body = await response.json().catch(() => null);
431
422
  if (cancelled)
432
423
  return;
433
424
  if (exchange.revision !== appGrantRevision()) {
@@ -474,8 +465,8 @@ export function AppIdentityBridge({ renderState, children, ...options } = {}) {
474
465
  if (ui.kind === "active")
475
466
  return children ?? null;
476
467
  if (ui.kind === "recovering")
477
- return _jsx(AuthNotice, { children: "Connecting to Hosty\u2026" });
478
- return (_jsx(AuthNotice, { children: ui.kind === "signin" ? (_jsxs(_Fragment, { children: [_jsx("span", { children: ui.error ?? "Sign in through Hosty to use this app." }), ui.signIn ? _jsx(AuthNoticeButton, { onClick: ui.signIn, children: "Sign in via Hosty" }) : ui.openUrl ? (_jsx("a", { href: ui.openUrl, ...(ui.embedded ? { target: "_blank", rel: "noopener noreferrer" } : {}), style: authNoticeActionStyle, children: "Sign in via Hosty" })) : (_jsx("span", { children: "Open this app from the machine running Hosty, or configure a public origin." }))] })) : ui.kind === "denied" ? (_jsx("span", { children: "You are signed in to Hosty but are not allowed to use this app." })) : ui.kind === "misconfigured" ? (_jsx("span", { children: "This app is not configured correctly on the host. Contact the administrator." })) : (_jsxs(_Fragment, { children: [_jsx("span", { children: "Can\u2019t reach Hosty right now." }), _jsx(AuthNoticeButton, { onClick: () => window.location.reload(), children: "Retry" })] })) }));
468
+ return _jsx(AuthNotice, { busy: true, children: "Connecting to Hosty\u2026" });
469
+ return (_jsx(AuthNotice, { children: ui.kind === "signin" ? (_jsxs(_Fragment, { children: [_jsx("span", { children: ui.error ?? "Sign in through Hosty to use this app." }), ui.signIn ? _jsx(AuthNoticeButton, { onClick: ui.signIn, children: "Sign in via Hosty" }) : ui.openUrl ? (_jsx("a", { href: ui.openUrl, ...(ui.embedded ? { target: "_blank", rel: "noopener noreferrer" } : {}), className: "hosty-sdk-button hosty-sdk-primary", children: "Sign in via Hosty" })) : (_jsx("span", { children: "Open this app from the machine running Hosty, or configure a public origin." }))] })) : ui.kind === "denied" ? (_jsx("span", { children: "You are signed in to Hosty but are not allowed to use this app." })) : ui.kind === "misconfigured" ? (_jsx("span", { children: "This app is not configured correctly on the host. Contact the administrator." })) : (_jsxs(_Fragment, { children: [_jsx("span", { children: "Can\u2019t reach Hosty right now." }), _jsx(AuthNoticeButton, { onClick: () => window.location.reload(), children: "Retry" })] })) }));
479
470
  }
480
471
  function readStoredTheme(key) {
481
472
  try {
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useCallback, useEffect, useRef, useState } from "react";
4
- const button = "inline-flex items-center justify-center gap-2 rounded-md border bg-background px-3 py-2 text-sm font-medium hover:bg-muted disabled:opacity-50 disabled:cursor-not-allowed";
5
- const field = "w-full rounded-md border bg-background px-3 py-2 text-sm";
4
+ import { SdkChromeStyles, SdkProgress, SdkSpinner } from "./sdk-chrome.js";
5
+ const button = "hosty-sdk-button";
6
+ const field = "hosty-sdk-input";
6
7
  function message(error) { return error instanceof Error ? error.message : "Sandbox request failed."; }
7
8
  function link(value) {
8
9
  try {
@@ -161,16 +162,16 @@ export function CoreEnvironmentPanel({ request, storageKey, workspace, workspace
161
162
  }
162
163
  }
163
164
  const disabled = busy || pending !== null;
164
- return _jsxs("section", { className: "space-y-4 rounded-lg border bg-card p-4", "aria-label": "Sandbox environments", children: [_jsxs("header", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h3", { className: "font-semibold", children: "Sandbox environments" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Separate Core instances with their own apps, users and temporary data." })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("button", { className: button, onClick: () => { setError(""); void refresh(); }, children: "Refresh sandboxes" }), workspace && _jsx("button", { className: button, disabled: disabled || !workspace.worktrees.some(t => t.state === "active"), onClick: () => setCreating(!creating), children: "Create sandbox" })] })] }), creating && workspace && _jsx(CreateEnvironment, { workspace: workspace, disabled: disabled, onCreate: create, onCancel: () => setCreating(false) }), notice && _jsx("p", { role: "status", className: "text-sm text-muted-foreground", children: notice }), (error || readError) && _jsx("p", { role: "alert", className: "text-sm text-destructive", children: error || readError }), pending && _jsxs("div", { className: "flex items-center gap-3 text-sm", children: [_jsx("span", { children: busy ? `Sandbox ${pending.kind} is in progress…` : "The request outcome is not confirmed." }), _jsx("button", { className: button, disabled: busy, onClick: () => void run(pending), children: "Retry same request" })] }), loading ? _jsx("p", { role: "status", className: "text-sm text-muted-foreground", children: "Loading sandboxes\u2026" }) : !environments.length && !error && !readError && _jsx("p", { className: "text-sm text-muted-foreground", children: "No active sandbox environments." }), !workspace && consents.length > 0 && _jsxs("details", { className: "rounded-md border p-3", children: [_jsxs("summary", { className: "cursor-pointer text-sm font-medium", children: ["Saved launch permissions (", consents.length, ")"] }), _jsx("p", { className: "mt-2 text-xs text-muted-foreground", children: "Revocation affects all matching launches and delegated access. Running children and their own accounts remain; use Stop or Delete sandbox to end them." }), _jsx("ul", { className: "divide-y", children: consents.map(grant => _jsxs("li", { className: "space-y-2 py-3 text-sm", children: [_jsxs("p", { children: [grant.appId, " \u00B7 ", grant.mode, " \u00B7 ", grant.state] }), _jsx("p", { className: "break-all text-xs text-muted-foreground", children: grant.target }), _jsxs("button", { className: button, disabled: disabled, onClick: () => { setBusy(true); void request(`/consents/${grant.id}/revoke`, {}).then(() => refresh()).catch(cause => setError(message(cause))).finally(() => setBusy(false)); }, children: ["Revoke permission for ", grant.appId] })] }, grant.id)) })] }), environments.map(item => _jsxs("article", { className: "space-y-3 rounded-md border p-3", "aria-label": `Sandbox ${item.label}`, children: [_jsxs("header", { className: "flex flex-wrap items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsxs("h4", { className: "font-medium", children: [item.label, " ", _jsx("span", { className: "ml-2 rounded border px-2 py-0.5 text-xs font-normal", children: item.state })] }), _jsxs("p", { className: "mt-1 text-xs text-muted-foreground", children: [item.mode === "source" ? "Core built from workspace source" : item.mode === "release" ? "Installed Core release" : "Current Core build", item.context?.workspaceId && workspaceHref && _jsxs(_Fragment, { children: [" \u00B7 ", _jsx("a", { className: "underline", href: workspaceHref(item.context.workspaceId), children: "Open workspace" })] })] })] }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [item.state === "running" && link(item.previewUrl) && _jsx("a", { className: button, href: item.previewUrl + "/login", target: "_blank", rel: "noreferrer", children: "Open Core" }), _jsx("button", { className: button, disabled: disabled || ["building", "starting", "stopping", "finishing", "cleanup-failed"].includes(item.state), onClick: () => void command(item, item.state === "running" ? "stop" : "start"), children: item.state === "running" ? "Stop" : "Start" }), _jsx("button", { className: button + " text-destructive", disabled: disabled || item.state === "finishing", onClick: () => setRemoving(item.id), children: item.state === "cleanup-failed" ? "Retry deletion" : "Delete sandbox" })] })] }), item.limits && _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Reserved: ", (item.limits.memoryBytes / 1073741824).toFixed(1), " GiB container memory \u00B7 ", item.limits.cpus, " CPUs \u00B7 ", item.limits.pids, " PIDs \u00B7 ", (item.limits.diskBytes / 1073741824).toFixed(1), " GiB soft disk limit. Released after deletion."] }), item.usage && _jsx("p", { className: "text-xs text-muted-foreground", children: item.usage.state === "unknown" ? "Resource usage unavailable; the reservation remains active." : `Observed: ${(item.usage.containerMemoryBytes / 1048576).toFixed(0)} MiB containers · ${(item.usage.coreMemoryBytes / 1048576).toFixed(0)} MiB Core process · ${(item.usage.ownedDiskBytes / 1073741824).toFixed(2)} GiB owned data.` }), item.access && _jsxs("div", { className: "space-y-2 text-sm", "aria-label": "Sandbox consent", children: [_jsx("p", { children: item.access.state === "operator" ? "Operator launch" : item.access.state === "active" ? item.access.saved ? "Saved launch permission" : "Permission for this run" : item.access.state === "target-changed" ? "Core target changed or unavailable; review launch permission" : "Launch permission revoked or unavailable" }), _jsx("p", { className: "text-xs text-muted-foreground", children: item.access.description }), item.access.grantId && ["active", "target-changed"].includes(item.access.state) && _jsx("button", { className: button, disabled: disabled, onClick: () => { setBusy(true); void request(`/${item.id}/consent/revoke`, {}).then(() => refresh()).catch(cause => setError(message(cause))).finally(() => setBusy(false)); }, children: "Revoke launch permission" })] }), item.error && _jsx("p", { role: "alert", className: "text-sm text-destructive", children: item.error }), item.state === "running" && _jsxs(_Fragment, { children: [_jsx(EnvironmentApplications, { value: apps[item.id] }), apps[item.id] && ["setup-required", "sign-in-required"].includes(apps[item.id].state) && _jsx(SetupAccount, { request: request, id: item.id, disabled: disabled, onDone: () => refresh() })] }), removing === item.id && _jsxs("div", { role: "alertdialog", "aria-label": `Delete ${item.label}`, className: "space-y-3 rounded-md border border-destructive p-3", children: [_jsx("p", { className: "text-sm", children: "Permanently delete this sandbox, all its apps, test users, data and caches? Workspace source and shared folders are preserved." }), _jsxs("div", { className: "flex gap-2", children: [_jsx("button", { className: button + " text-destructive", disabled: disabled, onClick: () => { setRemoving(null); void command(item, "finish"); }, children: "Delete permanently" }), _jsx("button", { className: button, onClick: () => setRemoving(null), children: "Cancel" })] })] })] }, item.id))] });
165
+ return _jsxs("section", { "data-hosty-sdk": true, className: "hosty-sandbox", "aria-label": "Sandbox environments", children: [_jsx(SdkChromeStyles, {}), _jsx("style", { children: styles }), _jsxs("header", { className: "hosty-sandbox-header", children: [_jsxs("div", { children: [_jsx("h3", { className: "hosty-sandbox-title", children: "Sandbox environments" }), _jsx("p", { className: "hosty-sandbox-muted", children: "Separate Core instances with their own apps, users and temporary data." })] }), _jsxs("div", { className: "hosty-sandbox-actions", children: [_jsx("button", { className: button, onClick: () => { setError(""); void refresh(); }, children: "Refresh sandboxes" }), workspace && _jsx("button", { className: button, disabled: disabled || !workspace.worktrees.some(t => t.state === "active"), onClick: () => setCreating(!creating), children: "Create sandbox" })] })] }), busy && _jsx(SdkProgress, { label: "Sandbox operation in progress" }), creating && workspace && _jsx(CreateEnvironment, { workspace: workspace, disabled: disabled, onCreate: create, onCancel: () => setCreating(false) }), notice && _jsx("p", { role: "status", className: "hosty-sandbox-muted", children: notice }), (error || readError) && _jsx("p", { role: "alert", className: "hosty-sandbox-error", children: error || readError }), pending && _jsxs("div", { className: "hosty-sandbox-pending", children: [_jsxs("span", { children: [busy && _jsx(SdkSpinner, {}), busy ? `Sandbox ${pending.kind} is in progress…` : "The request outcome is not confirmed."] }), _jsx("button", { className: button, disabled: busy, onClick: () => void run(pending), children: "Retry same request" })] }), loading ? _jsxs("p", { role: "status", className: "hosty-sdk-status", children: [_jsx(SdkSpinner, {}), "Loading sandboxes\u2026"] }) : !environments.length && !error && !readError && _jsx("p", { className: "hosty-sandbox-muted", children: "No active sandbox environments." }), !workspace && consents.length > 0 && _jsxs("details", { className: "hosty-sandbox-box", children: [_jsxs("summary", { className: "hosty-sandbox-summary", children: ["Saved launch permissions (", consents.length, ")"] }), _jsx("p", { className: "hosty-sandbox-fine", children: "Revocation affects all matching launches and delegated access. Running children and their own accounts remain; use Stop or Delete sandbox to end them." }), _jsx("ul", { className: "hosty-sandbox-list", children: consents.map(grant => _jsxs("li", { className: "hosty-sandbox-consent", children: [_jsxs("p", { children: [grant.appId, " \u00B7 ", grant.mode, " \u00B7 ", grant.state] }), _jsx("p", { className: "hosty-sandbox-path", children: grant.target }), _jsxs("button", { className: button, disabled: disabled, onClick: () => { setBusy(true); void request(`/consents/${grant.id}/revoke`, {}).then(() => refresh()).catch(cause => setError(message(cause))).finally(() => setBusy(false)); }, children: ["Revoke permission for ", grant.appId] })] }, grant.id)) })] }), environments.map(item => _jsxs("article", { className: "hosty-sandbox-box", "aria-label": `Sandbox ${item.label}`, children: [_jsxs("header", { className: "hosty-sandbox-header", children: [_jsxs("div", { children: [_jsxs("h4", { className: "hosty-sandbox-title", children: [item.label, " ", _jsx("span", { className: "hosty-sandbox-state", children: item.state })] }), _jsxs("p", { className: "hosty-sandbox-fine", children: [item.mode === "source" ? "Core built from workspace source" : item.mode === "release" ? "Installed Core release" : "Current Core build", item.context?.workspaceId && workspaceHref && _jsxs(_Fragment, { children: [" \u00B7 ", _jsx("a", { className: "hosty-sandbox-link", href: workspaceHref(item.context.workspaceId), children: "Open workspace" })] })] })] }), _jsxs("div", { className: "hosty-sandbox-actions", children: [item.state === "running" && link(item.previewUrl) && _jsx("a", { className: button, href: item.previewUrl + "/login", target: "_blank", rel: "noreferrer", children: "Open Core" }), _jsx("button", { className: button, disabled: disabled || ["building", "starting", "stopping", "finishing", "cleanup-failed"].includes(item.state), onClick: () => void command(item, item.state === "running" ? "stop" : "start"), children: item.state === "running" ? "Stop" : "Start" }), _jsx("button", { className: button + " hosty-sdk-danger", disabled: disabled || item.state === "finishing", onClick: () => setRemoving(item.id), children: item.state === "cleanup-failed" ? "Retry deletion" : "Delete sandbox" })] })] }), item.limits && _jsxs("p", { className: "hosty-sandbox-fine", children: ["Reserved: ", (item.limits.memoryBytes / 1073741824).toFixed(1), " GiB container memory \u00B7 ", item.limits.cpus, " CPUs \u00B7 ", item.limits.pids, " PIDs \u00B7 ", (item.limits.diskBytes / 1073741824).toFixed(1), " GiB soft disk limit. Released after deletion."] }), item.usage && _jsx("p", { className: "hosty-sandbox-fine", children: item.usage.state === "unknown" ? "Resource usage unavailable; the reservation remains active." : `Observed: ${(item.usage.containerMemoryBytes / 1048576).toFixed(0)} MiB containers · ${(item.usage.coreMemoryBytes / 1048576).toFixed(0)} MiB Core process · ${(item.usage.ownedDiskBytes / 1073741824).toFixed(2)} GiB owned data.` }), item.access && _jsxs("div", { className: "hosty-sandbox-consent", "aria-label": "Sandbox consent", children: [_jsx("p", { children: item.access.state === "operator" ? "Operator launch" : item.access.state === "active" ? item.access.saved ? "Saved launch permission" : "Permission for this run" : item.access.state === "target-changed" ? "Core target changed or unavailable; review launch permission" : "Launch permission revoked or unavailable" }), _jsx("p", { className: "hosty-sandbox-fine", children: item.access.description }), item.access.grantId && ["active", "target-changed"].includes(item.access.state) && _jsx("button", { className: button, disabled: disabled, onClick: () => { setBusy(true); void request(`/${item.id}/consent/revoke`, {}).then(() => refresh()).catch(cause => setError(message(cause))).finally(() => setBusy(false)); }, children: "Revoke launch permission" })] }), item.error && _jsx("p", { role: "alert", className: "hosty-sandbox-error", children: item.error }), item.state === "running" && _jsxs(_Fragment, { children: [_jsx(EnvironmentApplications, { value: apps[item.id] }), apps[item.id] && ["setup-required", "sign-in-required"].includes(apps[item.id].state) && _jsx(SetupAccount, { request: request, id: item.id, disabled: disabled, onDone: () => refresh() })] }), removing === item.id && _jsxs("div", { role: "alertdialog", "aria-label": `Delete ${item.label}`, className: "hosty-sandbox-confirm", children: [_jsx("p", { className: "hosty-sandbox-text", children: "Permanently delete this sandbox, all its apps, test users, data and caches? Workspace source and shared folders are preserved." }), _jsxs("div", { className: "hosty-sandbox-actions", children: [_jsx("button", { className: button + " hosty-sdk-danger", disabled: disabled, onClick: () => { setRemoving(null); void command(item, "finish"); }, children: "Delete permanently" }), _jsx("button", { className: button, onClick: () => setRemoving(null), children: "Cancel" })] })] })] }, item.id))] });
165
166
  }
166
167
  function EnvironmentApplications({ value }) {
167
168
  if (!value)
168
- return _jsx("p", { role: "status", className: "text-sm text-muted-foreground", children: "Reading application status\u2026" });
169
+ return _jsxs("p", { role: "status", className: "hosty-sdk-status", children: [_jsx(SdkSpinner, {}), "Reading application status\u2026"] });
169
170
  if (value.state !== "ready")
170
- return _jsx("p", { role: "status", className: "text-sm text-muted-foreground", children: value.error ?? (value.state === "setup-required" ? "Set up a test administrator for this fresh Core." : "Application status is unavailable.") });
171
+ return _jsx("p", { role: "status", className: "hosty-sandbox-muted", children: value.error ?? (value.state === "setup-required" ? "Set up a test administrator for this fresh Core." : "Application status is unavailable.") });
171
172
  if (!value.apps.length)
172
- return _jsx("p", { className: "text-sm text-muted-foreground", children: "No apps installed in this sandbox yet." });
173
- return _jsx("ul", { className: "divide-y", "aria-label": "Sandbox applications", children: value.apps.map(app => _jsxs("li", { className: "flex flex-wrap items-center justify-between gap-3 py-2 text-sm", children: [_jsxs("div", { children: [_jsx("span", { className: "font-medium", children: app.displayName }), _jsxs("span", { className: "ml-2 text-muted-foreground", children: [app.runtimeState, " \u00B7 ", app.health ?? "Health unknown"] })] }), app.runtimeState === "running" && link(app.previewUrl) && _jsxs("a", { className: button, href: app.previewUrl, target: "_blank", rel: "noreferrer", children: ["Open preview", _jsxs("span", { className: "sr-only", children: [" for ", app.displayName] })] })] }, app.id)) });
173
+ return _jsx("p", { className: "hosty-sandbox-muted", children: "No apps installed in this sandbox yet." });
174
+ return _jsx("ul", { className: "hosty-sandbox-list", "aria-label": "Sandbox applications", children: value.apps.map(app => _jsxs("li", { className: "hosty-sandbox-app", children: [_jsxs("div", { children: [_jsx("span", { className: "hosty-sandbox-title", children: app.displayName }), _jsxs("span", { className: "hosty-sandbox-app-state", children: [app.runtimeState, " \u00B7 ", app.health ?? "Health unknown"] })] }), app.runtimeState === "running" && link(app.previewUrl) && _jsxs("a", { className: button, href: app.previewUrl, target: "_blank", rel: "noreferrer", children: ["Open preview", _jsxs("span", { className: "hosty-sdk-sr-only", children: [" for ", app.displayName] })] })] }, app.id)) });
174
175
  }
175
176
  function CreateEnvironment({ workspace, disabled, onCreate, onCancel }) {
176
177
  const trees = workspace.worktrees.filter(t => t.state === "active");
@@ -182,7 +183,7 @@ function CreateEnvironment({ workspace, disabled, onCreate, onCancel }) {
182
183
  void onCreate({ label: form.get("label"), workspaceId: workspace.id, worktreeIds: selected,
183
184
  ...(source ? { coreWorktreeId: form.get("coreWorktreeId"), coreProject: form.get("coreProject") } : {}) });
184
185
  }
185
- return _jsxs("form", { onSubmit: submit, className: "space-y-3 rounded-md border bg-muted/30 p-3", "aria-label": "Create workspace sandbox", children: [_jsxs("label", { className: "block space-y-1 text-sm", children: ["Sandbox name", _jsx("input", { className: field, name: "label", required: true, maxLength: 120, defaultValue: "Workspace sandbox" })] }), _jsxs("fieldset", { className: "space-y-2", children: [_jsx("legend", { className: "text-sm font-medium", children: "Attached worktrees" }), trees.map(t => _jsxs("label", { className: "flex gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: selected.includes(t.id), onChange: e => setSelected(old => e.target.checked ? [...old, t.id] : old.filter(id => id !== t.id)) }), t.label] }, t.id))] }), _jsxs("label", { className: "flex gap-2 text-sm", children: [_jsx("input", { type: "checkbox", checked: source, onChange: e => setSource(e.target.checked) }), "Build Core from a worktree"] }), source && _jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [_jsxs("label", { className: "space-y-1 text-sm", children: ["Core worktree", _jsx("select", { className: field, name: "coreWorktreeId", required: true, children: trees.filter(t => selected.includes(t.id)).map(t => _jsx("option", { value: t.id, children: t.label }, t.id)) })] }), _jsxs("label", { className: "space-y-1 text-sm", children: ["Core project, relative to worktree", _jsx("input", { className: field, name: "coreProject", required: true, defaultValue: "apps/core/src/Haas.Hosty.Core/Haas.Hosty.Core.csproj" })] })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Starts a fresh Core with separate users and data. Attached worktrees stay available until the sandbox is deleted." }), _jsxs("div", { className: "flex gap-2", children: [_jsx("button", { className: button, type: "submit", disabled: disabled || selected.length === 0 || selected.length > 16, children: "Create and start" }), _jsx("button", { className: button, type: "button", onClick: onCancel, children: "Cancel" })] })] });
186
+ return _jsxs("form", { onSubmit: submit, className: "hosty-sandbox-form", "aria-label": "Create workspace sandbox", children: [_jsxs("label", { className: "hosty-sandbox-field", children: ["Sandbox name", _jsx("input", { className: field, name: "label", required: true, maxLength: 120, defaultValue: "Workspace sandbox" })] }), _jsxs("fieldset", { className: "hosty-sandbox-group", children: [_jsx("legend", { className: "hosty-sandbox-legend", children: "Attached worktrees" }), trees.map(t => _jsxs("label", { className: "hosty-sandbox-check", children: [_jsx("input", { type: "checkbox", checked: selected.includes(t.id), onChange: e => setSelected(old => e.target.checked ? [...old, t.id] : old.filter(id => id !== t.id)) }), t.label] }, t.id))] }), _jsxs("label", { className: "hosty-sandbox-check", children: [_jsx("input", { type: "checkbox", checked: source, onChange: e => setSource(e.target.checked) }), "Build Core from a worktree"] }), source && _jsxs("div", { className: "hosty-sandbox-fields", children: [_jsxs("label", { className: "hosty-sandbox-field", children: ["Core worktree", _jsx("select", { className: field, name: "coreWorktreeId", required: true, children: trees.filter(t => selected.includes(t.id)).map(t => _jsx("option", { value: t.id, children: t.label }, t.id)) })] }), _jsxs("label", { className: "hosty-sandbox-field", children: ["Core project, relative to worktree", _jsx("input", { className: field, name: "coreProject", required: true, defaultValue: "apps/core/src/Haas.Hosty.Core/Haas.Hosty.Core.csproj" })] })] }), _jsx("p", { className: "hosty-sandbox-fine", children: "Starts a fresh Core with separate users and data. Attached worktrees stay available until the sandbox is deleted." }), _jsxs("div", { className: "hosty-sandbox-actions", children: [_jsx("button", { className: button, type: "submit", disabled: disabled || selected.length === 0 || selected.length > 16, children: "Create and start" }), _jsx("button", { className: button, type: "button", onClick: onCancel, children: "Cancel" })] })] });
186
187
  }
187
188
  function SetupAccount({ request, id, disabled, onDone }) {
188
189
  const [error, setError] = useState("");
@@ -207,5 +208,15 @@ function SetupAccount({ request, id, disabled, onDone }) {
207
208
  setBusy(false);
208
209
  }
209
210
  }
210
- return _jsxs("form", { onSubmit: submit, "aria-label": "Sandbox administrator", className: "space-y-3 rounded-md border p-3", children: [_jsx("p", { className: "text-sm", children: "Use a separate sandbox administrator. For an existing Core, enter its current account. Parent credentials are not copied." }), _jsxs("div", { className: "grid gap-3 sm:grid-cols-3", children: [_jsxs("label", { className: "text-sm", children: ["Display name", _jsx("input", { className: field, name: "displayName", required: true, autoComplete: "off" })] }), _jsxs("label", { className: "text-sm", children: ["Test email", _jsx("input", { className: field, type: "email", name: "email", required: true, autoComplete: "off" })] }), _jsxs("label", { className: "text-sm", children: ["Test password", _jsx("input", { className: field, type: "password", name: "password", minLength: 12, required: true, autoComplete: "new-password" })] })] }), error && _jsx("p", { role: "alert", className: "text-sm text-destructive", children: error }), _jsx("button", { className: button, disabled: disabled || busy, children: busy ? "Connecting…" : "Connect test account" })] });
211
+ return _jsxs("form", { onSubmit: submit, "aria-label": "Sandbox administrator", className: "hosty-sandbox-box", children: [_jsx("p", { className: "hosty-sandbox-text", children: "Use a separate sandbox administrator. For an existing Core, enter its current account. Parent credentials are not copied." }), _jsxs("div", { className: "hosty-sandbox-fields", children: [_jsxs("label", { className: "hosty-sandbox-text", children: ["Display name", _jsx("input", { className: field, name: "displayName", required: true, autoComplete: "off" })] }), _jsxs("label", { className: "hosty-sandbox-text", children: ["Test email", _jsx("input", { className: field, type: "email", name: "email", required: true, autoComplete: "off" })] }), _jsxs("label", { className: "hosty-sandbox-text", children: ["Test password", _jsx("input", { className: field, type: "password", name: "password", minLength: 12, required: true, autoComplete: "new-password" })] })] }), error && _jsx("p", { role: "alert", className: "hosty-sandbox-error", children: error }), _jsx("button", { className: button, disabled: disabled || busy, children: busy ? "Connecting…" : "Connect test account" })] });
211
212
  }
213
+ const styles = `
214
+ .hosty-sandbox{padding:16px;border:1px solid var(--hs-border);border-radius:10px;background:var(--hs-card);min-width:0}
215
+ .hosty-sandbox>:not(style)+:not(style){margin-top:16px}.hosty-sandbox .hosty-sandbox-header{display:flex;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;gap:12px}.hosty-sandbox :where(h3,h4,p){margin:0}.hosty-sandbox h3{font:600 16px/24px var(--hs-font)}.hosty-sandbox h4{font:500 14px/22px var(--hs-font)}.hosty-sandbox :where(header,article,form)>p{margin-top:8px}.hosty-sandbox .hosty-sandbox-title{font-weight:500}
216
+ .hosty-sandbox .hosty-sandbox-muted{color:var(--hs-muted);font-size:13px;line-height:20px}.hosty-sandbox .hosty-sandbox-fine{color:var(--hs-muted);font-size:12px;line-height:20px}.hosty-sandbox .hosty-sandbox-path{overflow-wrap:anywhere;color:var(--hs-muted);font:400 12px/20px var(--hs-mono)}
217
+ .hosty-sandbox .hosty-sandbox-actions{display:flex;align-items:center;flex-wrap:wrap;gap:8px}.hosty-sandbox .hosty-sandbox-box,.hosty-sandbox .hosty-sandbox-form,.hosty-sandbox .hosty-sandbox-confirm{border:1px solid var(--hs-border);border-radius:10px;padding:12px;min-width:0}.hosty-sandbox .hosty-sandbox-box>:not(:first-child){margin-top:12px}.hosty-sandbox .hosty-sandbox-form{background:var(--hs-bg);display:flex;flex-direction:column;gap:12px}.hosty-sandbox .hosty-sandbox-confirm{background:var(--hs-danger-soft);border-color:var(--hs-danger);margin-top:12px}.hosty-sandbox-confirm>.hosty-sandbox-actions{margin-top:12px}
218
+ .hosty-sandbox .hosty-sandbox-error{padding:10px 12px;border:1px solid var(--hs-danger);border-radius:8px;color:var(--hs-danger);background:var(--hs-danger-soft)}.hosty-sandbox .hosty-sandbox-pending{display:flex;align-items:center;flex-wrap:wrap;gap:12px}.hosty-sandbox-pending>span{display:flex;align-items:center;gap:8px}.hosty-sandbox .hosty-sandbox-state{display:inline-block;padding:1px 6px;margin-left:8px;border:1px solid var(--hs-border);border-radius:6px;color:var(--hs-muted);font:400 12px/18px var(--hs-font)}
219
+ .hosty-sandbox .hosty-sandbox-list{list-style:none;padding:0;margin:0}.hosty-sandbox .hosty-sandbox-list>li+li{border-top:1px solid var(--hs-border)}.hosty-sandbox .hosty-sandbox-consent{display:flex;flex-direction:column;align-items:flex-start;gap:8px;padding:12px 0}.hosty-sandbox .hosty-sandbox-app{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;padding:10px 0}.hosty-sandbox-app-state{color:var(--hs-muted);margin-left:8px}
220
+ .hosty-sandbox .hosty-sandbox-fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,180px),1fr));gap:12px}.hosty-sandbox .hosty-sandbox-field,.hosty-sandbox .hosty-sandbox-fields>label{display:flex;flex-direction:column;gap:6px;font:500 13px/20px var(--hs-font);min-width:0}.hosty-sandbox .hosty-sandbox-check{display:flex;align-items:flex-start;gap:8px;font-size:14px}.hosty-sandbox input[type=checkbox]{flex:none;width:16px;height:16px;margin:3px 0 0;accent-color:var(--hs-brand)}.hosty-sandbox input[type=checkbox]:focus-visible{outline:2px solid var(--hs-brand);outline-offset:2px}
221
+ .hosty-sandbox .hosty-sandbox-group{min-width:0;border:0;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.hosty-sandbox legend{font:500 13px/20px var(--hs-font);margin-bottom:8px}.hosty-sandbox .hosty-sandbox-summary{cursor:pointer;font-weight:500}.hosty-sandbox .hosty-sandbox-link{color:var(--hs-info);text-decoration:underline;text-underline-offset:3px}
222
+ `;
@@ -131,7 +131,7 @@ export declare class SandboxClient {
131
131
  }): Promise<SandboxInstance>;
132
132
  call<T = unknown>(action: SandboxAction, input: Record<string, unknown>): Promise<T>;
133
133
  }
134
- export declare const coreEnvironmentActions: readonly ["environment-consent", "environment-create", "environment-list", "environment-status", "environment-start", "environment-stop", "environment-restart", "environment-finish", "environment-setup", "environment-api"];
134
+ export declare const coreEnvironmentActions: readonly ["environment-consent", "environment-create", "environment-list", "environment-status", "environment-start", "environment-stop", "environment-restart", "environment-finish", "environment-setup", "environment-api", "environment-initialization", "environment-initialization-resume", "environment-setup-link"];
135
135
  export type CoreEnvironmentAction = typeof coreEnvironmentActions[number];
136
136
  export interface CoreEnvironmentSavedConsent {
137
137
  id: string;
@@ -152,6 +152,7 @@ export interface CoreEnvironment {
152
152
  port: number;
153
153
  dataRoot?: string;
154
154
  previewUrl: string;
155
+ initializationRequested?: boolean;
155
156
  process?: {
156
157
  pid: number;
157
158
  startedAt: string;
@@ -94,7 +94,7 @@ export class SandboxClient {
94
94
  }
95
95
  }
96
96
  export const coreEnvironmentActions = ["environment-consent", "environment-create", "environment-list", "environment-status", "environment-start",
97
- "environment-stop", "environment-restart", "environment-finish", "environment-setup", "environment-api"];
97
+ "environment-stop", "environment-restart", "environment-finish", "environment-setup", "environment-api", "environment-initialization", "environment-initialization-resume", "environment-setup-link"];
98
98
  /** Full child Core transport. Requires core.environments permissions and live administrator authority.
99
99
  * Setup uses ordinary password authentication. Parent credentials are never forwarded to the child. */
100
100
  export class CoreEnvironmentClient {
@@ -115,15 +115,16 @@ export class CoreEnvironmentClient {
115
115
  if (!coreEnvironmentActions.includes(action))
116
116
  throw new SandboxError("environment_operation_invalid", "Unknown environment operation.", 400);
117
117
  const { environmentId, ...body } = input;
118
- const read = action === "environment-list" || action === "environment-status";
118
+ const read = action === "environment-list" || action === "environment-status" || action === "environment-initialization";
119
119
  let suffix = action === "environment-consent" ? "/consent" : "";
120
120
  if (action !== "environment-list" && action !== "environment-create" && action !== "environment-consent") {
121
121
  if (typeof environmentId !== "string" || !/^[a-f0-9]{32}$/.test(environmentId))
122
122
  throw new SandboxError("environment_id_invalid", "Select a Core-issued environment ID.", 400);
123
123
  suffix = `/${environmentId}` + (action === "environment-status" ? "" : action === "environment-api" ? "/api"
124
- : action === "environment-setup" ? "/setup" : `/operations/${action.slice("environment-".length)}`);
124
+ : action === "environment-initialization" ? "/initialization" : action === "environment-initialization-resume" ? "/initialization/resume"
125
+ : action === "environment-setup-link" ? "/setup-link" : action === "environment-setup" ? "/setup" : `/operations/${action.slice("environment-".length)}`);
125
126
  }
126
- const replayable = !read && action !== "environment-api" && action !== "environment-setup";
127
+ const replayable = !read && action !== "environment-api" && action !== "environment-setup" && action !== "environment-setup-link" && action !== "environment-initialization-resume";
127
128
  if (replayable && (typeof body.requestId !== "string" || !/^[a-f0-9-]{32,36}$/i.test(body.requestId)))
128
129
  throw new SandboxError("environment_request_required", "Persist a request UUID before sending lifecycle operations.", 400);
129
130
  let response;
@@ -0,0 +1,13 @@
1
+ export declare function SdkChromeStyles(): import("react").JSX.Element;
2
+ export declare function SdkBrand(): import("react").JSX.Element;
3
+ export declare function SdkSpinner(): import("react").JSX.Element;
4
+ export declare function SdkProgress({ label }: {
5
+ label: string;
6
+ }): import("react").JSX.Element;
7
+ export declare function SdkLoader(): import("react").JSX.Element;
8
+ type Icon = "close" | "external" | "menu" | "plus" | "minus" | "check" | "warning" | "error";
9
+ export declare function SdkIcon({ name }: {
10
+ name: Icon;
11
+ }): import("react").JSX.Element;
12
+ export declare const chromeStyles = "\n[data-hosty-sdk]{--hs-font:\"Hosty SDK Sans\",\"IBM Plex Sans\",ui-sans-serif,system-ui,sans-serif;--hs-mono:\"Hosty SDK Mono\",\"JetBrains Mono\",ui-monospace,monospace;--hs-bg:#F6F8FB;--hs-card:#FFFFFF;--hs-fg:#0F1B2D;--hs-muted:#5A6A82;--hs-border:#DFE5EE;--hs-input:#C3CDDB;--hs-soft:#EEF2F7;--hs-action:#0F1B2D;--hs-action-fg:#FFFFFF;--hs-brand:#A3E635;--hs-brand-fg:#0F1B2D;--hs-brand-soft:#ECFCCB;--hs-brand-edge:#5A9A0A;--hs-info:#1449C2;--hs-info-solid:#1C68FF;--hs-info-soft:#E8F0FF;--hs-warning:#8A5300;--hs-warning-soft:#FEF3DC;--hs-warning-border:#F8D9A0;--hs-danger:#B4232A;--hs-danger-soft:#FDECEC;--hs-success:#0F6B33;--hs-success-soft:#E8F7EE;color-scheme:light;color:var(--hs-fg);font:400 14px/1.5 var(--hs-font);letter-spacing:normal;text-transform:none;box-sizing:border-box}\nhtml.dark [data-hosty-sdk],html[data-hosty-theme=dark] [data-hosty-sdk]{--hs-bg:#0F1B2D;--hs-card:#17253B;--hs-fg:#F6F8FB;--hs-muted:#93A3BA;--hs-border:#26364F;--hs-input:#3A4B66;--hs-soft:#22324B;--hs-action:#F6F8FB;--hs-action-fg:#0F1B2D;--hs-brand-soft:#273B2E;--hs-brand-edge:#A3E635;--hs-info:#8DB4FF;--hs-info-solid:#4D8BFF;--hs-info-soft:#182E50;--hs-warning:#FBBF24;--hs-warning-soft:#392E22;--hs-warning-border:#6A4C22;--hs-danger:#FF8A8E;--hs-danger-soft:#3A2432;--hs-success:#4ADE80;--hs-success-soft:#18392F;color-scheme:dark}\n[data-hosty-sdk] :where(button,input,select,textarea){font:inherit;letter-spacing:normal;text-transform:none;box-sizing:border-box}\n[data-hosty-sdk] :where(button,a,input,select,textarea,summary,[tabindex]):focus-visible{outline:2px solid var(--hs-brand);outline-offset:2px;box-shadow:none}\n[data-hosty-sdk] :where(input,select,textarea)[aria-invalid=true]:focus-visible{outline-color:var(--hs-danger)}\n[data-hosty-sdk] ::selection{background:var(--hs-brand);color:var(--hs-brand-fg)}\n[data-hosty-sdk] .hosty-sdk-button{appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:8px;box-sizing:border-box;min-height:36px;padding:7px 12px;border:1px solid var(--hs-input);border-radius:8px;background:var(--hs-card);color:var(--hs-fg);font:500 14px/20px var(--hs-font);text-decoration:none;cursor:pointer;transition:background-color 120ms ease-out,color 120ms ease-out,border-color 120ms ease-out;white-space:normal;text-align:center}\n[data-hosty-sdk] .hosty-sdk-primary{background:var(--hs-action);border-color:var(--hs-action);color:var(--hs-action-fg)}\n[data-hosty-sdk] .hosty-sdk-ghost{background:transparent;border-color:transparent}\n[data-hosty-sdk] .hosty-sdk-danger{color:var(--hs-danger)}\n[data-hosty-sdk] .hosty-sdk-button:hover:not(:disabled){background:var(--hs-brand);border-color:var(--hs-brand);color:var(--hs-brand-fg)}\n[data-hosty-sdk] .hosty-sdk-button:active:not(:disabled){background:var(--hs-brand-soft);border-color:var(--hs-brand-edge);color:var(--hs-fg)}\n[data-hosty-sdk] .hosty-sdk-button:disabled{opacity:.5;cursor:not-allowed}\n[data-hosty-sdk] .hosty-sdk-icon-button{flex:none;width:32px;min-height:32px;padding:6px}\n[data-hosty-sdk] .hosty-sdk-input{box-sizing:border-box;width:100%;min-height:36px;padding:7px 10px;border:1px solid var(--hs-input);border-radius:8px;background:var(--hs-card);color:var(--hs-fg);font:400 14px/20px var(--hs-font)}\n.hosty-sdk-brand{display:inline-flex;align-items:center;gap:6px;flex:none;font:500 12px/18px var(--hs-font);color:var(--hs-muted)}\n.hosty-sdk-brand>svg{display:block;width:18px;height:18px;color:var(--hs-fg);flex:none}\n.hosty-sdk-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}\n.hosty-sdk-icon{display:block;width:16px;height:16px;flex:none}\n.hosty-sdk-spinner{display:inline-block;flex:none;width:16px;height:16px;box-sizing:border-box;border:2px solid var(--hs-input);border-top-color:var(--hs-info-solid);border-radius:50%;animation:hosty-sdk-spin .8s linear infinite}\n.hosty-sdk-progress{height:4px;border-radius:4px;background:var(--hs-soft);overflow:hidden;width:100%}\n.hosty-sdk-progress>span{display:block;width:38%;height:100%;border-radius:inherit;background:var(--hs-brand);animation:hosty-sdk-progress 1.6s ease-in-out infinite}\n.hosty-sdk-loader{display:block;width:36px;height:36px}\n.hosty-sdk-loader rect{fill:var(--hs-card);stroke:var(--hs-input);stroke-width:3;animation:hosty-sdk-cell 1.6s ease-in-out infinite;animation-delay:var(--hosty-cell-delay)}\n.hosty-sdk-status{display:flex;align-items:center;gap:8px;color:var(--hs-muted)}\n.hosty-panel-notice[data-hosty-sdk]{padding:24px;background:var(--hosty-panel-background,var(--hs-bg));color:var(--hosty-panel-foreground,var(--hs-fg));overflow-wrap:anywhere}.hosty-panel-notice[data-hosty-sdk] p{margin:8px 0;color:var(--hosty-panel-muted,var(--hs-muted))}.hosty-panel-notice[data-hosty-sdk] a{color:var(--hs-info);text-decoration:underline;text-underline-offset:3px}\n.hosty-panel-review[data-hosty-sdk]{position:absolute;top:8px;right:8px;z-index:2;padding:8px 12px;border:1px solid var(--hosty-panel-border,var(--hs-input));border-radius:8px;background:var(--hosty-panel-background,var(--hs-card));color:var(--hosty-panel-foreground,var(--hs-fg));font:500 13px/20px var(--hs-font);text-decoration:none}.hosty-panel-review[data-hosty-sdk]:hover{background:var(--hs-brand-soft)}.hosty-panel-review[data-hosty-sdk]:focus-visible{outline:2px solid var(--hs-brand);outline-offset:2px}\n@keyframes hosty-sdk-spin{to{transform:rotate(360deg)}}\n@keyframes hosty-sdk-progress{0%{transform:translateX(-110%)}100%{transform:translateX(370%)}}\n@keyframes hosty-sdk-cell{0%,55%,100%{fill:var(--hs-card);stroke:var(--hs-input)}18%{fill:var(--hs-brand);stroke:var(--hs-brand-edge)}}\n@media(prefers-reduced-motion:reduce){.hosty-sdk-spinner,.hosty-sdk-progress>span,.hosty-sdk-loader rect{animation:none}.hosty-sdk-spinner{border-color:var(--hs-info-solid);outline:1px solid var(--hs-info-solid);outline-offset:2px}.hosty-sdk-progress>span{width:100%;opacity:.7}.hosty-sdk-loader rect:nth-child(4){fill:var(--hs-brand);stroke:var(--hs-brand-edge)}}\n@media(max-width:480px){[data-hosty-sdk] .hosty-sdk-input{font-size:16px}}\n";
13
+ export {};
@@ -0,0 +1,102 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect } from "react";
4
+ import { CoreMark } from "./chrome-mark.js";
5
+ // Statically analyzable URLs work in workspace bundlers and the published dist alike.
6
+ const sansLatin = new URL("./assets/plex-sans-latin.woff2", import.meta.url).href;
7
+ const sansExtended = new URL("./assets/plex-sans-latin-ext.woff2", import.meta.url).href;
8
+ const sansCyrillic = new URL("./assets/plex-sans-cyrillic.woff2", import.meta.url).href;
9
+ const monoLatin = new URL("./assets/jetbrains-mono-latin.woff2", import.meta.url).href;
10
+ const monoExtended = new URL("./assets/jetbrains-mono-latin-ext.woff2", import.meta.url).href;
11
+ const monoCyrillic = new URL("./assets/jetbrains-mono-cyrillic.woff2", import.meta.url).href;
12
+ const fontOwners = new WeakMap();
13
+ function retainFonts(document) {
14
+ let owner = fontOwners.get(document);
15
+ if (!owner?.element.isConnected) {
16
+ const element = document.createElement("style");
17
+ element.dataset.hostySdkFonts = "";
18
+ element.textContent = fontStyles;
19
+ document.head.append(element);
20
+ owner = { element, users: 0 };
21
+ fontOwners.set(document, owner);
22
+ }
23
+ const retained = owner;
24
+ retained.users++;
25
+ return () => {
26
+ retained.users--;
27
+ // StrictMode and a moving access portal can replay effects before the next paint.
28
+ // Keep the same font sheet when another SDK surface acquires it in that interval.
29
+ queueMicrotask(() => {
30
+ if (retained.users !== 0)
31
+ return;
32
+ retained.element.remove();
33
+ if (fontOwners.get(document) === retained)
34
+ fontOwners.delete(document);
35
+ });
36
+ };
37
+ }
38
+ export function SdkChromeStyles() {
39
+ // Asset URLs differ between Node SSR and the browser bundle. The deterministic base
40
+ // style hydrates unchanged; only a mounted browser owner installs resolved font URLs.
41
+ useEffect(() => retainFonts(document), []);
42
+ return _jsx("style", { "data-hosty-sdk-styles": true, children: chromeStyles });
43
+ }
44
+ export function SdkBrand() { return _jsxs("span", { className: "hosty-sdk-brand", children: [_jsx(CoreMark, {}), "Hosty"] }); }
45
+ export function SdkSpinner() { return _jsx("span", { className: "hosty-sdk-spinner", "aria-hidden": "true" }); }
46
+ export function SdkProgress({ label }) {
47
+ return _jsx("div", { className: "hosty-sdk-progress", role: "progressbar", "aria-label": label, children: _jsx("span", {}) });
48
+ }
49
+ export function SdkLoader() {
50
+ return _jsx("svg", { className: "hosty-sdk-loader", viewBox: "0 0 100 100", "aria-hidden": "true", focusable: "false", children: [[12, 12], [12, 41], [12, 70], [41, 41], [70, 70], [70, 41], [70, 12]].map(([x, y], index) => _jsx("rect", { x: x, y: y, width: "18", height: "18", style: { "--hosty-cell-delay": `${index * .16}s` } }, index)) });
51
+ }
52
+ const paths = {
53
+ close: "m18 6-12 12M6 6l12 12", external: "M15 3h6v6m0-6L10 14m8-1v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",
54
+ menu: "M4 6h16M4 12h16M4 18h16", plus: "M12 5v14M5 12h14", minus: "M5 12h14", check: "m20 6-11 11-5-5",
55
+ warning: "m12 3 9 9-9 9-9-9 9-9Zm0 4v6m0 3v.01", error: "M12 8v4m0 4v.01M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z",
56
+ };
57
+ export function SdkIcon({ name }) {
58
+ return _jsx("svg", { className: "hosty-sdk-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", children: _jsx("path", { d: paths[name] }) });
59
+ }
60
+ // These roots contain SDK chrome only. Layout ancestors and the app document are deliberately absent.
61
+ const fontStyles = `
62
+ @font-face{font-family:"Hosty SDK Sans";font-style:normal;font-weight:100 900;font-display:swap;src:url("${sansLatin}") format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
63
+ @font-face{font-family:"Hosty SDK Sans";font-style:normal;font-weight:100 900;font-display:swap;src:url("${sansExtended}") format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C4,U+2113,U+2C60-2C7F,U+A720-A7FF}
64
+ @font-face{font-family:"Hosty SDK Sans";font-style:normal;font-weight:100 900;font-display:swap;src:url("${sansCyrillic}") format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}
65
+ @font-face{font-family:"Hosty SDK Mono";font-style:normal;font-weight:100 800;font-display:swap;src:url("${monoLatin}") format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
66
+ @font-face{font-family:"Hosty SDK Mono";font-style:normal;font-weight:100 800;font-display:swap;src:url("${monoExtended}") format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C4,U+2113,U+2C60-2C7F,U+A720-A7FF}
67
+ @font-face{font-family:"Hosty SDK Mono";font-style:normal;font-weight:100 800;font-display:swap;src:url("${monoCyrillic}") format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}
68
+ `;
69
+ export const chromeStyles = `
70
+ [data-hosty-sdk]{--hs-font:"Hosty SDK Sans","IBM Plex Sans",ui-sans-serif,system-ui,sans-serif;--hs-mono:"Hosty SDK Mono","JetBrains Mono",ui-monospace,monospace;--hs-bg:#F6F8FB;--hs-card:#FFFFFF;--hs-fg:#0F1B2D;--hs-muted:#5A6A82;--hs-border:#DFE5EE;--hs-input:#C3CDDB;--hs-soft:#EEF2F7;--hs-action:#0F1B2D;--hs-action-fg:#FFFFFF;--hs-brand:#A3E635;--hs-brand-fg:#0F1B2D;--hs-brand-soft:#ECFCCB;--hs-brand-edge:#5A9A0A;--hs-info:#1449C2;--hs-info-solid:#1C68FF;--hs-info-soft:#E8F0FF;--hs-warning:#8A5300;--hs-warning-soft:#FEF3DC;--hs-warning-border:#F8D9A0;--hs-danger:#B4232A;--hs-danger-soft:#FDECEC;--hs-success:#0F6B33;--hs-success-soft:#E8F7EE;color-scheme:light;color:var(--hs-fg);font:400 14px/1.5 var(--hs-font);letter-spacing:normal;text-transform:none;box-sizing:border-box}
71
+ html.dark [data-hosty-sdk],html[data-hosty-theme=dark] [data-hosty-sdk]{--hs-bg:#0F1B2D;--hs-card:#17253B;--hs-fg:#F6F8FB;--hs-muted:#93A3BA;--hs-border:#26364F;--hs-input:#3A4B66;--hs-soft:#22324B;--hs-action:#F6F8FB;--hs-action-fg:#0F1B2D;--hs-brand-soft:#273B2E;--hs-brand-edge:#A3E635;--hs-info:#8DB4FF;--hs-info-solid:#4D8BFF;--hs-info-soft:#182E50;--hs-warning:#FBBF24;--hs-warning-soft:#392E22;--hs-warning-border:#6A4C22;--hs-danger:#FF8A8E;--hs-danger-soft:#3A2432;--hs-success:#4ADE80;--hs-success-soft:#18392F;color-scheme:dark}
72
+ [data-hosty-sdk] :where(button,input,select,textarea){font:inherit;letter-spacing:normal;text-transform:none;box-sizing:border-box}
73
+ [data-hosty-sdk] :where(button,a,input,select,textarea,summary,[tabindex]):focus-visible{outline:2px solid var(--hs-brand);outline-offset:2px;box-shadow:none}
74
+ [data-hosty-sdk] :where(input,select,textarea)[aria-invalid=true]:focus-visible{outline-color:var(--hs-danger)}
75
+ [data-hosty-sdk] ::selection{background:var(--hs-brand);color:var(--hs-brand-fg)}
76
+ [data-hosty-sdk] .hosty-sdk-button{appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:8px;box-sizing:border-box;min-height:36px;padding:7px 12px;border:1px solid var(--hs-input);border-radius:8px;background:var(--hs-card);color:var(--hs-fg);font:500 14px/20px var(--hs-font);text-decoration:none;cursor:pointer;transition:background-color 120ms ease-out,color 120ms ease-out,border-color 120ms ease-out;white-space:normal;text-align:center}
77
+ [data-hosty-sdk] .hosty-sdk-primary{background:var(--hs-action);border-color:var(--hs-action);color:var(--hs-action-fg)}
78
+ [data-hosty-sdk] .hosty-sdk-ghost{background:transparent;border-color:transparent}
79
+ [data-hosty-sdk] .hosty-sdk-danger{color:var(--hs-danger)}
80
+ [data-hosty-sdk] .hosty-sdk-button:hover:not(:disabled){background:var(--hs-brand);border-color:var(--hs-brand);color:var(--hs-brand-fg)}
81
+ [data-hosty-sdk] .hosty-sdk-button:active:not(:disabled){background:var(--hs-brand-soft);border-color:var(--hs-brand-edge);color:var(--hs-fg)}
82
+ [data-hosty-sdk] .hosty-sdk-button:disabled{opacity:.5;cursor:not-allowed}
83
+ [data-hosty-sdk] .hosty-sdk-icon-button{flex:none;width:32px;min-height:32px;padding:6px}
84
+ [data-hosty-sdk] .hosty-sdk-input{box-sizing:border-box;width:100%;min-height:36px;padding:7px 10px;border:1px solid var(--hs-input);border-radius:8px;background:var(--hs-card);color:var(--hs-fg);font:400 14px/20px var(--hs-font)}
85
+ .hosty-sdk-brand{display:inline-flex;align-items:center;gap:6px;flex:none;font:500 12px/18px var(--hs-font);color:var(--hs-muted)}
86
+ .hosty-sdk-brand>svg{display:block;width:18px;height:18px;color:var(--hs-fg);flex:none}
87
+ .hosty-sdk-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}
88
+ .hosty-sdk-icon{display:block;width:16px;height:16px;flex:none}
89
+ .hosty-sdk-spinner{display:inline-block;flex:none;width:16px;height:16px;box-sizing:border-box;border:2px solid var(--hs-input);border-top-color:var(--hs-info-solid);border-radius:50%;animation:hosty-sdk-spin .8s linear infinite}
90
+ .hosty-sdk-progress{height:4px;border-radius:4px;background:var(--hs-soft);overflow:hidden;width:100%}
91
+ .hosty-sdk-progress>span{display:block;width:38%;height:100%;border-radius:inherit;background:var(--hs-brand);animation:hosty-sdk-progress 1.6s ease-in-out infinite}
92
+ .hosty-sdk-loader{display:block;width:36px;height:36px}
93
+ .hosty-sdk-loader rect{fill:var(--hs-card);stroke:var(--hs-input);stroke-width:3;animation:hosty-sdk-cell 1.6s ease-in-out infinite;animation-delay:var(--hosty-cell-delay)}
94
+ .hosty-sdk-status{display:flex;align-items:center;gap:8px;color:var(--hs-muted)}
95
+ .hosty-panel-notice[data-hosty-sdk]{padding:24px;background:var(--hosty-panel-background,var(--hs-bg));color:var(--hosty-panel-foreground,var(--hs-fg));overflow-wrap:anywhere}.hosty-panel-notice[data-hosty-sdk] p{margin:8px 0;color:var(--hosty-panel-muted,var(--hs-muted))}.hosty-panel-notice[data-hosty-sdk] a{color:var(--hs-info);text-decoration:underline;text-underline-offset:3px}
96
+ .hosty-panel-review[data-hosty-sdk]{position:absolute;top:8px;right:8px;z-index:2;padding:8px 12px;border:1px solid var(--hosty-panel-border,var(--hs-input));border-radius:8px;background:var(--hosty-panel-background,var(--hs-card));color:var(--hosty-panel-foreground,var(--hs-fg));font:500 13px/20px var(--hs-font);text-decoration:none}.hosty-panel-review[data-hosty-sdk]:hover{background:var(--hs-brand-soft)}.hosty-panel-review[data-hosty-sdk]:focus-visible{outline:2px solid var(--hs-brand);outline-offset:2px}
97
+ @keyframes hosty-sdk-spin{to{transform:rotate(360deg)}}
98
+ @keyframes hosty-sdk-progress{0%{transform:translateX(-110%)}100%{transform:translateX(370%)}}
99
+ @keyframes hosty-sdk-cell{0%,55%,100%{fill:var(--hs-card);stroke:var(--hs-input)}18%{fill:var(--hs-brand);stroke:var(--hs-brand-edge)}}
100
+ @media(prefers-reduced-motion:reduce){.hosty-sdk-spinner,.hosty-sdk-progress>span,.hosty-sdk-loader rect{animation:none}.hosty-sdk-spinner{border-color:var(--hs-info-solid);outline:1px solid var(--hs-info-solid);outline-offset:2px}.hosty-sdk-progress>span{width:100%;opacity:.7}.hosty-sdk-loader rect:nth-child(4){fill:var(--hs-brand);stroke:var(--hs-brand-edge)}}
101
+ @media(max-width:480px){[data-hosty-sdk] .hosty-sdk-input{font-size:16px}}
102
+ `;
@@ -5,9 +5,9 @@ export declare function parseChromeHints(search: string): UiChromeOverrides & {
5
5
  };
6
6
  export declare function validChromeOverrides(value: unknown): value is UiChromeOverrides;
7
7
  export declare function resolveChrome(defaults: UiChromeOverrides, hints: ReturnType<typeof parseChromeHints>, host?: UiChromeOverrides): {
8
- navigation: "hide" | "show";
8
+ navigation: "show" | "hide";
9
9
  enabled: boolean;
10
- launcher: "hide" | "show";
10
+ launcher: "show" | "hide";
11
11
  sources: {
12
12
  navigation: UiChromeSource;
13
13
  enabled: UiChromeSource;