@hosty-sdk/app 0.26.0 → 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 +158 -8
- package/dist/index.d.ts +9 -6
- package/dist/index.js +10 -1
- package/dist/next.d.ts +4 -0
- package/dist/next.js +29 -0
- package/dist/overlay-react.d.ts +7 -1
- package/dist/overlay-react.js +2 -2
- package/dist/react.d.ts +1 -0
- package/dist/react.js +3 -0
- package/dist/ui-composition.d.ts +71 -0
- package/dist/ui-composition.js +232 -0
- package/dist/ui-context.d.ts +2 -0
- package/dist/ui-context.js +1 -0
- package/dist/ui-embedded.d.ts +18 -1
- package/dist/ui-embedded.js +123 -17
- package/dist/ui-frame-layer.d.ts +36 -0
- package/dist/ui-frame-layer.js +88 -0
- package/dist/ui-frame.d.ts +2 -0
- package/dist/ui-frame.js +43 -7
- package/dist/ui-host.d.ts +62 -15
- package/dist/ui-host.js +666 -76
- package/dist/ui-navigation-react.d.ts +17 -0
- package/dist/ui-navigation-react.js +89 -0
- package/dist/ui-react.d.ts +15 -3
- package/dist/ui-react.js +131 -54
- package/dist/ui-server.d.ts +3 -1
- package/dist/ui-server.js +14 -2
- package/dist/ui-transport.d.ts +2 -2
- package/dist/ui-transport.js +1 -1
- package/dist/ui-types.d.ts +88 -0
- package/dist/ui-types.js +1 -0
- package/dist/ui-workspaces.d.ts +54 -0
- package/dist/ui-workspaces.js +3 -0
- package/dist/ui.d.ts +7 -1
- package/dist/ui.js +22 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -11,6 +11,9 @@ npm install @hosty-sdk/app
|
|
|
11
11
|
| --- | --- | --- |
|
|
12
12
|
| `@hosty-sdk/app` | anywhere | status taxonomy, recovery decision, `hosty:auth-required` and `hosty:request-delegated-token` schemas, URL/env helpers |
|
|
13
13
|
| `@hosty-sdk/app/server` | server only | Core revalidation with caching, cookie helpers, the app-code route factory, the app secrets client |
|
|
14
|
+
| `@hosty-sdk/app/next` | Next client | `HostyNextOverlay`, `HostyNextNavigationBridge` for App Router navigation |
|
|
15
|
+
| `@hosty-sdk/app/ui` | client | policy/navigation snapshots, replayable navigation, chooser and presentation operations |
|
|
16
|
+
| `@hosty-sdk/app/ui/react` | React client | navigation boundaries/hooks, app frames and retained panel workspaces |
|
|
14
17
|
| `@hosty-sdk/app/react` | client | `<HostyOverlay />` — shared loading, identity, required setup and recovery UI; legacy bridges remain available |
|
|
15
18
|
| `@hosty-sdk/app/session/server` | server | Framework-neutral session response adapter for authenticated Node servers |
|
|
16
19
|
| `@hosty-sdk/app/embedder` | client | theme sender and legacy embedder message parsers; Shell no longer mints app credentials |
|
|
@@ -23,8 +26,8 @@ Minimal Next.js wiring (the configuration object stays server-side):
|
|
|
23
26
|
|
|
24
27
|
```tsx
|
|
25
28
|
// app/layout.tsx — inside the body and the existing theme/launch providers
|
|
26
|
-
import {
|
|
27
|
-
// <
|
|
29
|
+
import { HostyNextOverlay } from "@hosty-sdk/app/next";
|
|
30
|
+
// <HostyNextOverlay>{children}</HostyNextOverlay>
|
|
28
31
|
|
|
29
32
|
// lib/hosty.ts
|
|
30
33
|
export const hostyConfig = {
|
|
@@ -48,8 +51,9 @@ export const POST = createAppCodeRouteHandler(hostyConfig);
|
|
|
48
51
|
|
|
49
52
|
Replace the protected scope's `AppIdentityBridge`, `AppActivityBridge`, custom sign-in rendering
|
|
50
53
|
and `MissingPermissionsNotice` with one `HostyOverlay`. Keep public routes outside that scope.
|
|
51
|
-
Existing launch/theme bridges
|
|
52
|
-
|
|
54
|
+
Existing launch/theme bridges stay in place for transport compatibility; add the composition
|
|
55
|
+
bootstrap below. `probePath` and `appCodePath` support existing route names. `chrome`, `navigation`
|
|
56
|
+
and `panelLayout` configure UI composition without replacing authentication/recovery rendering.
|
|
53
57
|
API routes and server-rendered data still enforce their own authorization. The overlay delays
|
|
54
58
|
protected client mounting; it is not a server data-access boundary.
|
|
55
59
|
|
|
@@ -323,16 +327,28 @@ client's user-token input. Keep tokens server-side. See
|
|
|
323
327
|
[Continuous App Access](../../docs/features/continuous-app-access/feature.md) for creation, logout,
|
|
324
328
|
revocation, exact MCP tools and installation boundaries. Background grants do not create a scheduler.
|
|
325
329
|
|
|
326
|
-
## Shared panels (Core 0.
|
|
330
|
+
## Shared panels (Core 0.131.0 / SDK 0.27.0)
|
|
327
331
|
|
|
328
332
|
`HostyOverlay` composes one UI owner per top-level browser document. Its default
|
|
329
333
|
`panelLayout="auto"` adds an opened-tool rail, an **Add tool** chooser and a resizable
|
|
330
334
|
workspace. In a directly embedded app it delegates presentation to the verified parent
|
|
331
|
-
and renders no local rail. Use `panelLayout="slot"` with
|
|
332
|
-
|
|
335
|
+
and renders no local rail. Use `panelLayout="slot"` with a `HostyPanelWorkspace` per workspace
|
|
336
|
+
ID inside your chrome, or `"none"` with an explicitly registered custom renderer. Without a renderer,
|
|
337
|
+
disable local tools; an opening otherwise fails with `presentation_unavailable`.
|
|
333
338
|
Panel hosting requires reviewed optional `apps.panels.read` (or existing `apps.read`);
|
|
334
339
|
participating as a child does not require catalog access.
|
|
335
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
|
+
|
|
336
352
|
```ts
|
|
337
353
|
// app/api/hosty/ui/[...action]/route.ts — own app configuration/session only
|
|
338
354
|
import { createHostyUiRouteHandler } from "@hosty-sdk/app/server";
|
|
@@ -343,7 +359,7 @@ export const POST = handler;
|
|
|
343
359
|
|
|
344
360
|
Non-Next servers use `createHostyUiResponse` from `@hosty-sdk/app/ui/server` with their
|
|
345
361
|
revalidated own app-user session/token. `UiServerClient` provides only `bootstrap`,
|
|
346
|
-
`listPanels` and `
|
|
362
|
+
`listPanels`, `resolvePanel`, `navigation` and `resolveNavigation`; never expose a generic Core proxy or caller-supplied credentials.
|
|
347
363
|
Both server adapters return `no-store` metadata. The React root's `uiPath` defaults to
|
|
348
364
|
`/api/hosty/ui` and must be a same-origin path.
|
|
349
365
|
|
|
@@ -394,3 +410,137 @@ peer metadata. Matching opaque Core/user contexts prevents accidental account mi
|
|
|
394
410
|
between cooperating apps; it is not attestation of app code or delegated API authority.
|
|
395
411
|
Every target API still authorizes its own session. Older surfaces remain `unreported`.
|
|
396
412
|
See [the panel contract and verification](../../docs/features/sdk-panel-system/feature.md).
|
|
413
|
+
|
|
414
|
+
## Navigation and UI composition
|
|
415
|
+
|
|
416
|
+
Supply identical root defaults to the before-paint bootstrap and overlay. Omitted defaults show
|
|
417
|
+
navigation and enable the standalone tool rail/chooser. Hints and verified host policy can override
|
|
418
|
+
each leaf independently; neither framing nor copied launch-mode storage hides the new navigation.
|
|
419
|
+
|
|
420
|
+
```tsx
|
|
421
|
+
// app/layout.tsx
|
|
422
|
+
import { chromeBootstrapScript } from "@hosty-sdk/app";
|
|
423
|
+
import { HostyNextOverlay } from "@hosty-sdk/app/next";
|
|
424
|
+
|
|
425
|
+
const chrome = { navigation: "show" as const, tools: { enabled: true, launcher: "show" as const } };
|
|
426
|
+
// In <head>, before hydration and the legacy launch bridge:
|
|
427
|
+
// <script dangerouslySetInnerHTML={{ __html: chromeBootstrapScript(chrome) }} />
|
|
428
|
+
// In <body>, around protected app content:
|
|
429
|
+
// <HostyNextOverlay chrome={chrome}>{children}</HostyNextOverlay>
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
For React without Next, use `HostyOverlay` and optionally pass
|
|
433
|
+
`navigation={{ renderer: "standard", router }}`. A router implements `getRoute(): string`,
|
|
434
|
+
`subscribe(listener): () => void` and `navigate(relativeRoute): void | Promise<void>`; optional
|
|
435
|
+
`endpoint` disambiguates several endpoint names on the same origin. Without an adapter the SDK
|
|
436
|
+
uses document navigation. The Next entry is optional and does not add Next to plain SDK consumers.
|
|
437
|
+
|
|
438
|
+
The standard renderer uses only manifest `ui.navigation`. Own navigation needs ordinary app access,
|
|
439
|
+
not `apps.panels.read`. Unavailable metadata has a retry state; an empty declaration adds no menu.
|
|
440
|
+
Same-endpoint links use cached validated metadata and the app router; endpoint changes revalidate
|
|
441
|
+
through Core and replace the same host view. Modified clicks use pinned standalone links.
|
|
442
|
+
|
|
443
|
+
For custom navigation, suppress the automatic menu and wrap only page navigation/branding:
|
|
444
|
+
|
|
445
|
+
```tsx
|
|
446
|
+
import { HostyNavigationBoundary, useHostyChrome, useHostyNavigation } from "@hosty-sdk/app/ui/react";
|
|
447
|
+
|
|
448
|
+
// <HostyNextOverlay navigation={{ renderer: "custom" }}>
|
|
449
|
+
// <HostyNavigationBoundary><MyPageNavigation /></HostyNavigationBoundary>
|
|
450
|
+
// <ProjectSelector /> // Contextual controls stay reachable when navigation is hidden.
|
|
451
|
+
// {children}
|
|
452
|
+
// </HostyNextOverlay>
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
The boundary retains component state while hidden and moves hidden focus to the content landmark.
|
|
456
|
+
Alternatively place `HostyNavigation` at a custom location. Framework-neutral renderers subscribe
|
|
457
|
+
to `ui.getChrome/subscribeChrome` and `ui.getNavigation/subscribeNavigation`, then apply equivalent
|
|
458
|
+
hidden/inert/focus behavior. Snapshots are immutable. `tools.owner` is derived by the SDK; it is not
|
|
459
|
+
a configurable default or URL field.
|
|
460
|
+
|
|
461
|
+
```ts
|
|
462
|
+
const result = await ui.navigate(entry.key, { requestId: crypto.randomUUID() });
|
|
463
|
+
const picker = await ui.openToolPicker({ requestId: crypto.randomUUID() });
|
|
464
|
+
// accepted means navigation was accepted / the chooser became visible.
|
|
465
|
+
// Retain request IDs for uncertain retries; an accepted replay does not repeat the action.
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
Document hints are `hosty_navigation=show|hide`, `hosty_tools=enabled|disabled` and
|
|
469
|
+
`hosty_tool_launcher=show|hide`. The SDK removes them from the visible URL, preserving history
|
|
470
|
+
state. They are not persisted. Verified host overrides take precedence, then hints, app defaults,
|
|
471
|
+
the current document's legacy launch fallback for navigation, and standalone defaults. Hints grant
|
|
472
|
+
no access or hosting authority. Hiding a launcher permits programmatic presentation; disabling
|
|
473
|
+
tools rejects new requests. A root disable applies to every source and collapses retained tools.
|
|
474
|
+
|
|
475
|
+
### Host views and retained tools
|
|
476
|
+
|
|
477
|
+
`HostyAppFrame` accepts a view descriptor. A desktop host can choose different policy from Shell:
|
|
478
|
+
|
|
479
|
+
```tsx
|
|
480
|
+
<HostyAppFrame surface={validatedSurface} title="Plans"
|
|
481
|
+
view={{ viewId: "plans-window-1", workspaceId: "default",
|
|
482
|
+
chrome: { navigation: "show", tools: { enabled: true, launcher: "hide" } },
|
|
483
|
+
navigate: surface => replaceWindowSurface(surface) }} />
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
Omit `navigate` for the SDK's plain iframe replacement, or use it to update the host's route/window
|
|
487
|
+
model. Key a replaced React frame by its immutable surface; live `chrome` updates keep that key.
|
|
488
|
+
One top-level coordinator handles direct app/tool children. It never delegates credentials.
|
|
489
|
+
|
|
490
|
+
Panel declarations and presentations have different identities. `PanelRef` identifies a manifest
|
|
491
|
+
tool. `openSurface` returns an SDK `presentationId`; its `requestId` identifies only the operation.
|
|
492
|
+
Default `reuse: "exact"` focuses the matching target in the destination workspace. A distinct route
|
|
493
|
+
retains another frame; `reuse: "new"` creates another even for the same route. Rails have one item
|
|
494
|
+
per declaration and an instance switcher. Replaying a closed presentation's operation does not
|
|
495
|
+
resurrect it; use a new request to reopen. Domain sessions remain owned by the app.
|
|
496
|
+
|
|
497
|
+
```ts
|
|
498
|
+
const opened = await ui.openSurface(target, {
|
|
499
|
+
placement: "side", requestId: crypto.randomUUID(), reuse: "exact",
|
|
500
|
+
});
|
|
501
|
+
// A host may satisfy "side" with a dock or tool window.
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
Hosts use `useHostyUiHost()` or `createUiHost()`:
|
|
505
|
+
|
|
506
|
+
```ts
|
|
507
|
+
host.setViewChrome("plans-window-1", { navigation: "show", tools: { launcher: "hide" } });
|
|
508
|
+
host.setPlacementResolver(({ source, target, presentations }) => ({ workspaceId: "shared" }));
|
|
509
|
+
host.focusPresentation(presentationId);
|
|
510
|
+
host.collapseWorkspace("shared");
|
|
511
|
+
await host.movePresentation(presentationId, "floating");
|
|
512
|
+
host.closePresentation(presentationId); // Does not delete the conversation.
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
Register destinations before their views. React's `<HostyPanelWorkspace workspaceId="shared">`
|
|
516
|
+
implements the renderer; `panelLayout="slot"` lets the host place multiple named workspaces.
|
|
517
|
+
`layout="dock"` keeps dock geometry inside an independent tool window. The default `responsive`
|
|
518
|
+
layout switches to a drawer in a narrow region. Keep populated workspaces mounted until their
|
|
519
|
+
presentations are moved or explicitly closed.
|
|
520
|
+
|
|
521
|
+
Non-React hosts call `registerWorkspace({ workspaceId, renderer })`. A renderer implements async
|
|
522
|
+
`present(instance, signal)` and `showToolPicker(signal)` returning `accepted | unavailable`, plus
|
|
523
|
+
`focus(id)`, `collapse()` and `release(id)`. Acceptance requires an attached visible destination,
|
|
524
|
+
not a promise of later UI. Matching `frameLayer` object identities allow movement; the renderer
|
|
525
|
+
must preserve iframe DOM parent and browsing context, changing geometry instead of reparenting.
|
|
526
|
+
The supplied React renderer does this. A failed move leaves the original presentation in place.
|
|
527
|
+
|
|
528
|
+
A placement resolver can nominate `presentationId` together with its actual workspace, but only
|
|
529
|
+
an exact resolved target is accepted. Requests are ordered per destination before asynchronous
|
|
530
|
+
resolution; a newer selection or collapse supersedes older work. Closing an app view cancels its
|
|
531
|
+
unaccepted requests and preserves accepted shared presentations. All browser IDs and ledgers are
|
|
532
|
+
local to the document; Core stores no browser workspace/session registry.
|
|
533
|
+
|
|
534
|
+
### Compatibility and migration
|
|
535
|
+
|
|
536
|
+
Both peers negotiate `ui.composition.v1` inside the existing verified wire-v1 handshake. New/old
|
|
537
|
+
peers keep legacy openings without new options. Explicit reuse, remote navigation or picker calls
|
|
538
|
+
to old hosts return `unsupported_composition`; the SDK does not silently discard those options.
|
|
539
|
+
Host loss preserves stale policy and exposes reconnect/separate recovery, without taking local
|
|
540
|
+
ownership. Silent session renewal preserves same-actor frames and drafts.
|
|
541
|
+
|
|
542
|
+
Remove `hosty-shell-chrome`/`data-hosty-launch` visibility CSS when adopting navigation boundaries.
|
|
543
|
+
Keep launch/auth exports for older hosts and native transport. Native clients send explicit hidden
|
|
544
|
+
navigation/launcher and disabled tools until they provide a panel host. `panelLayout="none"` is a
|
|
545
|
+
rendering choice, not a disable policy: explicitly disable local tools or register a renderer.
|
|
546
|
+
See [UI composition and browser acceptance](../../docs/features/sdk-ui-composition/feature.md).
|
package/dist/index.d.ts
CHANGED
|
@@ -106,7 +106,7 @@ export interface PageLocation {
|
|
|
106
106
|
*/
|
|
107
107
|
export declare function buildCoreOpenUrl(corePublicOrigin: string | null, appId: string, location: PageLocation): string | null;
|
|
108
108
|
/**
|
|
109
|
-
*
|
|
109
|
+
* Legacy launch/transport context. Navigation visibility uses the independent composition policy.
|
|
110
110
|
*
|
|
111
111
|
* - `embedded` — framed by the browser Shell. The shell renders navigation for this app. The
|
|
112
112
|
* app restores its per-tab grant, tries a silent Core redirect on first load, then uses its
|
|
@@ -117,19 +117,19 @@ export declare function buildCoreOpenUrl(corePublicOrigin: string | null, appId:
|
|
|
117
117
|
* - `standalone` — a plain browser tab on the app's own origin. Nothing else renders navigation
|
|
118
118
|
* for this app, so it keeps its own.
|
|
119
119
|
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* view keeps standalone recovery even though its app chrome is hidden.
|
|
120
|
+
* Legacy consumers can still use `hidesAppChrome`. New consumers use `useHostyChrome` and
|
|
121
|
+
* `HostyNavigationBoundary`; a desktop host can keep navigation visible in embedded apps.
|
|
122
|
+
* Authentication continues to use the actual transport environment.
|
|
124
123
|
*/
|
|
125
124
|
export type AppLaunchMode = "embedded" | "native" | "standalone";
|
|
126
125
|
/** The query parameter a shell appends to the launch URL to declare the mode. */
|
|
127
126
|
export declare const LAUNCH_MODE_PARAM = "hosty_launch";
|
|
128
127
|
/** Where a resolved mode is kept for the life of the tab or web view. */
|
|
129
128
|
export declare const LAUNCH_MODE_STORAGE_KEY = "hosty.launch.mode";
|
|
130
|
-
/**
|
|
129
|
+
/** Legacy launch attribute. Use data-hosty-navigation for composition visibility. */
|
|
131
130
|
export declare const LAUNCH_MODE_ATTRIBUTE = "data-hosty-launch";
|
|
132
131
|
/**
|
|
132
|
+
* @deprecated Use HostyNavigationBoundary or the effective navigation policy.
|
|
133
133
|
* Marks an element that duplicates chrome a shell already renders — the app's own name and the
|
|
134
134
|
* navigation between its manifest pages. Contextual controls and information a shell does not
|
|
135
135
|
* render (a project picker, a refresh action, an identity badge) are not chrome in this sense
|
|
@@ -164,6 +164,7 @@ export declare function resolveLaunchMode(input: {
|
|
|
164
164
|
heuristic: AppLaunchMode;
|
|
165
165
|
}): LaunchModeResolution;
|
|
166
166
|
/**
|
|
167
|
+
* @deprecated Use the effective composition navigation policy instead.
|
|
167
168
|
* True when a shell around this app already renders its name and page navigation, so the app's
|
|
168
169
|
* own copies are duplication. False for `standalone`, where nothing else renders them.
|
|
169
170
|
*/
|
|
@@ -203,3 +204,5 @@ export declare function decideRecoveryAction(input: {
|
|
|
203
204
|
openUrl: string | null;
|
|
204
205
|
redirectAlreadyAttempted: boolean;
|
|
205
206
|
}): RecoveryAction;
|
|
207
|
+
/** Paint-time navigation policy. Kept in the pure entry point for Node and edge consumers. */
|
|
208
|
+
export declare function chromeBootstrapScript(defaults?: import("./ui-types").UiChromeOverrides): string;
|
package/dist/index.js
CHANGED
|
@@ -142,9 +142,10 @@ export function buildCoreOpenUrl(corePublicOrigin, appId, location) {
|
|
|
142
142
|
export const LAUNCH_MODE_PARAM = "hosty_launch";
|
|
143
143
|
/** Where a resolved mode is kept for the life of the tab or web view. */
|
|
144
144
|
export const LAUNCH_MODE_STORAGE_KEY = "hosty.launch.mode";
|
|
145
|
-
/**
|
|
145
|
+
/** Legacy launch attribute. Use data-hosty-navigation for composition visibility. */
|
|
146
146
|
export const LAUNCH_MODE_ATTRIBUTE = "data-hosty-launch";
|
|
147
147
|
/**
|
|
148
|
+
* @deprecated Use HostyNavigationBoundary or the effective navigation policy.
|
|
148
149
|
* Marks an element that duplicates chrome a shell already renders — the app's own name and the
|
|
149
150
|
* navigation between its manifest pages. Contextual controls and information a shell does not
|
|
150
151
|
* render (a project picker, a refresh action, an identity badge) are not chrome in this sense
|
|
@@ -188,6 +189,7 @@ export function resolveLaunchMode(input) {
|
|
|
188
189
|
return { mode: stored ?? input.heuristic, fromParam: false };
|
|
189
190
|
}
|
|
190
191
|
/**
|
|
192
|
+
* @deprecated Use the effective composition navigation policy instead.
|
|
191
193
|
* True when a shell around this app already renders its name and page navigation, so the app's
|
|
192
194
|
* own copies are duplication. False for `standalone`, where nothing else renders them.
|
|
193
195
|
*/
|
|
@@ -260,3 +262,10 @@ export function decideRecoveryAction(input) {
|
|
|
260
262
|
}
|
|
261
263
|
return { kind: "card", card: "signin" };
|
|
262
264
|
}
|
|
265
|
+
/** Paint-time navigation policy. Kept in the pure entry point for Node and edge consumers. */
|
|
266
|
+
export function chromeBootstrapScript(defaults = {}) {
|
|
267
|
+
const navigation = defaults.navigation;
|
|
268
|
+
if (navigation !== undefined && navigation !== "show" && navigation !== "hide")
|
|
269
|
+
throw new Error("Invalid navigation default.");
|
|
270
|
+
return `(()=>{try{const d=${JSON.stringify({ navigation })},p=new URLSearchParams(location.search),h=new URLSearchParams();for(const k of ["hosty_navigation","hosty_tools","hosty_tool_launcher","hosty_launch"])for(const v of p.getAll(k))h.append(k,v);document.documentElement.setAttribute('data-hosty-chrome-hints',h.toString());const one=k=>p.getAll(k).length===1?p.get(k):null,n=one('hosty_navigation'),l=one('hosty_launch');document.documentElement.setAttribute('data-hosty-navigation',n==='show'||n==='hide'?n:d.navigation??(l==='native'||l==='embedded'?'hide':'show'));}catch{}})();`;
|
|
271
|
+
}
|
package/dist/next.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { HostyOverlay } from "./overlay-react.js";
|
|
2
|
+
/** Next routing stays an optional adapter; the composition model does not patch browser history. */
|
|
3
|
+
export declare function HostyNextNavigationBridge(): null;
|
|
4
|
+
export declare function HostyNextOverlay({ children, ...props }: Parameters<typeof HostyOverlay>[0]): import("react").JSX.Element;
|
package/dist/next.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Suspense, useContext, useEffect, useMemo, useRef } from "react";
|
|
4
|
+
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
5
|
+
import { HostyOverlay } from "./overlay-react.js";
|
|
6
|
+
import { UiCompositionContext } from "./ui-context.js";
|
|
7
|
+
/** Next routing stays an optional adapter; the composition model does not patch browser history. */
|
|
8
|
+
export function HostyNextNavigationBridge() {
|
|
9
|
+
const model = useContext(UiCompositionContext), router = useRouter(), pathname = usePathname(), search = useSearchParams();
|
|
10
|
+
const current = useRef(router);
|
|
11
|
+
current.current = router;
|
|
12
|
+
const listeners = useRef(new Set());
|
|
13
|
+
const adapter = useMemo(() => ({
|
|
14
|
+
getRoute: () => `${window.location.pathname}${window.location.search}${window.location.hash}`,
|
|
15
|
+
subscribe(listener) {
|
|
16
|
+
listeners.current.add(listener);
|
|
17
|
+
window.addEventListener("hashchange", listener);
|
|
18
|
+
return () => { listeners.current.delete(listener); window.removeEventListener("hashchange", listener); };
|
|
19
|
+
},
|
|
20
|
+
navigate(route) { current.current.push(route); },
|
|
21
|
+
}), []);
|
|
22
|
+
useEffect(() => { model?.attachRouter(adapter); return () => model?.attachRouter(); }, [model, adapter]);
|
|
23
|
+
useEffect(() => { for (const listener of listeners.current)
|
|
24
|
+
listener(); }, [pathname, search]);
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
export function HostyNextOverlay({ children, ...props }) {
|
|
28
|
+
return _jsxs(HostyOverlay, { ...props, children: [_jsx(Suspense, { fallback: null, children: _jsx(HostyNextNavigationBridge, {}) }), children] });
|
|
29
|
+
}
|
package/dist/overlay-react.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
+
import type { UiChromeOverrides, UiRouterAdapter } from "./ui-types.js";
|
|
2
3
|
/** Mount once around protected content. Presentation and recovery belong to Hosty. */
|
|
3
|
-
export declare function HostyOverlay({ children, probePath, appCodePath, uiPath, panelLayout, localContent }: {
|
|
4
|
+
export declare function HostyOverlay({ children, probePath, appCodePath, uiPath, panelLayout, localContent, chrome, navigation }: {
|
|
4
5
|
/** Explicitly public/local content available during an outage; never put protected actor data here. */
|
|
5
6
|
localContent?: ReactNode;
|
|
7
|
+
chrome?: UiChromeOverrides;
|
|
8
|
+
navigation?: {
|
|
9
|
+
renderer: "standard" | "custom";
|
|
10
|
+
router?: UiRouterAdapter;
|
|
11
|
+
};
|
|
6
12
|
children: ReactNode;
|
|
7
13
|
probePath?: string;
|
|
8
14
|
appCodePath?: string;
|
package/dist/overlay-react.js
CHANGED
|
@@ -30,7 +30,7 @@ function sessionReady(session) {
|
|
|
30
30
|
return session?.setup === "ready" && (!session.activityRequired || session.recoveryReady) && !activityExpired(session);
|
|
31
31
|
}
|
|
32
32
|
/** Mount once around protected content. Presentation and recovery belong to Hosty. */
|
|
33
|
-
export function HostyOverlay({ children, probePath = "/api/hosty/session", appCodePath = "/api/auth/app-code", uiPath, panelLayout = "auto", localContent }) {
|
|
33
|
+
export function HostyOverlay({ children, probePath = "/api/hosty/session", appCodePath = "/api/auth/app-code", uiPath, panelLayout = "auto", localContent, chrome, navigation }) {
|
|
34
34
|
const actor = useRef(null);
|
|
35
35
|
const changingActor = useRef(false);
|
|
36
36
|
const [session, setSession] = useState(null);
|
|
@@ -168,7 +168,7 @@ export function HostyOverlay({ children, probePath = "/api/hosty/session", appCo
|
|
|
168
168
|
}
|
|
169
169
|
const localOffline = (identity.ui.kind === "unavailable" || (identity.ui.kind === "active" && session?.setup === "unavailable"))
|
|
170
170
|
&& 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, { 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 })] });
|
|
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 })] });
|
|
172
172
|
}
|
|
173
173
|
function OverlaySurface({ pending, title, message, action, actionLabel, reviewUrl }) {
|
|
174
174
|
const [portal, setPortal] = useState(null);
|
package/dist/react.d.ts
CHANGED
|
@@ -109,3 +109,4 @@ export declare function HostThemeBridge({ followSystem, onTheme }?: HostThemeBri
|
|
|
109
109
|
export { MissingPermissionsNotice } from "./permissions-react.js";
|
|
110
110
|
export { AppActivityBridge } from "./activity-react.js";
|
|
111
111
|
export { HostyOverlay } from "./overlay-react.js";
|
|
112
|
+
export { HostyNavigation, HostyNavigationBoundary, useHostyChrome, useHostyNavigation } from "./ui-navigation-react.js";
|
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 { captureChromeHints } from "./ui-composition.js";
|
|
3
4
|
// React slice: the client identity bridge — probe, silent recovery, and the fallback
|
|
4
5
|
// cards. Prop-less by design: the app id and browser-reachable Core origin arrive in the
|
|
5
6
|
// probe response's `recovery` field (request-time values, never build-time props).
|
|
@@ -138,6 +139,7 @@ function currentLaunchMode() {
|
|
|
138
139
|
*/
|
|
139
140
|
export function HostLaunchBridge() {
|
|
140
141
|
useEffect(() => {
|
|
142
|
+
captureChromeHints();
|
|
141
143
|
const url = new URL(window.location.href);
|
|
142
144
|
const param = url.searchParams.get(LAUNCH_MODE_PARAM);
|
|
143
145
|
const { mode, fromParam } = resolveLaunchMode({
|
|
@@ -565,3 +567,4 @@ export function HostThemeBridge({ followSystem = true, onTheme } = {}) {
|
|
|
565
567
|
export { MissingPermissionsNotice } from "./permissions-react.js";
|
|
566
568
|
export { AppActivityBridge } from "./activity-react.js";
|
|
567
569
|
export { HostyOverlay } from "./overlay-react.js";
|
|
570
|
+
export { HostyNavigation, HostyNavigationBoundary, useHostyChrome, useHostyNavigation } from "./ui-navigation-react.js";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { type UiChromeOverrides, type UiChromeSnapshot, type UiChromeSource, type UiEnvironment, type UiNavigationCatalog, type UiNavigationEntry, type UiNavigationSnapshot, type UiRouterAdapter } from "./ui-types.js";
|
|
2
|
+
export declare const CHROME_HINT_ATTRIBUTE = "data-hosty-chrome-hints";
|
|
3
|
+
export declare function parseChromeHints(search: string): UiChromeOverrides & {
|
|
4
|
+
legacy?: "show" | "hide";
|
|
5
|
+
};
|
|
6
|
+
export declare function validChromeOverrides(value: unknown): value is UiChromeOverrides;
|
|
7
|
+
export declare function resolveChrome(defaults: UiChromeOverrides, hints: ReturnType<typeof parseChromeHints>, host?: UiChromeOverrides): {
|
|
8
|
+
navigation: "hide" | "show";
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
launcher: "hide" | "show";
|
|
11
|
+
sources: {
|
|
12
|
+
navigation: UiChromeSource;
|
|
13
|
+
enabled: UiChromeSource;
|
|
14
|
+
launcher: UiChromeSource;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export declare const INITIAL_CHROME: UiChromeSnapshot;
|
|
18
|
+
export declare const INITIAL_NAVIGATION: UiNavigationSnapshot;
|
|
19
|
+
/** Presentation hints are captured only for this document, never from inherited session storage. */
|
|
20
|
+
export declare function captureChromeHints(): string;
|
|
21
|
+
export declare function withChromeHints(href: string, chrome: UiChromeOverrides): string;
|
|
22
|
+
export { chromeBootstrapScript } from "./index.js";
|
|
23
|
+
export declare function activeNavigationKey(entries: readonly UiNavigationEntry[], endpoint: string | undefined, route: string): string | null;
|
|
24
|
+
/** Shared observable state for local and embedded clients; it owns no frame or identity authority. */
|
|
25
|
+
export declare class UiCompositionModel {
|
|
26
|
+
private readonly path;
|
|
27
|
+
private defaults;
|
|
28
|
+
private chrome;
|
|
29
|
+
private navigation;
|
|
30
|
+
private readonly chromeListeners;
|
|
31
|
+
private readonly navigationListeners;
|
|
32
|
+
private readonly hints;
|
|
33
|
+
private overrides;
|
|
34
|
+
private hostRevision;
|
|
35
|
+
private hostGeneration;
|
|
36
|
+
private rootDisabled;
|
|
37
|
+
private capabilities;
|
|
38
|
+
private environment;
|
|
39
|
+
private router?;
|
|
40
|
+
private stopRouter?;
|
|
41
|
+
private endpoint?;
|
|
42
|
+
private epoch;
|
|
43
|
+
private disposed;
|
|
44
|
+
private refreshing;
|
|
45
|
+
constructor(path: string, mode: UiEnvironment["mode"], defaults?: UiChromeOverrides);
|
|
46
|
+
getChrome: () => UiChromeSnapshot;
|
|
47
|
+
subscribeChrome: (listener: () => void) => () => void;
|
|
48
|
+
getNavigation: () => Readonly<{
|
|
49
|
+
status: "loading" | "ready" | "unavailable";
|
|
50
|
+
catalog: UiNavigationCatalog | null;
|
|
51
|
+
entries: readonly UiNavigationEntry[];
|
|
52
|
+
activeKey: string | null;
|
|
53
|
+
code?: string;
|
|
54
|
+
}>;
|
|
55
|
+
subscribeNavigation: (listener: () => void) => () => void;
|
|
56
|
+
configure(defaults: UiChromeOverrides): void;
|
|
57
|
+
setEnvironment(environment: UiEnvironment): void;
|
|
58
|
+
setHostPolicy(generation: string, revision: number, policy: UiChromeOverrides, rootDisabled: boolean, endpoint?: string): void;
|
|
59
|
+
setLegacyHost(): void;
|
|
60
|
+
private publishChrome;
|
|
61
|
+
attachRouter(router?: UiRouterAdapter): void;
|
|
62
|
+
private updateNavigation;
|
|
63
|
+
currentRoute: () => string;
|
|
64
|
+
currentEndpoint: () => string | undefined;
|
|
65
|
+
isLocalEntry(entry: UiNavigationEntry): boolean;
|
|
66
|
+
updateRoute: () => void;
|
|
67
|
+
refreshNavigation: () => Promise<void>;
|
|
68
|
+
resetNavigation(): void;
|
|
69
|
+
navigateLocal(entry: UiNavigationEntry): Promise<boolean>;
|
|
70
|
+
dispose(): void;
|
|
71
|
+
}
|