@fungi.computer/tourtois 0.0.1 → 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 +86 -11
- 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/SettingsHome.js +4 -4
- 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 +47 -47
- 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 +32 -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/launcher-gill.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 +102 -12
- 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 +32 -1
- package/dist/useAppWindowManager.js +3 -0
- package/package.json +40 -19
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";
|
|
@@ -13,11 +14,40 @@ import { useWindowMotion } from "./use-window-motion.js";
|
|
|
13
14
|
import { backHint } from "./shell-commands.js";
|
|
14
15
|
import { createAppWindowManager, } from "./app-windows.js";
|
|
15
16
|
import { launchComputerId } from "./app-launch.js";
|
|
17
|
+
/**
|
|
18
|
+
* The App's own path, commands and state, when it publishes them; an App
|
|
19
|
+
* that publishes none keeps the title and subtitle in its header.
|
|
20
|
+
*/
|
|
21
|
+
function headerContent(navigation, popover, activate) {
|
|
22
|
+
if (navigation?.crumbs === undefined || navigation.crumbs.length === 0)
|
|
23
|
+
return {};
|
|
24
|
+
let content = {
|
|
25
|
+
crumbs: navigation.crumbs,
|
|
26
|
+
popover,
|
|
27
|
+
onCrumb: (crumbId, siblingId) => {
|
|
28
|
+
activate(siblingId === undefined
|
|
29
|
+
? { kind: "crumb", crumbId }
|
|
30
|
+
: { kind: "crumb", crumbId, siblingId });
|
|
31
|
+
},
|
|
32
|
+
onAction: (actionId) => {
|
|
33
|
+
activate({ kind: "action", actionId });
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
if (navigation.primary !== undefined)
|
|
37
|
+
content = { ...content, primary: navigation.primary };
|
|
38
|
+
if (navigation.menu !== undefined)
|
|
39
|
+
content = { ...content, menu: navigation.menu };
|
|
40
|
+
if (navigation.status !== undefined)
|
|
41
|
+
content = { ...content, status: navigation.status };
|
|
42
|
+
return content;
|
|
43
|
+
}
|
|
16
44
|
const windowStatusLabel = (projection) => {
|
|
17
45
|
if (projection.status === "error")
|
|
18
|
-
return
|
|
46
|
+
return describeAppError(projection.error).message;
|
|
19
47
|
if (projection.status === "loading")
|
|
20
48
|
return `${projection.launch.title} opening…`;
|
|
49
|
+
if (projection.status === "getting-ready")
|
|
50
|
+
return "Getting ready…";
|
|
21
51
|
return projection.launch.title;
|
|
22
52
|
};
|
|
23
53
|
/** The focused window's App-level settings; its Computer lives in its status bar. */
|
|
@@ -26,8 +56,9 @@ export function FocusedWindowControls({ app, onOpenSettings, onOpenPermissions,
|
|
|
26
56
|
return null;
|
|
27
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 })] }));
|
|
28
58
|
}
|
|
29
|
-
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, }) {
|
|
30
60
|
const containerRef = useRef(null);
|
|
61
|
+
const popover = useLayer("popover");
|
|
31
62
|
const [computerPickerOpen, setComputerPickerOpen] = useState(false);
|
|
32
63
|
useEffect(() => {
|
|
33
64
|
if (projection.launch.computers === undefined)
|
|
@@ -101,7 +132,14 @@ export function AppPane({ projection, icon, manager, onError, index, focusWindow
|
|
|
101
132
|
onBack(projection.windowId);
|
|
102
133
|
}, style: binding.stackingStyle, children: [_jsx(WindowHeader, { title: projection.launch.title, ...(projection.navigation?.title === undefined
|
|
103
134
|
? {}
|
|
104
|
-
: { subtitle: projection.navigation.title }),
|
|
135
|
+
: { subtitle: projection.navigation.title }), ...headerContent(projection.navigation, popover, (activation) => {
|
|
136
|
+
void manager
|
|
137
|
+
.activateNavigation(projection.windowId, activation)
|
|
138
|
+
.catch((cause) => {
|
|
139
|
+
console.error("App workbench operation failed", cause);
|
|
140
|
+
onError("App could not handle that action.");
|
|
141
|
+
});
|
|
142
|
+
}), density: layout.mode === "tiled" ? "compact" : "regular", icon: _jsx(AppIcon, { roles: projection.launch.roles, icon: icon }), ...(projection.navigation?.canGoBack === true
|
|
105
143
|
? {
|
|
106
144
|
back: {
|
|
107
145
|
label: `Back in ${projection.launch.title}`,
|
|
@@ -135,9 +173,21 @@ export function AppPane({ projection, icon, manager, onError, index, focusWindow
|
|
|
135
173
|
console.error("App workbench operation failed", cause);
|
|
136
174
|
onError("App could not be minimized.");
|
|
137
175
|
});
|
|
138
|
-
}, 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" &&
|
|
139
180
|
!(projection.error instanceof AppError &&
|
|
140
|
-
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) => {
|
|
141
191
|
onSelectComputer(projection.windowId, computerId);
|
|
142
192
|
}, surface: () => containerRef.current, onAction: (action) => {
|
|
143
193
|
void manager
|
|
@@ -152,13 +202,30 @@ export function AppPane({ projection, icon, manager, onError, index, focusWindow
|
|
|
152
202
|
* One window's status bar, owned by the shell like its title bar: the
|
|
153
203
|
* window's Computer first (only the person changes it, here), then the items
|
|
154
204
|
* the App declared as data. The shell renders every item with Caps pieces and
|
|
155
|
-
* 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.
|
|
156
210
|
*/
|
|
157
|
-
export function WindowStatusBar({ projection, onSelectComputer, onAction, surface, computerPickerOpen, onComputerPickerOpenChange, }) {
|
|
211
|
+
export function WindowStatusBar({ projection, onSelectComputer, onAction, surface, computerPickerOpen, onComputerPickerOpenChange, onReload, }) {
|
|
158
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
|
+
}, []);
|
|
159
226
|
const { computers, title } = projection.launch;
|
|
160
227
|
const items = projection.statusItems;
|
|
161
|
-
if (computers === undefined && items.length === 0)
|
|
228
|
+
if (computers === undefined && items.length === 0 && !onReload)
|
|
162
229
|
return null;
|
|
163
230
|
const anchorFor = (key) => {
|
|
164
231
|
const origin = surface?.()?.getBoundingClientRect();
|
|
@@ -172,7 +239,12 @@ export function WindowStatusBar({ projection, onSelectComputer, onAction, surfac
|
|
|
172
239
|
};
|
|
173
240
|
const render = (item) => (_jsx(WindowStatusItem, { item: item, container: popover, onAction: onAction, anchorFor: anchorFor }, item.id));
|
|
174
241
|
const end = items.filter((item) => item.end === true);
|
|
175
|
-
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);
|
|
176
248
|
return (_jsx(AppStatusBar, { className: "tourtois-window-status-bar", "aria-label": `${title} status`, attachment: computers === undefined ? undefined : (_jsx(StatusBarPicker, { ...(computerPickerOpen === undefined
|
|
177
249
|
? {}
|
|
178
250
|
: { open: computerPickerOpen }), ...(onComputerPickerOpenChange === undefined
|
|
@@ -180,5 +252,8 @@ export function WindowStatusBar({ projection, onSelectComputer, onAction, surfac
|
|
|
180
252
|
: { onOpenChange: onComputerPickerOpenChange }), noun: "Computer", pluralNoun: "Computers", accessibleLabel: `Choose Computer for ${title}`, icon: _jsx(Sigil, { name: "computer", size: 16 }), options: computers.map((computer) => ({
|
|
181
253
|
id: computer.computerId,
|
|
182
254
|
label: computer.name,
|
|
183
|
-
})), 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) }));
|
|
184
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 {};
|