@fungi.computer/nacre 0.0.0-stage → 0.0.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 (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -2
  3. package/dist/app-directory.d.ts +600 -0
  4. package/dist/app-directory.js +323 -0
  5. package/dist/browser-actions.d.ts +7 -0
  6. package/dist/browser-actions.js +56 -0
  7. package/dist/command-host.d.ts +140 -0
  8. package/dist/command-host.js +532 -0
  9. package/dist/commands.d.ts +25 -0
  10. package/dist/commands.js +78 -0
  11. package/dist/index.d.ts +8 -0
  12. package/dist/index.js +9 -0
  13. package/dist/keys.d.ts +47 -0
  14. package/dist/keys.js +136 -0
  15. package/dist/shell-module.d.ts +98 -0
  16. package/dist/shell-module.js +90 -0
  17. package/dist/window-manager.d.ts +159 -0
  18. package/dist/window-manager.js +796 -0
  19. package/dist/workbench-contract.d.ts +70 -0
  20. package/dist/workbench-contract.js +1 -0
  21. package/dist/workbench-directory.d.ts +343 -0
  22. package/dist/workbench-directory.js +65 -0
  23. package/dist/workbench-host-types.d.ts +114 -0
  24. package/dist/workbench-host-types.js +1 -0
  25. package/dist/workbench-host.d.ts +34 -0
  26. package/dist/workbench-host.js +356 -0
  27. package/dist/workbench-model.d.ts +223 -0
  28. package/dist/workbench-model.js +93 -0
  29. package/dist/workbench.d.ts +4 -0
  30. package/dist/workbench.js +2 -0
  31. package/docs/api/README.md +21 -0
  32. package/docs/api/app-directory.md +587 -0
  33. package/docs/api/command-host.md +225 -0
  34. package/docs/api/commands.md +199 -0
  35. package/docs/api/index.md +1860 -0
  36. package/docs/api/keys.md +183 -0
  37. package/docs/api/shell-module.md +233 -0
  38. package/docs/api/window-manager.md +269 -0
  39. package/docs/api/workbench-directory.md +141 -0
  40. package/docs/api/workbench.md +201 -0
  41. package/package.json +98 -3
package/dist/keys.d.ts ADDED
@@ -0,0 +1,47 @@
1
+ import type { WhistleBindingProjection, WhistleExecutionInput, WhistleExecutionOutcome, WhistleSnapshot } from "@fungi.computer/whistle";
2
+ /** One physical chord parsed from a Whistle binding such as `alt+left`. */
3
+ export type ShellChord = Readonly<{
4
+ /** The key's normalized name, such as left or escape. */
5
+ key: string;
6
+ /** Whether Alt (Option) must be held. */
7
+ alt: boolean;
8
+ /** Whether Control must be held. */
9
+ ctrl: boolean;
10
+ /** Whether Meta (Command or Super) must be held. */
11
+ meta: boolean;
12
+ /** Whether Shift must be held. */
13
+ shift: boolean;
14
+ }>;
15
+ /** Keyboard facts shared by DOM events and terminal adapters. */
16
+ export type KeyEventFacts = Pick<KeyboardEvent, "altKey" | "ctrlKey" | "key" | "metaKey" | "shiftKey">;
17
+ /**
18
+ * Parse one single-chord binding. Sequences (including `<leader>x`) and
19
+ * unknown modifiers return `null`; they are never matched as one chord.
20
+ */
21
+ export declare function parseChord(binding: string, mac: boolean): ShellChord | null;
22
+ /** Whether one key event is exactly this chord. */
23
+ export declare function chordMatches(event: KeyEventFacts, chord: ShellChord): boolean;
24
+ /** One key offered after the leader chord, with the command it runs. */
25
+ export type LeaderKey = WhistleBindingProjection & Readonly<{
26
+ /** The key pressed after the leader chord. */
27
+ key: string;
28
+ }>;
29
+ /** The single-keystroke `<leader>x` rows, first binding per key. */
30
+ export declare function leaderKeys(active: WhistleSnapshot): readonly LeaderKey[];
31
+ /** Whether one key event (after the leader) selects this leader key. */
32
+ export declare function leaderKeyMatches(event: Pick<KeyboardEvent, "altKey" | "ctrlKey" | "key" | "metaKey">, key: string): boolean;
33
+ /** The shell host operations the key dispatcher reads. */
34
+ export type ShellKeyHost = Readonly<{
35
+ /** Read the commands currently available to the focused view. */
36
+ active: () => WhistleSnapshot;
37
+ /** Observe command changes; call the returned disposer to stop listening. */
38
+ subscribe: (listener: () => void) => () => void;
39
+ /** Run a command through the host with the original input origin. */
40
+ execute: (commandId: string, input: WhistleExecutionInput) => Promise<WhistleExecutionOutcome>;
41
+ }>;
42
+ /**
43
+ * Dispatch the active view's single-chord bindings from the shell document.
44
+ * Text entry is a mode: while an input is focused only Ctrl/Cmd chords fire,
45
+ * so Alt-arrows keep their word-navigation meaning.
46
+ */
47
+ export declare function installShellKeys(root: HTMLElement, host: ShellKeyHost, mac: boolean): () => void;
package/dist/keys.js ADDED
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Single-chord and leader binding dispatch for the shell document. App frames
3
+ * own their local key dispatch; reserved shell chords cross through the host.
4
+ */
5
+ /** Whistle binding prefix for a key pressed after the leader chord. */
6
+ const leaderPrefix = "<leader>";
7
+ const keySegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
8
+ const keyNames = new Map([
9
+ ["arrowleft", "left"],
10
+ ["arrowright", "right"],
11
+ ["arrowup", "up"],
12
+ ["arrowdown", "down"],
13
+ ["esc", "escape"],
14
+ [" ", "space"],
15
+ ]);
16
+ /** Normalize a DOM key or binding key name to the binding vocabulary. */
17
+ function keyName(key) {
18
+ const lower = key.toLowerCase();
19
+ return keyNames.get(lower) ?? lower;
20
+ }
21
+ const modifierFields = new Map([
22
+ ["alt", "alt"],
23
+ ["option", "alt"],
24
+ ["ctrl", "ctrl"],
25
+ ["control", "ctrl"],
26
+ ["super", "meta"],
27
+ ["meta", "meta"],
28
+ ["cmd", "meta"],
29
+ ["shift", "shift"],
30
+ ["mod", "platform"],
31
+ ]);
32
+ /**
33
+ * Parse one single-chord binding. Sequences (including `<leader>x`) and
34
+ * unknown modifiers return `null`; they are never matched as one chord.
35
+ */
36
+ export function parseChord(binding, mac) {
37
+ if (binding.startsWith(leaderPrefix) || binding.trim().includes(" "))
38
+ return null;
39
+ const parts = binding.toLowerCase().split("+");
40
+ const key = parts.pop();
41
+ if (key === undefined || key.length === 0)
42
+ return null;
43
+ const chord = {
44
+ key: keyName(key),
45
+ alt: false,
46
+ ctrl: false,
47
+ meta: false,
48
+ shift: false,
49
+ };
50
+ for (const modifier of parts) {
51
+ const field = modifierFields.get(modifier);
52
+ if (field === undefined)
53
+ return null;
54
+ const resolved = field === "platform" ? (mac ? "meta" : "ctrl") : field;
55
+ chord[resolved] = true;
56
+ }
57
+ return chord;
58
+ }
59
+ /** Whether one key event is exactly this chord. */
60
+ export function chordMatches(event, chord) {
61
+ return (keyName(event.key) === chord.key &&
62
+ event.altKey === chord.alt &&
63
+ event.ctrlKey === chord.ctrl &&
64
+ event.metaKey === chord.meta &&
65
+ event.shiftKey === chord.shift);
66
+ }
67
+ /** The single-keystroke `<leader>x` rows, first binding per key. */
68
+ export function leaderKeys(active) {
69
+ const seen = new Set();
70
+ const rows = [];
71
+ for (const row of active.bindings) {
72
+ if (!row.binding.startsWith(leaderPrefix))
73
+ continue;
74
+ const key = row.binding.slice(leaderPrefix.length);
75
+ const segments = keySegmenter.segment(key)[Symbol.iterator]();
76
+ if (segments.next().done || !segments.next().done || seen.has(key))
77
+ continue;
78
+ seen.add(key);
79
+ rows.push({ ...row, key });
80
+ }
81
+ return rows;
82
+ }
83
+ /** Whether one key event (after the leader) selects this leader key. */
84
+ export function leaderKeyMatches(event, key) {
85
+ if (event.altKey || event.ctrlKey || event.metaKey)
86
+ return false;
87
+ return event.key === key || keyName(event.key) === key;
88
+ }
89
+ /** Whether the event started in text entry, including inside shadow roots. */
90
+ function isEditing(event) {
91
+ return event
92
+ .composedPath()
93
+ .some((node) => node instanceof HTMLElement &&
94
+ (node.isContentEditable ||
95
+ node.getAttribute("contenteditable") === "true" ||
96
+ node.matches("input, textarea, select")));
97
+ }
98
+ /**
99
+ * Dispatch the active view's single-chord bindings from the shell document.
100
+ * Text entry is a mode: while an input is focused only Ctrl/Cmd chords fire,
101
+ * so Alt-arrows keep their word-navigation meaning.
102
+ */
103
+ export function installShellKeys(root, host, mac) {
104
+ let chords = [];
105
+ const refresh = () => {
106
+ chords = host.active().bindings.flatMap(({ binding, commandId }) => {
107
+ const chord = parseChord(binding, mac);
108
+ return chord === null ? [] : [{ commandId, chord }];
109
+ });
110
+ };
111
+ const onKeyDown = (event) => {
112
+ if (event.defaultPrevented || event.isComposing)
113
+ return;
114
+ const editing = isEditing(event);
115
+ const match = chords.find(({ chord }) => chordMatches(event, chord) && (!editing || chord.ctrl || chord.meta));
116
+ if (match === undefined)
117
+ return;
118
+ event.preventDefault();
119
+ void host
120
+ .execute(match.commandId, { origin: "keyboard" })
121
+ .catch((cause) => {
122
+ console.warn("Shell key command failed", cause);
123
+ });
124
+ };
125
+ refresh();
126
+ const unsubscribe = host.subscribe(refresh);
127
+ root.addEventListener("keydown", onKeyDown, { capture: true });
128
+ let cleaned = false;
129
+ return () => {
130
+ if (cleaned)
131
+ return;
132
+ cleaned = true;
133
+ unsubscribe();
134
+ root.removeEventListener("keydown", onKeyDown, { capture: true });
135
+ };
136
+ }
@@ -0,0 +1,98 @@
1
+ import type { AgentAccessOperation, AppPermissionOutcome, AppPermissionRequest, ComputerAccessOperation } from "@fungi.computer/agar";
2
+ import type { AppStorage } from "@fungi.computer/agar/storage";
3
+ import type { KeyWire } from "@fungi.computer/ear-tag/keys";
4
+ import type { WhistleJsonValue } from "@fungi.computer/whistle";
5
+ import type { TeamWorkbenchHostHandle } from "./workbench-contract.js";
6
+ import type { WorkbenchHostControls } from "./workbench-host-types.js";
7
+ /**
8
+ * What a host hands the shell a Team picked. The host composes that shell into
9
+ * its own top-level document, so it runs with the host's escalated
10
+ * permissions: the workbench mounts each child App in its sandboxed frame, and
11
+ * `storage`/`methods` are the shell installation's own desktop data.
12
+ */
13
+ export type ShellMountInput = Readonly<{
14
+ /** The workbench the shell drives for the Team that was picked. */
15
+ workbench: TeamWorkbenchHostHandle;
16
+ /** The shell installation's own desktop storage. */
17
+ storage: AppStorage;
18
+ /** The shell installation's backend methods. */
19
+ methods: Readonly<{
20
+ /** Call one of the shell installation's declared backend methods. */
21
+ call(method: string, input: string): Promise<WhistleJsonValue>;
22
+ }>;
23
+ }>;
24
+ /**
25
+ * Render the shell into `root`; resolves once it is mounted, with the
26
+ * function that removes it again. The shell loads its own code and styles.
27
+ */
28
+ export type MountShell = (root: HTMLElement, input: ShellMountInput) => Promise<() => void>;
29
+ /**
30
+ * Settings a shell renders for a host that has none of its own, such as a
31
+ * standalone Oyster: the shell draws them into `root`, and the host hands
32
+ * `controls` to its workbench so Apps and the dashboard can open them.
33
+ */
34
+ export type ShellPlatformControls = Readonly<{
35
+ /** The Settings controls the host hands to its workbench. */
36
+ controls: WorkbenchHostControls;
37
+ /** A child App suggested desktop Settings for one of its surfaces. */
38
+ suggestDesktopSettings(surfaceId: string): void;
39
+ /** Remove the rendered Settings. */
40
+ dispose(): void;
41
+ }>;
42
+ /**
43
+ * A host's App-access flow. The host owns admission, current grants and saving;
44
+ * the shell renders the prompt and navigates its own Settings.
45
+ */
46
+ export type RequestShellAppAccess = (
47
+ /** Current child surface and its host-admitted window Computer. */
48
+ target: Parameters<WorkbenchHostControls["requestAppAccess"]>[0],
49
+ /** Rights requested through the App SDK. */
50
+ request: AppPermissionRequest,
51
+ /** Shell-owned presentation, with no grant authority of its own. */
52
+ dialog: Readonly<{
53
+ /** Ask the member and settle only after the host's save succeeds. */
54
+ ask(request: Readonly<{
55
+ /** Display name supplied by the host's verified installation. */
56
+ appName: string;
57
+ /** Agent operations to present. */
58
+ agents: readonly AgentAccessOperation[];
59
+ /** Computer operations to present. */
60
+ computers: readonly ComputerAccessOperation[];
61
+ /** A host refusal is visible and cannot be converted into an Allow. */
62
+ refusal?: Readonly<{
63
+ code: string;
64
+ message: string;
65
+ }>;
66
+ /** Commit through the host; false means refusal, rejection permits retry. */
67
+ allow(): Promise<boolean>;
68
+ }>): Promise<AppPermissionOutcome>;
69
+ /** Open this installation's Settings, or the App chooser for null. */
70
+ customize(installationId: string | null): "customize";
71
+ }>) => Promise<AppPermissionOutcome>;
72
+ /** Render the shell's own Settings using the embedding host's access authority. */
73
+ export type MountPlatformControls = (root: HTMLElement, input: Readonly<{
74
+ /** Identity used to scope this host's Settings data. */
75
+ teamId: string;
76
+ /** Host-owned key decoders; the shell never imports the host implementation. */
77
+ keysWire: Pick<KeyWire<string>, "issue" | "revoke" | "summary">;
78
+ /** Host-owned approval flow; a shell never decides or writes its own grant. */
79
+ requestAppAccess: RequestShellAppAccess;
80
+ }>) => Promise<ShellPlatformControls>;
81
+ /** What a host gets from a picked shell Release's module. */
82
+ export type ShellModule = Readonly<{
83
+ /** Render the shell into a root element. */
84
+ mountShell: MountShell;
85
+ /** Absent when the shell renders no Settings of its own. */
86
+ mountPlatformControls: MountPlatformControls | null;
87
+ }>;
88
+ /** The picked shell's module could not be loaded or does not fit the contract. */
89
+ export declare class ShellModuleError extends Error {
90
+ /** Always ShellModuleError. */
91
+ readonly name = "ShellModuleError";
92
+ }
93
+ /**
94
+ * Import the picked shell Release's module from the host's own origin and
95
+ * check that it exports `mountShell`, and `mountPlatformControls` when it
96
+ * offers its own Settings. The host never bundles a shell.
97
+ */
98
+ export declare function importShellModule(moduleUrl: URL, hostOrigin: string): Promise<ShellModule>;
@@ -0,0 +1,90 @@
1
+ import * as z from "zod";
2
+ const unsubscribe = () => z.function({ input: [], output: z.void() });
3
+ /** The module namespace a shell Release must export; built on first use,
4
+ * since only a browser has `HTMLElement`. */
5
+ const shellModuleSchema = () => z.object({
6
+ mountShell: z.function({
7
+ // The host supplies the input itself; the shell parses its own handles.
8
+ input: [z.instanceof(HTMLElement), z.unknown()],
9
+ output: z.promise(z.function({ input: [], output: z.void() })),
10
+ }),
11
+ mountPlatformControls: z
12
+ .function({
13
+ input: [
14
+ z.instanceof(HTMLElement),
15
+ // As with mountShell, the trusted host supplies its own input and
16
+ // callback handles. Decode the picked shell's exports and results,
17
+ // not the embedding host's already-typed values.
18
+ z.unknown(),
19
+ ],
20
+ output: z.promise(z.object({
21
+ controls: z.object({
22
+ sections: z.array(z.string()).readonly().optional(),
23
+ // The host passes the workbench's own values; the shell
24
+ // parses what it reads.
25
+ parseSettings: z.function({
26
+ input: [z.unknown()],
27
+ output: z.json(),
28
+ }),
29
+ openSettings: z.function({
30
+ input: [z.unknown(), z.object({ restoreFocus: z.boolean() })],
31
+ output: z.void(),
32
+ }),
33
+ subscribeSettingsClosed: z.function({
34
+ input: [unsubscribe()],
35
+ output: unsubscribe(),
36
+ }),
37
+ subscribeChanges: z.function({
38
+ input: [unsubscribe()],
39
+ output: unsubscribe(),
40
+ }),
41
+ attachDesktopSettings: z.function({
42
+ input: [z.unknown()],
43
+ output: unsubscribe(),
44
+ }),
45
+ requestAppAccess: z.function({
46
+ input: [z.unknown(), z.unknown()],
47
+ output: z.promise(z.enum(["granted", "declined", "customize"])),
48
+ }),
49
+ }),
50
+ suggestDesktopSettings: z.function({
51
+ input: [z.string()],
52
+ output: z.void(),
53
+ }),
54
+ dispose: unsubscribe(),
55
+ })),
56
+ })
57
+ .optional(),
58
+ });
59
+ /** The picked shell's module could not be loaded or does not fit the contract. */
60
+ export class ShellModuleError extends Error {
61
+ /** Always ShellModuleError. */
62
+ name = "ShellModuleError";
63
+ }
64
+ /**
65
+ * Import the picked shell Release's module from the host's own origin and
66
+ * check that it exports `mountShell`, and `mountPlatformControls` when it
67
+ * offers its own Settings. The host never bundles a shell.
68
+ */
69
+ export async function importShellModule(moduleUrl, hostOrigin) {
70
+ if (moduleUrl.origin !== hostOrigin || moduleUrl.hash !== "")
71
+ throw new ShellModuleError("The shell module must come from this host");
72
+ let namespace;
73
+ try {
74
+ namespace = await import(/* @vite-ignore */ moduleUrl.href);
75
+ }
76
+ catch (cause) {
77
+ throw new ShellModuleError("The picked shell could not be loaded", {
78
+ cause,
79
+ });
80
+ }
81
+ const decoded = shellModuleSchema().safeParse(namespace);
82
+ if (!decoded.success)
83
+ throw new ShellModuleError("The picked shell does not export mountShell", {
84
+ cause: decoded.error,
85
+ });
86
+ return {
87
+ mountShell: decoded.data.mountShell,
88
+ mountPlatformControls: decoded.data.mountPlatformControls ?? null,
89
+ };
90
+ }
@@ -0,0 +1,159 @@
1
+ import { type AppNavigationActivation, type AppNavigationState, type AppStatusAction, type AppStatusItem } from "@fungi.computer/agar";
2
+ /**
3
+ * Shared window lifetime owner for browser and terminal shells.
4
+ *
5
+ * This module knows how a window is mounted and disposed. It deliberately does
6
+ * not know how an App is selected, admitted, or transported; those decisions
7
+ * are made by the owner issuing the launch descriptor.
8
+ */
9
+ /** Existing cold-document bound, also used as one total preparation budget. */
10
+ export declare const APP_SURFACE_DOCUMENT_TIMEOUT_MS = 30000;
11
+ /**
12
+ * Shell-only preparation retry. No document, connection or mounted operation
13
+ * belongs inside `prepare`. Each fetch receives the interruptible attempt's
14
+ * signal; the absolute budget covers both requests and backoff, never resets,
15
+ * and is released as soon as preparation succeeds.
16
+ */
17
+ export declare function prepareWithDeliveryRetry<A>(prepare: (signal: AbortSignal) => Promise<A>, signal: AbortSignal, onPending: () => void): Promise<A>;
18
+ /** Minimum identity required to own a window across mount generations. */
19
+ export type WindowLaunch = Readonly<{
20
+ /** The id that owns the window across its mount generations. */
21
+ windowId: string;
22
+ }>;
23
+ /** Current presentation state of a managed window. */
24
+ export type WindowStatus = "loading" | "getting-ready" | "ready" | "minimized" | "error";
25
+ /** Mount operations supplied by the embedding host; selection and authority stay with it. */
26
+ export type WindowHost<Launch extends WindowLaunch> = Readonly<{
27
+ /** Show or hide the current mount without disposing it. */
28
+ setVisibility: (visibility: "hidden" | "visible") => void | Promise<void>;
29
+ /** Reconfigure the mounted App without replacing its iframe or root. */
30
+ reconfigure: (launch: Launch, signal?: AbortSignal) => void | Promise<void>;
31
+ /** Ask the mounted App whether a user initiated close may discard work. */
32
+ requestClose?: () => boolean | Promise<boolean>;
33
+ /** Ask the mounted App to go back in its own history; `true` when it did. */
34
+ requestBack?: () => boolean | Promise<boolean>;
35
+ /** Deliver the person's use of a crumb or header command; `true` when the App acted. */
36
+ activateNavigation?: (activation: AppNavigationActivation) => boolean | Promise<boolean>;
37
+ /** Deliver the person's use of a status item; `true` when the App acted. */
38
+ activateStatus?: (action: AppStatusAction) => boolean | Promise<boolean>;
39
+ /** Release the current mount and its host resources. */
40
+ dispose: () => void | Promise<void>;
41
+ }>;
42
+ /** What a mounted App tells its window while it lives. */
43
+ export type WindowEvents = Readonly<{
44
+ /** Preparation is waiting for delivery; no App operation is being retried. */
45
+ onDeliveryPending?: () => void;
46
+ /** Open this generation's shell-owned Computer picker. */
47
+ onComputerChoice: () => Promise<void>;
48
+ /** The App's latest parsed navigation state; `null` withdraws it. */
49
+ onNavigationState: (state: AppNavigationState | null) => void;
50
+ /** The App's latest parsed status readouts; an empty list clears them. */
51
+ onStatusItems: (items: readonly AppStatusItem[]) => void;
52
+ }>;
53
+ /** Immutable snapshot of one window and its mount generation. */
54
+ export type WindowProjection<Launch extends WindowLaunch, Container> = Readonly<{
55
+ /** The id of the window. */
56
+ windowId: string;
57
+ /** The launch descriptor the window currently has. */
58
+ launch: Launch;
59
+ /** The container the window's App is mounted in. */
60
+ container: Container;
61
+ /** Whether the window is loading, ready, minimized or failed. */
62
+ status: WindowStatus;
63
+ /**
64
+ * Which mount of the window this is; it grows each time the App is mounted
65
+ * afresh.
66
+ */
67
+ generation: number;
68
+ /** The failure that put the window in the error state, if any. */
69
+ error?: unknown;
70
+ /** A failure from releasing the earlier mount, if any. */
71
+ cleanupError?: unknown;
72
+ /** The ready App's navigation state; `null` while it reports none. */
73
+ navigation: AppNavigationState | null;
74
+ /** The App's status readouts for its window's status bar. */
75
+ statusItems: readonly AppStatusItem[];
76
+ }>;
77
+ /** Current ordered collection of managed window snapshots. */
78
+ export type WindowsProjection<Launch extends WindowLaunch, Container> = Readonly<{
79
+ /** The managed windows, in order. */
80
+ windows: readonly WindowProjection<Launch, Container>[];
81
+ }>;
82
+ /** Whether any projected App window is still visible in the workspace. */
83
+ export declare function hasVisibleWindows<Launch extends WindowLaunch, Container>(projection: WindowsProjection<Launch, Container>): boolean;
84
+ /** Create a host-owned mount in a container, observing cancellation and navigation. */
85
+ export type WindowMount<Launch extends WindowLaunch, Container> = (container: Container, launch: Launch, signal: AbortSignal, events: WindowEvents) => WindowHost<Launch> | Promise<WindowHost<Launch>>;
86
+ /** Own window lifetimes, visibility and serialized host mutations. */
87
+ export type WindowManager<Launch extends WindowLaunch, Container> = Readonly<{
88
+ /** Open a window or restore the existing window with the same identity. */
89
+ open: (launch: Launch) => Promise<void>;
90
+ /** Notify the manager that a waiting container is attached. */
91
+ attach: (windowId: string) => void;
92
+ /** Hide one window without disposing its mount. */
93
+ minimize: (windowId: string) => Promise<void>;
94
+ /** Show an individually hidden window. */
95
+ restore: (windowId: string) => Promise<void>;
96
+ /** Hide visible Apps without closing them; preserve individually minimized Apps. */
97
+ hideAll: () => Promise<void>;
98
+ /** Restore surviving windows from the last Show desktop operation. */
99
+ restoreHidden: () => Promise<void>;
100
+ /** Consult the App before disposing and removing its window. */
101
+ close: (windowId: string) => Promise<void>;
102
+ /** Update a mounted window through the host after validation. */
103
+ reconfigure: (windowId: string, launch: Launch) => Promise<void>;
104
+ /**
105
+ * Reconfigure from the launch committed when the window's mutation queue
106
+ * reaches this change, so it never reapplies a stale snapshot over an
107
+ * earlier queued change.
108
+ */
109
+ update: (windowId: string, derive: (current: Launch) => Launch) => Promise<void>;
110
+ /** Consult the old App, dispose it, then create a new mount generation. */
111
+ replace: (windowId: string, launch: Launch) => Promise<void>;
112
+ /** Ask a ready window's App to go back; `false` when it cannot. */
113
+ back: (windowId: string) => Promise<boolean>;
114
+ /** Deliver a crumb or header command to a ready window's App; `false` when it cannot. */
115
+ activateNavigation: (windowId: string, activation: AppNavigationActivation) => Promise<boolean>;
116
+ /** Deliver a status item action to a ready window's App; `false` when it cannot. */
117
+ activateStatus: (windowId: string, action: AppStatusAction) => Promise<boolean>;
118
+ /** Register the current window generation's host-owned picker. */
119
+ registerComputerChoice: (windowId: string, handler: () => void) => () => void;
120
+ /** Dispose all surviving windows and release subscriptions. */
121
+ dispose: () => Promise<void>;
122
+ /** Read the current immutable window projection. */
123
+ read: () => WindowsProjection<Launch, Container>;
124
+ /** Observe projection changes until the returned disposer is called. */
125
+ subscribe: (listener: () => void) => () => void;
126
+ }>;
127
+ /** Host adapters used to create, attach and validate window mounts. */
128
+ export type WindowManagerOptions<Launch extends WindowLaunch, Container> = Readonly<{
129
+ /** Create the App's mount inside a window's container. */
130
+ mount: WindowMount<Launch, Container>;
131
+ /** Create a container for a new window generation. */
132
+ createContainer: (launch: Launch) => Container;
133
+ /**
134
+ * A renderer may create a projection container before attaching it. Waiting
135
+ * here keeps iframe and Agar setup inside a real document tree.
136
+ */
137
+ waitForAttachment?: boolean;
138
+ /** Observe mount or mutation failures, including failures after retirement. */
139
+ onFailure?: (failure: Readonly<{
140
+ windowId: string;
141
+ generation: number | null;
142
+ phase: "mount" | "mutation";
143
+ cause: unknown;
144
+ }>) => void;
145
+ /** Report whether the renderer has attached a container. */
146
+ isAttached?: (container: Container) => boolean;
147
+ /** Reject an inadmissible launch change before it reaches the mounted host. */
148
+ validateReconfiguration?: (current: Launch, next: Launch) => void;
149
+ /**
150
+ * Whether `next` differs from `current` only in shell chrome the mounted
151
+ * App never sees. Such a change is committed without reconfiguring the App.
152
+ */
153
+ isChromeOnlyChange?: (current: Launch, next: Launch) => boolean;
154
+ }>;
155
+ /**
156
+ * Owns App-window generations while leaving selection and App authority to the
157
+ * caller. A stale mount is always disposed when it eventually resolves.
158
+ */
159
+ export declare function createWindowManager<Launch extends WindowLaunch, Container>(options: WindowManagerOptions<Launch, Container>): WindowManager<Launch, Container>;