@hosty-sdk/app 0.27.22 → 0.27.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/dist/react.js +15 -5
- package/dist/ui-react.d.ts +3 -1
- package/dist/ui-react.js +6 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -338,6 +338,17 @@ disable local tools; an opening otherwise fails with `presentation_unavailable`.
|
|
|
338
338
|
Panel hosting requires reviewed optional `apps.panels.read` (or existing `apps.read`);
|
|
339
339
|
participating as a child does not require catalog access.
|
|
340
340
|
|
|
341
|
+
Panel headers expose **Minimize panel (keep tool open)** (`−`) without a close button.
|
|
342
|
+
Hosts with a tool rail can set `minimizeControl="drawer"` on `HostyPanelWorkspace` to omit the
|
|
343
|
+
minimize button in docked panels and use the active rail icon to toggle them. Responsive drawers
|
|
344
|
+
keep a visible minimize button because the rail is inert while a drawer is open. The default
|
|
345
|
+
`minimizeControl="always"` retains the button in standalone layouts.
|
|
346
|
+
Custom host layouts can use `UiHost.closePresentation(presentationId)` to release a tool view;
|
|
347
|
+
this does not end its conversation or background work.
|
|
348
|
+
To match host chrome, set `--hosty-panel-background`, `--hosty-panel-foreground`,
|
|
349
|
+
`--hosty-panel-muted` and `--hosty-panel-border` on the workspace or an ancestor.
|
|
350
|
+
These overrides apply in both themes; omitted properties retain the SDK light/dark defaults.
|
|
351
|
+
|
|
341
352
|
```ts
|
|
342
353
|
// app/api/hosty/ui/[...action]/route.ts — own app configuration/session only
|
|
343
354
|
import { createHostyUiRouteHandler } from "@hosty-sdk/app/server";
|
package/dist/react.js
CHANGED
|
@@ -208,24 +208,34 @@ export function useAppIdentity({ probePath = "/api/auth/identity", appCodePath =
|
|
|
208
208
|
const onSessionRef = useRef(onSession);
|
|
209
209
|
useEffect(() => { onSessionRef.current = onSession; }, [onSession]);
|
|
210
210
|
const continuity = activity?.continuity;
|
|
211
|
+
const continuityCoreOrigin = activity?.openUrl ? new URL(activity.openUrl).origin : null;
|
|
211
212
|
const continuityActor = session?.userId;
|
|
212
213
|
useEffect(() => {
|
|
213
|
-
if (!continuity || !
|
|
214
|
+
if (!continuity || !continuityCoreOrigin || !continuityActor)
|
|
214
215
|
return;
|
|
215
|
-
return configureAppContinuity({ coreOrigin:
|
|
216
|
+
return configureAppContinuity({ coreOrigin: continuityCoreOrigin, authority: continuity,
|
|
216
217
|
probe: async () => {
|
|
217
218
|
const next = await refreshRef.current();
|
|
218
219
|
return next?.status === "active" && next.userId === continuityActor
|
|
219
220
|
&& (!next.hosty || (next.hosty.version === 1 && next.hosty.setup === "ready"));
|
|
220
221
|
} });
|
|
221
|
-
}, [continuity?.grantId,
|
|
222
|
+
}, [continuity?.grantId, continuityCoreOrigin, continuityActor]);
|
|
222
223
|
useEffect(() => {
|
|
223
224
|
let cancelled = false;
|
|
224
225
|
let probeSequence = 0;
|
|
225
226
|
const controller = new AbortController();
|
|
226
227
|
if (initialGrantPresent.current === null)
|
|
227
228
|
initialGrantPresent.current = restoreAppGrant();
|
|
228
|
-
|
|
229
|
+
let pendingProbe = null;
|
|
230
|
+
// Focus, continuity maintenance and setup review can request the same identity at once.
|
|
231
|
+
// Share its result: a superseded successful probe must not look like an access outage.
|
|
232
|
+
function probeAndRecover() {
|
|
233
|
+
if (pendingProbe)
|
|
234
|
+
return pendingProbe;
|
|
235
|
+
pendingProbe = runProbeAndRecover().finally(() => { pendingProbe = null; });
|
|
236
|
+
return pendingProbe;
|
|
237
|
+
}
|
|
238
|
+
async function runProbeAndRecover() {
|
|
229
239
|
const sequence = ++probeSequence;
|
|
230
240
|
// Dedicated controller + setTimeout instead of AbortSignal.any/timeout, which are not
|
|
231
241
|
// available in every browser (a synchronous throw here would stop the bridge from
|
|
@@ -274,7 +284,7 @@ export function useAppIdentity({ probePath = "/api/auth/identity", appCodePath =
|
|
|
274
284
|
}
|
|
275
285
|
if (revision !== appGrantRevision()) {
|
|
276
286
|
initialGrantPresent.current ||= restoreAppGrant();
|
|
277
|
-
return
|
|
287
|
+
return runProbeAndRecover();
|
|
278
288
|
}
|
|
279
289
|
forgetRejectedAppGrant(rejectionCode, revision);
|
|
280
290
|
if (openUrl && protocol)
|
package/dist/ui-react.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export type HostyPanelWorkspaceProps = {
|
|
|
23
23
|
workspaceId?: string;
|
|
24
24
|
/** A host-owned tool window can keep dock geometry even in a narrow container. */
|
|
25
25
|
layout?: "responsive" | "dock";
|
|
26
|
+
/** Keep a visible dismiss control in drawers; hosts with a tool rail can omit it in docks. */
|
|
27
|
+
minimizeControl?: "always" | "drawer";
|
|
26
28
|
children: ReactNode;
|
|
27
29
|
railMode?: "opened" | "catalog";
|
|
28
30
|
className?: string;
|
|
@@ -42,4 +44,4 @@ export type HostyPanelWorkspaceProps = {
|
|
|
42
44
|
renderIcon?: (panel: UiPanel) => ReactNode;
|
|
43
45
|
};
|
|
44
46
|
/** A single retained frame tree for dock and drawer; only CSS placement changes at the breakpoint. */
|
|
45
|
-
export declare function HostyPanelWorkspace({ children, workspaceId, layout, railMode, className, initialWidth, initialExpanded, onWidthChange, onExpandedChange, onStartApp, onAskAssistant, onAuthRequired, outbound, renderIcon }: HostyPanelWorkspaceProps): import("react").JSX.Element;
|
|
47
|
+
export declare function HostyPanelWorkspace({ children, workspaceId, layout, minimizeControl, railMode, className, initialWidth, initialExpanded, onWidthChange, onExpandedChange, onStartApp, onAskAssistant, onAuthRequired, outbound, renderIcon }: HostyPanelWorkspaceProps): import("react").JSX.Element;
|
package/dist/ui-react.js
CHANGED
|
@@ -57,7 +57,7 @@ export function HostyUiRoot({ children, uiPath, panelLayout = "auto", active = t
|
|
|
57
57
|
const empty = { bootstrap: null, catalog: [], tools: [], selected: null, expanded: false, width: 360, error: null };
|
|
58
58
|
const noop = () => () => { };
|
|
59
59
|
/** A single retained frame tree for dock and drawer; only CSS placement changes at the breakpoint. */
|
|
60
|
-
export function HostyPanelWorkspace({ children, workspaceId = DEFAULT_WORKSPACE, layout = "responsive", railMode = "opened", className = "", initialWidth, initialExpanded, onWidthChange, onExpandedChange, onStartApp, onAskAssistant, onAuthRequired, outbound, renderIcon }) {
|
|
60
|
+
export function HostyPanelWorkspace({ children, workspaceId = DEFAULT_WORKSPACE, layout = "responsive", minimizeControl = "always", railMode = "opened", className = "", initialWidth, initialExpanded, onWidthChange, onExpandedChange, onStartApp, onAskAssistant, onAuthRequired, outbound, renderIcon }) {
|
|
61
61
|
const host = useHostyUiHost();
|
|
62
62
|
const layer = useContext(UiFrameLayerContext), chrome = useHostyChrome();
|
|
63
63
|
const viewport = useRef(null);
|
|
@@ -65,7 +65,7 @@ export function HostyPanelWorkspace({ children, workspaceId = DEFAULT_WORKSPACE,
|
|
|
65
65
|
const unsupported = state.error?.code.startsWith("unsupported") === true;
|
|
66
66
|
const workspace = state.workspaces?.find(w => w.workspaceId === workspaceId);
|
|
67
67
|
const workspaceTools = state.tools.filter(t => t.workspaceId === workspaceId);
|
|
68
|
-
const root = useRef(null), content = useRef(null), body = useRef(null),
|
|
68
|
+
const root = useRef(null), content = useRef(null), body = useRef(null), minimize = useRef(null);
|
|
69
69
|
const opener = useRef(null), panelOpener = useRef(null);
|
|
70
70
|
const [size, setSize] = useState(1200), [chooser, setChooser] = useState(false), [error, setError] = useState(null);
|
|
71
71
|
const chooserRef = useRef(null);
|
|
@@ -195,7 +195,7 @@ export function HostyPanelWorkspace({ children, workspaceId = DEFAULT_WORKSPACE,
|
|
|
195
195
|
return;
|
|
196
196
|
window.dispatchEvent(new CustomEvent("hosty:ui:modal", { detail: `tools:${workspaceId}` }));
|
|
197
197
|
opener.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
198
|
-
|
|
198
|
+
minimize.current?.focus();
|
|
199
199
|
const dismiss = (event) => { if (event.detail === workspaceId)
|
|
200
200
|
host?.setExpanded(false, workspaceId); };
|
|
201
201
|
window.addEventListener("hosty:ui:drawer-dismiss", dismiss);
|
|
@@ -279,14 +279,14 @@ export function HostyPanelWorkspace({ children, workspaceId = DEFAULT_WORKSPACE,
|
|
|
279
279
|
event.preventDefault();
|
|
280
280
|
host?.setWidth(width + (event.key === "ArrowLeft" ? 16 : -16), workspaceId);
|
|
281
281
|
}
|
|
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" }), _jsx("button", { ref:
|
|
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
283
|
}
|
|
284
284
|
const styles = `
|
|
285
285
|
.hosty-panel-viewport{flex:1;min-height:0;min-width:0}
|
|
286
286
|
.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
287
|
|
|
288
|
-
.hosty-panel-workspace{--hp-bg
|
|
289
|
-
html.dark .hosty-panel-workspace,html[data-hosty-theme=dark] .hosty-panel-workspace{--hp-bg
|
|
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
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
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
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)}
|
package/package.json
CHANGED