@hosty-sdk/app 0.27.22 → 0.27.23

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 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";
@@ -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), close = 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
- close.current?.focus();
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: close, type: "button", onClick: () => host?.setExpanded(false, workspaceId), "aria-label": "Return to document", title: "Return to document", children: "\u00D7" })] }), 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)) })] }), tool && _jsx("button", { type: "button", className: "hosty-panel-state", onClick: () => host?.closePresentation(tool.presentationId), children: "Close this tool view" }), _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: () => close.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" })] })] });
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:#f8f9fb;--hp-fg:#20242c;--hp-muted:#606875;--hp-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:#14171c;--hp-fg:#f0f2f5;--hp-muted:#a2aab7;--hp-border:#323843}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hosty-sdk/app",
3
- "version": "0.27.22",
3
+ "version": "0.27.23",
4
4
  "description": "Hosty app auth: session state machine, recovery bridge, Core revalidation, and the embedder responder.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",