@fungi.computer/tourtois 0.1.0 → 0.2.0
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 +19 -0
- package/dist/AppAccessConsent.d.ts +6 -1
- package/dist/AppAccessConsent.js +4 -5
- package/dist/AppDesktopSettings.d.ts +2 -2
- package/dist/AppPane.d.ts +11 -4
- package/dist/AppPane.js +50 -10
- package/dist/AppWorkbench.d.ts +1 -1
- package/dist/AppWorkbench.js +10 -5
- package/dist/AvatarPreview.d.ts +8 -1
- package/dist/AvatarPreview.js +9 -1
- package/dist/Keys.d.ts +42 -121
- package/dist/Keys.js +200 -157
- package/dist/OysterComputers.d.ts +22 -0
- package/dist/OysterComputers.js +177 -0
- package/dist/SettingsNavigation.d.ts +1 -1
- package/dist/SettingsNavigation.js +1 -1
- package/dist/SystemSettings.d.ts +2 -2
- package/dist/SystemSettings.js +1 -1
- package/dist/TeamDesktop.d.ts +1 -1
- package/dist/app-authority.d.ts +37 -37
- package/dist/app-authority.js +1 -1
- package/dist/app-directory-reconciliation.js +7 -2
- package/dist/app-directory.d.ts +6 -5
- package/dist/app-directory.js +4 -1
- package/dist/app-launch.d.ts +1 -1
- package/dist/app-launch.js +6 -3
- package/dist/app-menus.d.ts +1 -1
- package/dist/app-surface.d.ts +2 -1
- package/dist/app-windows.d.ts +5 -2
- package/dist/app-windows.js +1 -1
- package/dist/command-dialogs.d.ts +1 -1
- package/dist/command-dialogs.js +4 -4
- package/dist/desktop/Desktop.d.ts +1 -1
- package/dist/desktop/consent.d.ts +1 -1
- package/dist/desktop/consent.js +1 -1
- package/dist/desktop/row-gill.d.ts +1 -1
- package/dist/oyster-platform-controls.d.ts +1 -1
- package/dist/oyster-platform-controls.js +64 -23
- package/dist/platform-controls.d.ts +6 -5
- package/dist/platform-controls.js +3 -3
- package/dist/queries.d.ts +4 -0
- package/dist/queries.js +4 -0
- package/dist/shell-commands.d.ts +1 -1
- package/dist/shell-commands.js +4 -4
- package/dist/shell-host.d.ts +7 -9
- package/dist/shell-host.js +36 -10
- package/dist/shell-module.d.ts +1 -1
- package/dist/shell.d.ts +1 -1
- package/dist/shell.js +5 -9
- package/dist/useAppWindowManager.d.ts +4 -1
- package/dist/useAppWindowManager.js +3 -0
- package/package.json +39 -18
package/README.md
CHANGED
|
@@ -123,6 +123,25 @@ layout. [src/shell-module.ts](src/shell-module.ts) is the `mountShell` entry,
|
|
|
123
123
|
can supply. The agent tools are in [backend/methods.ts](backend/methods.ts), and
|
|
124
124
|
[package.json](package.json) declares what the shell asks of its host.
|
|
125
125
|
|
|
126
|
+
### Open windows and new versions
|
|
127
|
+
|
|
128
|
+
The directory refreshes every 15 seconds while the desktop is visible, as well
|
|
129
|
+
as on host change notifications. A changed active Release leaves the existing
|
|
130
|
+
App mounted and offers **New version available — Reload** in its existing window
|
|
131
|
+
footer. The footer already owns host status; the header remains the App's
|
|
132
|
+
navigation and window controls.
|
|
133
|
+
|
|
134
|
+
Reload uses Oyster's ordinary window replacement and asks the mounted App's
|
|
135
|
+
close veto before disposing it. Refusal leaves the old window and the Reload
|
|
136
|
+
offer intact. Acceptance mounts the active Release; closing and reopening also
|
|
137
|
+
uses the active Release without a notice. This does not transfer unsaved memory
|
|
138
|
+
or preserve obsolete backend authority. Permission changes and explicit
|
|
139
|
+
selection-changed recovery retain their existing reconciliation behavior.
|
|
140
|
+
|
|
141
|
+
With the Tourtois workbench running, `pnpm test:window-new-version` exercises
|
|
142
|
+
the directory change, footer action and reopen at desktop and phone widths.
|
|
143
|
+
`test/window-status-bar.test.tsx` proves the Reload close veto.
|
|
144
|
+
|
|
126
145
|
## License
|
|
127
146
|
|
|
128
147
|
MIT. See [LICENSE](LICENSE).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentAccessOperation, AppPermissionOutcome, ComputerAccessOperation } from "@fungi.computer/agar";
|
|
2
|
-
import type { AppAgentPermission, AppComputerPermission } from "@fungi.computer/
|
|
2
|
+
import type { AppAgentPermission, AppComputerPermission } from "@fungi.computer/nacre/app-directory";
|
|
3
3
|
export declare const consentableAgentOperations: (requested: readonly AgentAccessOperation[], declared: readonly string[]) => readonly AgentAccessOperation[];
|
|
4
4
|
export declare const consentableComputerOperations: (requested: readonly ComputerAccessOperation[], declared: readonly string[]) => readonly ComputerAccessOperation[];
|
|
5
5
|
/** The all-Team-Agents grant that adds `offered` to the current policy. */
|
|
@@ -13,6 +13,11 @@ export type AppAccessRequest = Readonly<{
|
|
|
13
13
|
appName: string;
|
|
14
14
|
agents: readonly AgentAccessOperation[];
|
|
15
15
|
computers: readonly ComputerAccessOperation[];
|
|
16
|
+
/** A host refusal is visible and cannot be converted into an Allow. */
|
|
17
|
+
refusal?: Readonly<{
|
|
18
|
+
code: string;
|
|
19
|
+
message: string;
|
|
20
|
+
}>;
|
|
16
21
|
/** Save the grant through the owner's own authority; resolves false when it failed. */
|
|
17
22
|
allow: () => Promise<boolean>;
|
|
18
23
|
settle: (outcome: AppPermissionOutcome) => void;
|
package/dist/AppAccessConsent.js
CHANGED
|
@@ -98,12 +98,12 @@ export function AppAccessConsent({ request, }) {
|
|
|
98
98
|
? `Let ${request.appName} work with your ${resources(request, "Agents", "Computers")}?`
|
|
99
99
|
: "" }), _jsx(DialogDescription, { children: request
|
|
100
100
|
? `${request.appName} can ${describe([...request.agents, ...request.computers])} for every ${resources(request, "Agent", "Computer")} in this Oyster. You can change this later in System Settings.`
|
|
101
|
-
: "" }), failed ? (_jsx("p", { role: "alert", children: "Access could not be saved. Try again." })) : null, _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "ghost", disabled: saving, onClick: () => {
|
|
101
|
+
: "" }), request?.refusal !== undefined ? (_jsx("p", { role: "alert", "data-refusal-code": request.refusal.code, children: request.refusal.message })) : null, failed ? (_jsx("p", { role: "alert", children: "Access could not be saved. Try again." })) : null, _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "ghost", disabled: saving, onClick: () => {
|
|
102
102
|
settle("customize");
|
|
103
103
|
}, children: "Customize\u2026" }), _jsx(Button, { variant: "ghost", disabled: saving, onClick: () => {
|
|
104
104
|
settle("declined");
|
|
105
|
-
}, children: "Not now" }), _jsx(Button, { disabled: saving || request === null, onClick: () => {
|
|
106
|
-
if (request === null)
|
|
105
|
+
}, children: "Not now" }), _jsx(Button, { disabled: saving || request === null || request.refusal !== undefined, onClick: () => {
|
|
106
|
+
if (request === null || request.refusal !== undefined)
|
|
107
107
|
return;
|
|
108
108
|
setSaving(true);
|
|
109
109
|
setFailed(false);
|
|
@@ -111,8 +111,7 @@ export function AppAccessConsent({ request, }) {
|
|
|
111
111
|
if (saved)
|
|
112
112
|
settle("granted");
|
|
113
113
|
else {
|
|
114
|
-
|
|
115
|
-
setFailed(true);
|
|
114
|
+
settle("declined");
|
|
116
115
|
}
|
|
117
116
|
}, () => {
|
|
118
117
|
setSaving(false);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { WorkbenchDesktopSettings } from "@fungi.computer/
|
|
2
|
-
import type { AppDirectoryEntry, AppDirectoryGill } from "@fungi.computer/
|
|
1
|
+
import type { WorkbenchDesktopSettings } from "@fungi.computer/nacre/workbench";
|
|
2
|
+
import type { AppDirectoryEntry, AppDirectoryGill } from "@fungi.computer/nacre/app-directory";
|
|
3
3
|
export type AppDesktopSettingsApp = Pick<AppDirectoryEntry, "surfaceId" | "availability"> & Readonly<{
|
|
4
4
|
gills: readonly AppDirectoryGill[];
|
|
5
5
|
}>;
|
package/dist/AppPane.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type AppStatusAction } from "@fungi.computer/agar";
|
|
2
|
-
import { createAppWindowManager, type AppWindowProjection } from "./app-windows.js";
|
|
2
|
+
import { createAppWindowManager, type AppWindowProjection, type AppLaunchDescriptor } from "./app-windows.js";
|
|
3
3
|
import { type AppDirectoryApp } from "./app-launch.js";
|
|
4
4
|
/** The focused window's App-level settings; its Computer lives in its status bar. */
|
|
5
5
|
export declare function FocusedWindowControls({ app, onOpenSettings, onOpenPermissions, projection, }: {
|
|
@@ -8,8 +8,9 @@ export declare function FocusedWindowControls({ app, onOpenSettings, onOpenPermi
|
|
|
8
8
|
onOpenPermissions: () => void;
|
|
9
9
|
projection: AppWindowProjection;
|
|
10
10
|
}): import("react/jsx-runtime").JSX.Element | null;
|
|
11
|
-
export declare function AppPane({ projection, icon, manager, onError, index, focusWindow, afterHide, onBack, onSelectComputer, }: {
|
|
11
|
+
export declare function AppPane({ projection, nextLaunch, icon, manager, onError, index, focusWindow, afterHide, onBack, onSelectComputer, }: {
|
|
12
12
|
projection: AppWindowProjection;
|
|
13
|
+
nextLaunch?: AppLaunchDescriptor | null;
|
|
13
14
|
icon: string | null | undefined;
|
|
14
15
|
manager: ReturnType<typeof createAppWindowManager>;
|
|
15
16
|
onError: (message: string) => void;
|
|
@@ -24,9 +25,13 @@ export declare function AppPane({ projection, icon, manager, onError, index, foc
|
|
|
24
25
|
* One window's status bar, owned by the shell like its title bar: the
|
|
25
26
|
* window's Computer first (only the person changes it, here), then the items
|
|
26
27
|
* the App declared as data. The shell renders every item with Caps pieces and
|
|
27
|
-
* hands the person's use back to the App.
|
|
28
|
+
* hands the person's use back to the App. Release availability shares this
|
|
29
|
+
* existing footer instead of competing with App navigation in the header.
|
|
30
|
+
* Reload sits beside the App's far-end readouts. Phone chrome keeps one row:
|
|
31
|
+
* a shorter notice takes precedence over Inkcap's low-value file count, never
|
|
32
|
+
* over the App's save state. Desktop presentation stays unchanged.
|
|
28
33
|
*/
|
|
29
|
-
export declare function WindowStatusBar({ projection, onSelectComputer, onAction, surface, computerPickerOpen, onComputerPickerOpenChange, }: {
|
|
34
|
+
export declare function WindowStatusBar({ projection, onSelectComputer, onAction, surface, computerPickerOpen, onComputerPickerOpenChange, onReload, }: {
|
|
30
35
|
projection: AppWindowProjection;
|
|
31
36
|
onSelectComputer: (computerId: string) => void;
|
|
32
37
|
onAction: (action: AppStatusAction) => void;
|
|
@@ -34,4 +39,6 @@ export declare function WindowStatusBar({ projection, onSelectComputer, onAction
|
|
|
34
39
|
surface?: () => HTMLElement | null;
|
|
35
40
|
computerPickerOpen?: boolean;
|
|
36
41
|
onComputerPickerOpenChange?: (open: boolean) => void;
|
|
42
|
+
/** Uses the window manager's ordinary, veto-respecting replacement. */
|
|
43
|
+
onReload?: (() => void) | undefined;
|
|
37
44
|
}): import("react/jsx-runtime").JSX.Element | null;
|
package/dist/AppPane.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { IconButton } from "@fungi.computer/caps/components/icon-button";
|
|
3
|
-
import { AppStatusBar, StatusBarPicker, } from "@fungi.computer/caps/blocks/app-status-bar";
|
|
3
|
+
import { AppStatusBar, AppStatusItem, StatusBarChip, StatusBarPicker, } from "@fungi.computer/caps/blocks/app-status-bar";
|
|
4
4
|
import { Sigil } from "@fungi.computer/sigils";
|
|
5
|
+
import { describeAppError } from "@fungi.computer/agar/errors";
|
|
5
6
|
import { WindowActions, WindowBody, WindowFrame, WindowHeader, } from "@fungi.computer/caps/components/window";
|
|
6
7
|
import { Settings, ShieldCheck } from "lucide-react";
|
|
7
8
|
import { WindowStatusItem } from "./status-bar.js";
|
|
@@ -42,9 +43,11 @@ function headerContent(navigation, popover, activate) {
|
|
|
42
43
|
}
|
|
43
44
|
const windowStatusLabel = (projection) => {
|
|
44
45
|
if (projection.status === "error")
|
|
45
|
-
return
|
|
46
|
+
return describeAppError(projection.error).message;
|
|
46
47
|
if (projection.status === "loading")
|
|
47
48
|
return `${projection.launch.title} opening…`;
|
|
49
|
+
if (projection.status === "getting-ready")
|
|
50
|
+
return "Getting ready…";
|
|
48
51
|
return projection.launch.title;
|
|
49
52
|
};
|
|
50
53
|
/** The focused window's App-level settings; its Computer lives in its status bar. */
|
|
@@ -53,7 +56,7 @@ export function FocusedWindowControls({ app, onOpenSettings, onOpenPermissions,
|
|
|
53
56
|
return null;
|
|
54
57
|
return (_jsxs("div", { className: "tourtois-focused-controls", role: "group", "aria-label": `${projection.launch.title} settings`, children: [_jsx(IconButton, { size: "responsive", "aria-label": `Permissions for ${projection.launch.title}`, title: "App permissions", icon: _jsx(ShieldCheck, {}), onClick: onOpenPermissions }), _jsx(IconButton, { size: "responsive", "aria-label": `Settings for ${projection.launch.title}`, title: "App settings", icon: _jsx(Settings, {}), onClick: onOpenSettings })] }));
|
|
55
58
|
}
|
|
56
|
-
export function AppPane({ projection, icon, manager, onError, index, focusWindow, afterHide, onBack, onSelectComputer, }) {
|
|
59
|
+
export function AppPane({ projection, nextLaunch, icon, manager, onError, index, focusWindow, afterHide, onBack, onSelectComputer, }) {
|
|
57
60
|
const containerRef = useRef(null);
|
|
58
61
|
const popover = useLayer("popover");
|
|
59
62
|
const [computerPickerOpen, setComputerPickerOpen] = useState(false);
|
|
@@ -170,9 +173,21 @@ export function AppPane({ projection, icon, manager, onError, index, focusWindow
|
|
|
170
173
|
console.error("App workbench operation failed", cause);
|
|
171
174
|
onError("App could not be minimized.");
|
|
172
175
|
});
|
|
173
|
-
}, onClose: close }) }) }), projection.status === "loading"
|
|
176
|
+
}, onClose: close }) }) }), projection.status === "loading" ||
|
|
177
|
+
projection.status === "getting-ready" ? (_jsxs("div", { className: "tourtois-app-loading", role: "status", children: [_jsx(AppIcon, { roles: projection.launch.roles, icon: icon }), _jsxs("div", { children: [_jsx("strong", { children: projection.status === "getting-ready"
|
|
178
|
+
? "Getting ready…"
|
|
179
|
+
: `Opening ${projection.launch.title}…` }), _jsx("span", { children: "Preparing this App for your workspace." })] })] })) : null, projection.status === "error" &&
|
|
174
180
|
!(projection.error instanceof AppError &&
|
|
175
|
-
projection.error.code === "DISCONNECTED") ? (_jsx("p", { className: "tourtois-window-status", role: "status", children: windowStatusLabel(projection) })) : null, _jsx(WindowBody, { ref: containerRef, className: "tourtois-pane-body", hidden: projection.status === "error" }), _jsx(WindowStatusBar, { projection: projection,
|
|
181
|
+
projection.error.code === "DISCONNECTED") ? (_jsx("p", { className: "tourtois-window-status", role: "status", children: windowStatusLabel(projection) })) : null, _jsx(WindowBody, { ref: containerRef, className: "tourtois-pane-body", hidden: projection.status === "error" }), _jsx(WindowStatusBar, { projection: projection, onReload: nextLaunch && nextLaunch.releaseId !== projection.launch.releaseId
|
|
182
|
+
? () => {
|
|
183
|
+
void manager
|
|
184
|
+
.replace(projection.windowId, nextLaunch)
|
|
185
|
+
.catch((cause) => {
|
|
186
|
+
console.error("App reload failed", cause);
|
|
187
|
+
onError("App could not be reloaded.");
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
: undefined, computerPickerOpen: computerPickerOpen, onComputerPickerOpenChange: setComputerPickerOpen, onSelectComputer: (computerId) => {
|
|
176
191
|
onSelectComputer(projection.windowId, computerId);
|
|
177
192
|
}, surface: () => containerRef.current, onAction: (action) => {
|
|
178
193
|
void manager
|
|
@@ -187,13 +202,30 @@ export function AppPane({ projection, icon, manager, onError, index, focusWindow
|
|
|
187
202
|
* One window's status bar, owned by the shell like its title bar: the
|
|
188
203
|
* window's Computer first (only the person changes it, here), then the items
|
|
189
204
|
* the App declared as data. The shell renders every item with Caps pieces and
|
|
190
|
-
* hands the person's use back to the App.
|
|
205
|
+
* hands the person's use back to the App. Release availability shares this
|
|
206
|
+
* existing footer instead of competing with App navigation in the header.
|
|
207
|
+
* Reload sits beside the App's far-end readouts. Phone chrome keeps one row:
|
|
208
|
+
* a shorter notice takes precedence over Inkcap's low-value file count, never
|
|
209
|
+
* over the App's save state. Desktop presentation stays unchanged.
|
|
191
210
|
*/
|
|
192
|
-
export function WindowStatusBar({ projection, onSelectComputer, onAction, surface, computerPickerOpen, onComputerPickerOpenChange, }) {
|
|
211
|
+
export function WindowStatusBar({ projection, onSelectComputer, onAction, surface, computerPickerOpen, onComputerPickerOpenChange, onReload, }) {
|
|
193
212
|
const popover = useLayer("popover");
|
|
213
|
+
const [phone, setPhone] = useState(false);
|
|
214
|
+
useEffect(() => {
|
|
215
|
+
// Match Caps' phone status-bar breakpoint, including its touch targets.
|
|
216
|
+
const media = window.matchMedia("(max-width: 640px)");
|
|
217
|
+
const update = () => {
|
|
218
|
+
setPhone(media.matches);
|
|
219
|
+
};
|
|
220
|
+
update();
|
|
221
|
+
media.addEventListener("change", update);
|
|
222
|
+
return () => {
|
|
223
|
+
media.removeEventListener("change", update);
|
|
224
|
+
};
|
|
225
|
+
}, []);
|
|
194
226
|
const { computers, title } = projection.launch;
|
|
195
227
|
const items = projection.statusItems;
|
|
196
|
-
if (computers === undefined && items.length === 0)
|
|
228
|
+
if (computers === undefined && items.length === 0 && !onReload)
|
|
197
229
|
return null;
|
|
198
230
|
const anchorFor = (key) => {
|
|
199
231
|
const origin = surface?.()?.getBoundingClientRect();
|
|
@@ -207,7 +239,12 @@ export function WindowStatusBar({ projection, onSelectComputer, onAction, surfac
|
|
|
207
239
|
};
|
|
208
240
|
const render = (item) => (_jsx(WindowStatusItem, { item: item, container: popover, onAction: onAction, anchorFor: anchorFor }, item.id));
|
|
209
241
|
const end = items.filter((item) => item.end === true);
|
|
210
|
-
const start = items.filter((item) => item.end !== true
|
|
242
|
+
const start = items.filter((item) => item.end !== true &&
|
|
243
|
+
!(phone &&
|
|
244
|
+
projection.launch.appId === "inkcap" &&
|
|
245
|
+
item.kind === "text" &&
|
|
246
|
+
item.id === "documents"));
|
|
247
|
+
const appReadouts = end.length === 0 ? undefined : end.map(render);
|
|
211
248
|
return (_jsx(AppStatusBar, { className: "tourtois-window-status-bar", "aria-label": `${title} status`, attachment: computers === undefined ? undefined : (_jsx(StatusBarPicker, { ...(computerPickerOpen === undefined
|
|
212
249
|
? {}
|
|
213
250
|
: { open: computerPickerOpen }), ...(onComputerPickerOpenChange === undefined
|
|
@@ -215,5 +252,8 @@ export function WindowStatusBar({ projection, onSelectComputer, onAction, surfac
|
|
|
215
252
|
: { onOpenChange: onComputerPickerOpenChange }), noun: "Computer", pluralNoun: "Computers", accessibleLabel: `Choose Computer for ${title}`, icon: _jsx(Sigil, { name: "computer", size: 16 }), options: computers.map((computer) => ({
|
|
216
253
|
id: computer.computerId,
|
|
217
254
|
label: computer.name,
|
|
218
|
-
})), value: launchComputerId(projection.launch.href), emptyMessage: "No Computers are granted to this App", disabled: projection.status === "loading"
|
|
255
|
+
})), value: launchComputerId(projection.launch.href), emptyMessage: "No Computers are granted to this App", disabled: projection.status === "loading" ||
|
|
256
|
+
projection.status === "getting-ready", onSelect: onSelectComputer })), end: onReload ? (_jsxs("span", { className: phone
|
|
257
|
+
? "flex min-w-0 items-center gap-xs"
|
|
258
|
+
: "flex min-w-0 flex-wrap items-center gap-xs", children: [appReadouts, _jsxs(AppStatusItem, { children: [_jsx("span", { role: "status", children: phone ? "Update ready" : "New version available" }), _jsx(StatusBarChip, { label: "Reload", "aria-label": `Reload ${title}`, onClick: onReload })] })] })) : (appReadouts), children: start.length === 0 ? undefined : start.map(render) }));
|
|
219
259
|
}
|
package/dist/AppWorkbench.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { DesktopMethods } from "./desktop-api.js";
|
|
|
2
2
|
import type { AppOpenInput } from "@fungi.computer/agar/apps";
|
|
3
3
|
import { type AppBarItem } from "@fungi.computer/caps/blocks/app-bar";
|
|
4
4
|
import { type ReactNode, type RefObject } from "react";
|
|
5
|
-
import type { WorkbenchDirectory } from "@fungi.computer/
|
|
5
|
+
import type { WorkbenchDirectory } from "@fungi.computer/nacre/workbench-directory";
|
|
6
6
|
import { type ComputerOption } from "./app-launch.js";
|
|
7
7
|
import type { PlatformControls } from "./platform-controls.js";
|
|
8
8
|
import type { AppStorage } from "@fungi.computer/agar/storage";
|
package/dist/AppWorkbench.js
CHANGED
|
@@ -7,7 +7,7 @@ import { WindowSnapPreview } from "@fungi.computer/caps/components/window";
|
|
|
7
7
|
import { MaosaicoRegion, useLayer, useMaosaicoLayout, } from "@fungi.computer/maosaico";
|
|
8
8
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore, } from "react";
|
|
9
9
|
import { createPortal } from "react-dom";
|
|
10
|
-
import { addressKey } from "@fungi.computer/
|
|
10
|
+
import { addressKey } from "@fungi.computer/nacre/app-directory";
|
|
11
11
|
import { reconcileAppDirectory, reconcileAppWindowFromDirectory, } from "./app-directory-reconciliation.js";
|
|
12
12
|
import { useAppDirectory } from "./app-directory.js";
|
|
13
13
|
import { AppIcon } from "./app-icon.js";
|
|
@@ -238,7 +238,9 @@ export function AppWorkbench({ paneLayerRef, phone, teamId, teamName, initialCom
|
|
|
238
238
|
void refetchApps();
|
|
239
239
|
}), [platform, refetchApps]);
|
|
240
240
|
const launchFor = (installation, currentLaunch) => {
|
|
241
|
-
return createAppLaunchDescriptor(installation, currentLaunch
|
|
241
|
+
return createAppLaunchDescriptor(installation, currentLaunch
|
|
242
|
+
? launchComputerId(currentLaunch.href)
|
|
243
|
+
: (appsQuery.data?.computer?.computerId ?? initialComputerId), window.location.origin, computers, computersUnavailable);
|
|
242
244
|
};
|
|
243
245
|
const refreshAppDirectory = async (launch) => {
|
|
244
246
|
const attempts = selectionRefreshes.current.get(launch.windowId) ?? 0;
|
|
@@ -546,9 +548,12 @@ export function AppWorkbench({ paneLayerRef, phone, teamId, teamName, initialCom
|
|
|
546
548
|
return app ? (launchFor(app)?.computers ?? []) : [];
|
|
547
549
|
}, storage: desktopStorage, methods: desktopMethods, transport: gills, onRetryApps: () => {
|
|
548
550
|
void appsQuery.refetch();
|
|
549
|
-
} }) }), _jsxs(MaosaicoRegion, { layer: "windows", style: { height: "100%", pointerEvents: "none" }, ref: paneLayerRef, className: "tourtois-pane-layer", "data-layout-mode": mode, children: [appProjection.windows.map((projection, index) =>
|
|
550
|
-
|
|
551
|
-
|
|
551
|
+
} }) }), _jsxs(MaosaicoRegion, { layer: "windows", style: { height: "100%", pointerEvents: "none" }, ref: paneLayerRef, className: "tourtois-pane-layer", "data-layout-mode": mode, children: [appProjection.windows.map((projection, index) => {
|
|
552
|
+
const app = directory.find((item) => item.surfaceId === projection.launch.surfaceId);
|
|
553
|
+
return (_jsx(AppPane, { index: index, focusWindow: focusWindow, afterHide: afterHide, onBack: goBack, manager: appWindows, projection: projection, nextLaunch: app ? launchFor(app, projection.launch) : null, icon: app?.icon, onError: setAppStatus, onSelectComputer: (windowId, computerId) => {
|
|
554
|
+
void selectWindowComputer(windowId, computerId);
|
|
555
|
+
} }, projection.windowId));
|
|
556
|
+
}), extraPanes?.({ activeId, order, focus }), layout.snapPreview && (_jsx(WindowSnapPreview, { "data-snap-side": layout.snapPreview.side, style: {
|
|
552
557
|
left: layout.snapPreview.frame.x,
|
|
553
558
|
top: layout.snapPreview.frame.y,
|
|
554
559
|
width: layout.snapPreview.frame.width,
|
package/dist/AvatarPreview.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import type { SpriteService } from "@fungi.computer/sprite";
|
|
1
2
|
import type { Avatar } from "@fungi.computer/sprite/avatar";
|
|
2
|
-
import type
|
|
3
|
+
import { type CSSProperties } from "react";
|
|
4
|
+
/**
|
|
5
|
+
* Replaces the shared Shiitake sprite service for every preview below it;
|
|
6
|
+
* a host or test that must not draw live sprites supplies a fake. Absent, previews
|
|
7
|
+
* use Parakeet's shared service.
|
|
8
|
+
*/
|
|
9
|
+
export declare const AvatarSpriteServiceContext: import("react").Context<SpriteService | undefined>;
|
|
3
10
|
/** Member and picker previews use Parakeet's shared avatar mapper and service. */
|
|
4
11
|
export declare function AvatarPreview({ avatar, activity, expression, className, style, }: {
|
|
5
12
|
avatar: Avatar;
|
package/dist/AvatarPreview.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { ShiitakeAvatar } from "@fungi.computer/parakeet/agent-head";
|
|
3
|
+
import { createContext, useContext } from "react";
|
|
4
|
+
/**
|
|
5
|
+
* Replaces the shared Shiitake sprite service for every preview below it;
|
|
6
|
+
* a host or test that must not draw live sprites supplies a fake. Absent, previews
|
|
7
|
+
* use Parakeet's shared service.
|
|
8
|
+
*/
|
|
9
|
+
export const AvatarSpriteServiceContext = createContext(undefined);
|
|
3
10
|
/** Member and picker previews use Parakeet's shared avatar mapper and service. */
|
|
4
11
|
export function AvatarPreview({ avatar, activity = "idle", expression = avatar.expression, className, style, }) {
|
|
5
|
-
|
|
12
|
+
const service = useContext(AvatarSpriteServiceContext);
|
|
13
|
+
return (_jsx("span", { className: className, style: { display: "block", ...style }, "aria-hidden": "true", children: _jsx(ShiitakeAvatar, { avatar: avatar, className: "h-full w-full", ...(service ? { service } : {}), state: { activity, expression } }) }));
|
|
6
14
|
}
|
package/dist/Keys.d.ts
CHANGED
|
@@ -1,131 +1,52 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import type { Grant } from "@fungi.computer/ear-tag/grants";
|
|
2
|
+
import type { IssueKeyInput, KeySummary, UpdateGrantsInput } from "@fungi.computer/ear-tag/keys";
|
|
3
|
+
import { type ComponentType } from "react";
|
|
4
|
+
export type KeyAppOption = Readonly<{
|
|
5
|
+
installationId: string;
|
|
6
|
+
displayName: string;
|
|
6
7
|
}>;
|
|
7
|
-
export type
|
|
8
|
-
|
|
9
|
-
export declare const keyGrantTargetSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
10
|
-
kind: z.ZodLiteral<"all">;
|
|
11
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
12
|
-
kind: z.ZodLiteral<"selected">;
|
|
13
|
-
installationIds: z.ZodArray<z.ZodString>;
|
|
14
|
-
}, z.core.$strict>], "kind">;
|
|
15
|
-
export type KeyGrantTarget = z.output<typeof keyGrantTargetSchema>;
|
|
16
|
-
/** One App-scoped grant a key holds. */
|
|
17
|
-
export declare const keyGrantSchema: z.ZodObject<{
|
|
18
|
-
level: z.ZodEnum<{
|
|
19
|
-
read: "read";
|
|
20
|
-
write: "write";
|
|
21
|
-
}>;
|
|
22
|
-
target: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
23
|
-
kind: z.ZodLiteral<"all">;
|
|
24
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
25
|
-
kind: z.ZodLiteral<"selected">;
|
|
26
|
-
installationIds: z.ZodArray<z.ZodString>;
|
|
27
|
-
}, z.core.$strict>], "kind">;
|
|
28
|
-
}, z.core.$strict>;
|
|
29
|
-
export type KeyGrant = z.output<typeof keyGrantSchema>;
|
|
30
|
-
/** One key as the host lists it; its secret never comes back after issuing. */
|
|
31
|
-
export declare const keyRecordSchema: z.ZodObject<{
|
|
32
|
-
keyId: z.ZodString;
|
|
33
|
-
name: z.ZodString;
|
|
34
|
-
grants: z.ZodArray<z.ZodObject<{
|
|
35
|
-
level: z.ZodEnum<{
|
|
36
|
-
read: "read";
|
|
37
|
-
write: "write";
|
|
38
|
-
}>;
|
|
39
|
-
target: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
40
|
-
kind: z.ZodLiteral<"all">;
|
|
41
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
42
|
-
kind: z.ZodLiteral<"selected">;
|
|
43
|
-
installationIds: z.ZodArray<z.ZodString>;
|
|
44
|
-
}, z.core.$strict>], "kind">;
|
|
45
|
-
}, z.core.$strict>>;
|
|
46
|
-
computerAccess: z.ZodBoolean;
|
|
47
|
-
expiresAt: z.ZodNullable<z.ZodNumber>;
|
|
48
|
-
createdAt: z.ZodNumber;
|
|
49
|
-
lastUsedAt: z.ZodNullable<z.ZodNumber>;
|
|
50
|
-
revokedAt: z.ZodNullable<z.ZodNumber>;
|
|
51
|
-
}, z.core.$strict>;
|
|
52
|
-
export type KeyRecord = z.output<typeof keyRecordSchema>;
|
|
53
|
-
/** Every key this owner has issued. */
|
|
54
|
-
export declare const keysViewSchema: z.ZodObject<{
|
|
55
|
-
keys: z.ZodArray<z.ZodObject<{
|
|
56
|
-
keyId: z.ZodString;
|
|
57
|
-
name: z.ZodString;
|
|
58
|
-
grants: z.ZodArray<z.ZodObject<{
|
|
59
|
-
level: z.ZodEnum<{
|
|
60
|
-
read: "read";
|
|
61
|
-
write: "write";
|
|
62
|
-
}>;
|
|
63
|
-
target: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
64
|
-
kind: z.ZodLiteral<"all">;
|
|
65
|
-
}, z.core.$strict>, z.ZodObject<{
|
|
66
|
-
kind: z.ZodLiteral<"selected">;
|
|
67
|
-
installationIds: z.ZodArray<z.ZodString>;
|
|
68
|
-
}, z.core.$strict>], "kind">;
|
|
69
|
-
}, z.core.$strict>>;
|
|
70
|
-
computerAccess: z.ZodBoolean;
|
|
71
|
-
expiresAt: z.ZodNullable<z.ZodNumber>;
|
|
72
|
-
createdAt: z.ZodNumber;
|
|
73
|
-
lastUsedAt: z.ZodNullable<z.ZodNumber>;
|
|
74
|
-
revokedAt: z.ZodNullable<z.ZodNumber>;
|
|
75
|
-
}, z.core.$strict>>;
|
|
76
|
-
}, z.core.$strict>;
|
|
77
|
-
export type KeysView = z.output<typeof keysViewSchema>;
|
|
78
|
-
/** What the owner submits to issue one key. */
|
|
79
|
-
export type KeyIssueDraft = Readonly<{
|
|
8
|
+
export type KeyTargetOption = Readonly<{
|
|
9
|
+
id: string;
|
|
80
10
|
name: string;
|
|
81
|
-
grants: readonly KeyGrant[];
|
|
82
|
-
computerAccess: boolean;
|
|
83
|
-
expiresAt: number | null;
|
|
84
11
|
}>;
|
|
85
|
-
/**
|
|
86
|
-
export
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** The host's owner-direct custody for Keys. */
|
|
12
|
+
/** Presentation supplied by the host, not another capability catalog. */
|
|
13
|
+
export type KeyGrantOption = Readonly<{
|
|
14
|
+
capability: string;
|
|
15
|
+
label: string;
|
|
16
|
+
/** Omit for capabilities which have no selected-target form. */
|
|
17
|
+
targets?: readonly KeyTargetOption[];
|
|
18
|
+
}>;
|
|
19
|
+
export type KeyIssueDraft = Pick<IssueKeyInput<string>, "name" | "grants" | "expiresAt">;
|
|
20
|
+
export type KeysView = Readonly<{
|
|
21
|
+
keys: readonly KeySummary<string>[];
|
|
22
|
+
}>;
|
|
23
|
+
export type KeyIssueResult = Readonly<{
|
|
24
|
+
plaintext: string;
|
|
25
|
+
key: KeySummary<string>;
|
|
26
|
+
}>;
|
|
27
|
+
export type KeyChange = Readonly<{
|
|
28
|
+
key: KeySummary<string>;
|
|
29
|
+
notice: string;
|
|
30
|
+
/** Replay the same command, never generate another mutation identity. */
|
|
31
|
+
retry: boolean;
|
|
32
|
+
}>;
|
|
33
|
+
export type KeyGrantPreviewProps = Readonly<{
|
|
34
|
+
grants: readonly Grant[];
|
|
35
|
+
onReady: (ready: boolean) => void;
|
|
36
|
+
}>;
|
|
37
|
+
/** Hosts decode their current transport with their own Ear Tag vocabulary. */
|
|
112
38
|
export type KeysPort = Readonly<{
|
|
39
|
+
scope: string;
|
|
40
|
+
expiry: "required" | "optional";
|
|
41
|
+
options(apps: readonly KeyAppOption[], signal: AbortSignal): Promise<readonly KeyGrantOption[]>;
|
|
113
42
|
list(): Promise<KeysView>;
|
|
114
43
|
issue(draft: KeyIssueDraft): Promise<KeyIssueResult>;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
export type KeyAppOption = Readonly<{
|
|
119
|
-
installationId: string;
|
|
120
|
-
displayName: string;
|
|
44
|
+
updateGrants?(input: UpdateGrantsInput<string>): Promise<KeyChange>;
|
|
45
|
+
revoke(keyId: string, requestId: string): Promise<KeyChange>;
|
|
46
|
+
Preview?: ComponentType<KeyGrantPreviewProps>;
|
|
121
47
|
}>;
|
|
122
|
-
/**
|
|
123
|
-
|
|
124
|
-
* act with the owner's own access, limited to the Apps, level and expiry
|
|
125
|
-
* chosen here. A key never gets its own identity; it narrows the owner's.
|
|
126
|
-
*/
|
|
127
|
-
export declare function Keys({ port, apps, }: {
|
|
48
|
+
/** Scope changes remount the secret-once state as well as separating query caches. */
|
|
49
|
+
export declare function Keys(props: {
|
|
128
50
|
port: KeysPort;
|
|
129
51
|
apps: readonly KeyAppOption[];
|
|
130
52
|
}): import("react/jsx-runtime").JSX.Element;
|
|
131
|
-
export {};
|