@fungi.computer/tourtois 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -83,8 +83,11 @@ methods. Tourtois's code loads as one lazy chunk behind that module, so no host
83
83
  bundles it, and a fork mounts the same way.
84
84
 
85
85
  Tourtois asks the host to mount each App in that App's own sandboxed frame,
86
- under its own installation, Release and permissions. Settings pages are supplied
87
- by the host too: Tourtois offers a page only when the host backs it.
86
+ under its own installation, Release and permissions. Tourtois imports no host
87
+ implementation package. Standalone App Permissions frame Oyster's own grants
88
+ page, also available without a shell; the changed Oyster and Tourtois ship
89
+ together. Other Settings sections still use their existing host-backed ports:
90
+ this repair does not frame Keys, MCP servers, Computers or the consent dialog.
88
91
 
89
92
  ## Agent tools
90
93
 
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { desktopSettingsHandler } from "./desktop/consent.js";
3
3
  import { AppError } from "@fungi.computer/agar";
4
4
  import {} from "@fungi.computer/caps/blocks/app-bar";
5
+ import { ResizeHandle } from "@fungi.computer/caps/components/resize-handle";
5
6
  import { toast, Toaster } from "@fungi.computer/caps/components/toaster";
6
7
  import { WindowSnapPreview } from "@fungi.computer/caps/components/window";
7
8
  import { MaosaicoRegion, useLayer, useMaosaicoLayout, } from "@fungi.computer/maosaico";
@@ -560,19 +561,7 @@ export function AppWorkbench({ paneLayerRef, phone, teamId, teamName, initialCom
560
561
  width: layout.snapPreview.frame.width,
561
562
  height: layout.snapPreview.frame.height,
562
563
  ...layout.stackingStyle,
563
- } })), layout.divider ? (_jsx("div", { className: "tourtois-divider", role: "separator", "aria-label": "Main window width", "aria-orientation": "vertical", "aria-valuemin": Math.round(layout.divider.min), "aria-valuemax": Math.round(layout.divider.max), "aria-valuenow": Math.round(layout.divider.position), tabIndex: 0, style: { left: layout.divider.position, ...layout.stackingStyle }, ...layout.divider.props, onKeyDown: (event) => {
564
- if (!layout.divider)
565
- return;
566
- const step = new Map([
567
- ["ArrowLeft", -32],
568
- ["ArrowRight", 32],
569
- ]).get(event.key) ?? 0;
570
- if (step) {
571
- event.preventDefault();
572
- event.stopPropagation();
573
- layout.resizeMain(layout.divider.position + step);
574
- }
575
- } })) : null] })] }), _jsx(ShellCommandDialogs, { host: appSurfaceHost, state: shellCommands, focusedWindow: activeWindow, directoryApps: directory.map((app) => ({
564
+ } })), layout.divider ? (_jsx(ResizeHandle, { ...layout.divider.props, className: "pointer-events-auto absolute top-1/2 -translate-x-1/2 -translate-y-1/2", "aria-label": "Main window width", orientation: "vertical", min: layout.divider.min, max: layout.divider.max, value: layout.divider.position, onValueChange: layout.resizeMain, style: { left: layout.divider.position, ...layout.stackingStyle } })) : null] })] }), _jsx(ShellCommandDialogs, { host: appSurfaceHost, state: shellCommands, focusedWindow: activeWindow, directoryApps: directory.map((app) => ({
576
565
  commands: app.commands,
577
566
  name: app.displayName ?? "App",
578
567
  roles: app.roles,
@@ -15,6 +15,7 @@ import { ActionRow } from "@fungi.computer/caps/components/action-row";
15
15
  import { DataList, DataRow } from "@fungi.computer/caps/components/data-list";
16
16
  import { SettingsLeaveContext, } from "./settings-leave-guard.js";
17
17
  import { Button } from "@fungi.computer/caps/components/button";
18
+ import { Tabs, TabsContent, TabsList, TabsTrigger, } from "@fungi.computer/caps/components/tabs";
18
19
  import { WindowFrame, WindowHeader, WindowActions, } from "@fungi.computer/caps/components/window";
19
20
  import { Dialog, DialogContent, DialogTitle, DialogDescription, } from "@fungi.computer/caps/components/dialog";
20
21
  /** One system-settings surface. The menu only requests opening it. */
@@ -142,31 +143,14 @@ export function SystemSettings({ location: requestedLocation, sections: ceiling,
142
143
  });
143
144
  } }) }, addressKey(app.address)))) }), appCount === 0 ? (_jsx("p", { children: desktopOnly
144
145
  ? "No Apps installed yet."
145
- : "No Apps in this Team yet." })) : null] })) : null, appsLoaded ? (_jsxs("section", { className: "tourtois-settings-content", "aria-label": "App settings", hidden: !appSection || appKey === null, children: [_jsxs("div", { className: "tourtois-settings-app-header", children: [_jsxs(SectionHeading, { className: "tourtois-settings-app-heading", children: [selected ? (_jsx(AppIcon, { roles: selected.roles, icon: selected.icon })) : null, selected
146
- ? (selected.displayName ?? selected.appId)
147
- : "Apps"] }), selected ? (_jsxs("div", { className: "tourtois-settings-app-tabs", role: "group", "aria-label": "App settings sections", children: [!desktopOnly ? (_jsx(Button, { type: "button", size: "sm", variant: "ghost", className: !advanced && !desktop
148
- ? "bg-base-300 text-base-content"
149
- : undefined, "aria-pressed": !advanced && !desktop, onClick: () => {
150
- onNavigate({
151
- category: "apps",
152
- appKey,
153
- section: "permissions",
154
- });
155
- }, children: "Permissions" })) : null, _jsx(Button, { type: "button", size: "sm", variant: "ghost", className: desktop ? "bg-base-300 text-base-content" : undefined, "aria-pressed": desktop, onClick: () => {
156
- onNavigate({
157
- category: "apps",
158
- appKey,
159
- section: "desktop",
160
- });
161
- }, children: "Desktop" }), AppAdvanced !== undefined ? (_jsx(Button, { type: "button", size: "sm", variant: "ghost", className: advanced
162
- ? "bg-base-300 text-base-content"
163
- : undefined, "aria-pressed": advanced, onClick: () => {
164
- onNavigate({
165
- category: "apps",
166
- appKey,
167
- section: "advanced",
168
- });
169
- }, children: "Advanced" })) : null] })) : null] }), _jsxs("div", { className: "tourtois-settings-app-content", children: [!selected ? (_jsx("p", { children: "Choose an app to manage its permissions and advanced settings." })) : null, _jsx("div", { className: "tourtois-settings-permissions", hidden: !selected || advanced || desktop, children: AppPermissions !== undefined ? (_jsx(AppPermissions, { selectedKey: appKey, onAccepted: onAccepted })) : null }), selected && desktop && desktopOnly ? (_jsx(AppCallPermissions, { calls: selected.calls, apps: apps })) : null, selected && desktop ? (_jsx(AppDesktopSettings, { app: selected, desktop: desktopSettings, consentReadyAt: desktopConsentReadyAt }, selected.surfaceId)) : null, AppAdvanced !== undefined ? (_jsx(AppAdvanced, { selectedKey: appKey, visible: advanced, onNavigate: onNavigate, onAccepted: onAccepted })) : null] })] })) : null] }) }), leaveError ? (_jsx("div", { className: "tourtois-settings-discard", role: "alert", children: leaveError })) : null, discard ? (_jsxs("div", { className: "tourtois-settings-discard", role: "alert", children: [_jsx("p", { children: "Discard unsaved changes?" }), _jsxs("div", { className: "tourtois-settings-actions", children: [_jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => {
146
+ : "No Apps in this Team yet." })) : null] })) : null, appsLoaded ? (_jsx("section", { className: "tourtois-settings-content", "aria-label": "App settings", hidden: !appSection || appKey === null, children: _jsxs(Tabs, { className: "flex min-h-0 flex-1 flex-col", value: advanced ? "advanced" : desktop ? "desktop" : "permissions", onValueChange: (section) => {
147
+ if (section === "permissions" ||
148
+ section === "desktop" ||
149
+ section === "advanced")
150
+ onNavigate({ category: "apps", appKey, section });
151
+ }, children: [_jsxs("div", { className: "tourtois-settings-app-header", children: [_jsxs(SectionHeading, { className: "tourtois-settings-app-heading", children: [selected ? (_jsx(AppIcon, { roles: selected.roles, icon: selected.icon })) : null, selected
152
+ ? (selected.displayName ?? selected.appId)
153
+ : "Apps"] }), _jsxs(TabsList, { variant: "segmented", className: "ml-auto w-auto", hidden: !selected || (desktopOnly && AppAdvanced === undefined), "aria-label": "App settings sections", children: [!desktopOnly ? (_jsx(TabsTrigger, { value: "permissions", children: "Permissions" })) : null, _jsx(TabsTrigger, { value: "desktop", children: "Desktop" }), AppAdvanced !== undefined ? (_jsx(TabsTrigger, { value: "advanced", children: "Advanced" })) : null] })] }), _jsxs("div", { className: "tourtois-settings-app-content", children: [!selected ? (_jsx("p", { children: "Choose an app to manage its permissions and advanced settings." })) : null, AppPermissions !== undefined ? (_jsx(TabsContent, { value: "permissions", forceMount: true, className: "mt-0 flex min-h-0 flex-1 flex-col", children: _jsx("div", { className: "tourtois-settings-permissions", hidden: !selected, children: _jsx(AppPermissions, { selectedKey: appKey, onAccepted: onAccepted }) }) })) : null, _jsxs(TabsContent, { value: "desktop", forceMount: true, className: "mt-0 flex min-h-0 flex-1 flex-col", children: [selected && desktop && desktopOnly ? (_jsx(AppCallPermissions, { calls: selected.calls, apps: apps })) : null, selected && desktop ? (_jsx(AppDesktopSettings, { app: selected, desktop: desktopSettings, consentReadyAt: desktopConsentReadyAt }, selected.surfaceId)) : null] }), AppAdvanced !== undefined ? (_jsx(TabsContent, { value: "advanced", forceMount: true, className: "mt-0 flex min-h-0 flex-1 flex-col", children: _jsx(AppAdvanced, { selectedKey: appKey, visible: advanced, onNavigate: onNavigate, onAccepted: onAccepted }) })) : null] })] }) })) : null] }) }), leaveError ? (_jsx("div", { className: "tourtois-settings-discard", role: "alert", children: leaveError })) : null, discard ? (_jsxs("div", { className: "tourtois-settings-discard", role: "alert", children: [_jsx("p", { children: "Discard unsaved changes?" }), _jsxs("div", { className: "tourtois-settings-actions", children: [_jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => {
170
154
  setDiscard(false);
171
155
  }, children: "Keep editing" }), _jsx(Button, { type: "button", size: "sm", onClick: () => {
172
156
  if (!canClose())
@@ -52,7 +52,6 @@ export declare const appPermissionChangesSchema: z.ZodObject<{
52
52
  marketplace: z.ZodOptional<z.ZodObject<{
53
53
  browse: z.ZodBoolean;
54
54
  purchase: z.ZodBoolean;
55
- installFree: z.ZodBoolean;
56
55
  }, z.core.$strict>>;
57
56
  }, z.core.$strict>;
58
57
  export type AppPermissionChanges = z.output<typeof appPermissionChangesSchema>;
@@ -109,7 +108,6 @@ export declare const appPermissionReceiptSchema: z.ZodObject<{
109
108
  marketplacePermissions: z.ZodObject<{
110
109
  browse: z.ZodBoolean;
111
110
  purchase: z.ZodBoolean;
112
- installFree: z.ZodBoolean;
113
111
  }, z.core.$strict>;
114
112
  }, z.core.$strip>;
115
113
  export declare function appPermissionsMutationRequest(teamId: string, requestId: string, address: AppAddress, expectedAuthorityVersion: number, permissions: AppPermissionChanges): {
@@ -201,9 +201,9 @@ function ArrangedDesktop({ owner, ...props }) {
201
201
  onSelect: () => undefined,
202
202
  })),
203
203
  ];
204
- return (_jsxs(_Fragment, { children: [snapshot.refreshFailure ? (_jsx("p", { role: "alert", children: snapshot.refreshFailure.message })) : null, editing ? (_jsxs("div", { className: "tourtois-desktop-controls", children: [_jsx(Button, { size: "sm", variant: "ghost", onClick: () => {
204
+ return (_jsxs(_Fragment, { children: [snapshot.refreshFailure ? (_jsx("p", { role: "alert", children: snapshot.refreshFailure.message })) : null, editing ? (_jsxs("div", { className: "tourtois-desktop-controls", children: [_jsx(Button, { size: "sm", variant: "outline", onClick: () => {
205
205
  onEditingChange(false);
206
- }, children: "Done arranging" }), _jsx("div", { className: "tourtois-desktop-add-gill", children: _jsxs(Select, { "aria-label": "Add widget", value: "", disabled: saving, onChange: (event) => {
206
+ }, children: "Done arranging" }), _jsx("div", { className: "tourtois-desktop-add-gill", children: _jsxs(Select, { "aria-label": "Add widget", className: "select-sm", value: "", disabled: saving, onChange: (event) => {
207
207
  const gill = catalog[Number(event.currentTarget.value)];
208
208
  const size = gill?.sizes[0];
209
209
  if (gill && size && (gill.params?.length ?? 0) > 0)
@@ -214,9 +214,9 @@ function ArrangedDesktop({ owner, ...props }) {
214
214
  gillId: gill.gillId,
215
215
  size,
216
216
  });
217
- }, children: [_jsx("option", { value: "", children: "Add widget\u2026" }), catalog.map((gill, index) => (_jsx("option", { value: index, children: gill.title }, `${gill.surfaceId}:${gill.gillId}`)))] }) }), _jsx(Button, { size: "sm", variant: "ghost", disabled: saving, onClick: () => {
217
+ }, children: [_jsx("option", { value: "", children: "Add widget\u2026" }), catalog.map((gill, index) => (_jsx("option", { value: index, children: gill.title }, `${gill.surfaceId}:${gill.gillId}`)))] }) }), _jsx(Button, { size: "sm", variant: "outline", disabled: saving, onClick: () => {
218
218
  void save("layout.reset", {});
219
- }, children: "Reset to Team default" }), snapshot.canWriteTeam ? (_jsx(Button, { size: "sm", variant: "ghost", disabled: saving, onClick: () => {
219
+ }, children: "Reset to Team default" }), snapshot.canWriteTeam ? (_jsx(Button, { size: "sm", variant: "outline", disabled: saving, onClick: () => {
220
220
  void save("layout.set", { layout: snapshot.layout }, "team");
221
221
  }, children: "Save as Team default" })) : null] })) : null, configuration ? (_jsx(GillParamPicker, { gill: configuration.gill, initialParams: snapshot.layout.items.find((item) => item.id === configuration.itemId)?.params, computers: props.computersForGill?.(configuration.gill.surfaceId) ?? [], transport: props.transport, editing: configuration.itemId !== undefined, onClose: () => {
222
222
  setConfiguration(null);
@@ -27,9 +27,9 @@ export function LauncherGill({ items, size = "M", onAllApps, loading = false, un
27
27
  onSelect: item.onSelect,
28
28
  }));
29
29
  const ready = !loading && !unavailable;
30
- return (_jsxs(GillFrame, { title: "Launcher", size: size, onContentHeight: onContentHeight, editHandle: editHandle ?? (_jsx(_Fragment, { children: _jsx(Button, { size: "sm", variant: "ghost", "aria-expanded": editing, onClick: () => {
31
- setEditing(!editing);
32
- }, children: editing ? "Done" : "Edit launcher" }) })), children: [loading ? _jsx("p", { role: "status", children: "Loading Apps\u2026" }) : null, unavailable ? (_jsxs("p", { role: "alert", children: ["App directory unavailable.", " ", onRetry ? (_jsx(Button, { size: "sm", variant: "ghost", onClick: onRetry, children: "Retry" })) : null] })) : null, ready ? (_jsxs(_Fragment, { children: [items.length === 0 ? (_jsx(EmptyState, { variant: "hint", title: "No Apps available", description: "Installed launchable Apps appear here." })) : null, items.length > 0 && shown.length === 0 ? (_jsx("p", { children: "No Apps shown. Edit the launcher to show them again." })) : null, _jsx(AppGrid, { "aria-label": "Team Apps", items: [
30
+ return (_jsxs(GillFrame, { title: "Launcher", size: size, onContentHeight: onContentHeight, editHandle: editHandle ?? (_jsx(Button, { size: "xs", variant: "outline", "aria-expanded": editing, onClick: () => {
31
+ setEditing(!editing);
32
+ }, children: editing ? "Done" : "Edit launcher" })), children: [loading ? _jsx("p", { role: "status", children: "Loading Apps\u2026" }) : null, unavailable ? (_jsxs("p", { role: "alert", children: ["App directory unavailable.", " ", onRetry ? (_jsx(Button, { size: "sm", variant: "ghost", onClick: onRetry, children: "Retry" })) : null] })) : null, ready ? (_jsxs(_Fragment, { children: [items.length === 0 ? (_jsx(EmptyState, { variant: "hint", title: "No Apps available", description: "Installed launchable Apps appear here." })) : null, items.length > 0 && shown.length === 0 ? (_jsx("p", { children: "No Apps shown. Edit the launcher to show them again." })) : null, _jsx(AppGrid, { "aria-label": "Team Apps", items: [
33
33
  ...tiles,
34
34
  ...warnings,
35
35
  {
@@ -1,8 +1,8 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- // Tourtois's Settings on a standalone Oyster, which has no Settings of its
3
- // own: Apps, Connectors and Keys over the Oyster owner routes. The Oyster page
4
- // mounts this from the picked shell Release's module; a host with its own
5
- // Settings (the Hub) never calls it.
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Standalone Oyster Settings chrome, loaded from the chosen shell Release.
3
+ // App Permissions frame Oyster's own grants page, never its implementation.
4
+ // Connectors, Keys and Computers retain their existing host-backed ports.
5
+ // A host with its own Settings (the Hub) never calls this entrypoint.
6
6
  import "@fungi.computer/parakeet/styles.css";
7
7
  import "./shell.css";
8
8
  import { AppError } from "@fungi.computer/agar";
@@ -11,10 +11,12 @@ import { workbenchDirectorySchema } from "@fungi.computer/nacre/workbench-direct
11
11
  import { QueryClientProvider } from "@tanstack/react-query";
12
12
  import * as S from "effect/Schema";
13
13
  import { z } from "zod";
14
+ import { useEffect, useRef, useState } from "react";
14
15
  import { createRoot } from "react-dom/client";
15
16
  import { agentMcpServersViewSchema, createPlatformControls, PlatformControlsView, } from "./platform-controls.js";
16
17
  import { settingsLocationSchema } from "./settings-routes.js";
17
18
  import { OysterComputers, createOysterComputersPort, } from "./OysterComputers.js";
19
+ import { useSettingsLeaveGuard, } from "./settings-leave-guard.js";
18
20
  async function json(path) {
19
21
  const response = await fetch(path, {
20
22
  credentials: "same-origin",
@@ -111,6 +113,65 @@ function createOysterKeysPort(oysterKeysWire) {
111
113
  };
112
114
  }
113
115
  const directory = async () => workbenchDirectorySchema.parse(await json("/api/workbench/directory"));
116
+ const permissionFrameMessage = z.strictObject({
117
+ type: z.literal("oyster.permissions"),
118
+ event: z.enum(["accepted", "state"]),
119
+ state: z.enum(["clean", "dirty", "pending", "uncertain"]),
120
+ });
121
+ function OysterPermissionsFrame({ installationId, onAccepted, }) {
122
+ const frame = useRef(null);
123
+ const sizeObserver = useRef(null);
124
+ const [height, setHeight] = useState(0);
125
+ const leave = useRef("clean");
126
+ const [refreshFailed, setRefreshFailed] = useState(false);
127
+ useSettingsLeaveGuard(() => leave.current);
128
+ useEffect(() => () => {
129
+ sizeObserver.current?.disconnect();
130
+ }, []);
131
+ useEffect(() => {
132
+ const receive = (event) => {
133
+ const source = frame.current?.contentWindow;
134
+ if (!source ||
135
+ event.source !== source ||
136
+ event.origin !== window.location.origin)
137
+ return;
138
+ const parsed = permissionFrameMessage.safeParse(event.data);
139
+ if (!parsed.success)
140
+ return;
141
+ leave.current = parsed.data.state;
142
+ if (parsed.data.event === "accepted") {
143
+ setRefreshFailed(false);
144
+ void onAccepted().catch((cause) => {
145
+ console.error("App permissions Settings refresh failed", cause);
146
+ setRefreshFailed(true);
147
+ });
148
+ }
149
+ };
150
+ window.addEventListener("message", receive);
151
+ return () => {
152
+ window.removeEventListener("message", receive);
153
+ };
154
+ }, [onAccepted]);
155
+ return (_jsxs(_Fragment, { children: [refreshFailed ? (_jsx("p", { role: "alert", children: "Permissions saved. Settings could not refresh. Close and reopen Settings to retry." })) : null, _jsx("iframe", { ref: frame, title: "App permissions", src: `/api/installations/${encodeURIComponent(installationId)}/grants`, className: "w-full shrink-0 border-0", style: { height }, onLoad: () => {
156
+ sizeObserver.current?.disconnect();
157
+ const body = frame.current?.contentDocument?.body;
158
+ if (!body)
159
+ return;
160
+ // The same-origin host page owns its markup. Observe its body rather
161
+ // than its editor internals; Settings remains the only scroll owner.
162
+ const resize = () => {
163
+ setHeight(Math.ceil(body.getBoundingClientRect().height));
164
+ };
165
+ sizeObserver.current = new ResizeObserver(resize);
166
+ sizeObserver.current.observe(body);
167
+ resize();
168
+ } })] }));
169
+ }
170
+ function OysterAppPermissions({ selectedKey, onAccepted, }) {
171
+ if (!selectedKey?.startsWith("installation:"))
172
+ return null;
173
+ return (_jsx(OysterPermissionsFrame, { installationId: selectedKey.slice("installation:".length), onAccepted: onAccepted }, selectedKey));
174
+ }
114
175
  export const mountPlatformControls = (root, input) => {
115
176
  const keys = createOysterKeysPort(input.keysWire);
116
177
  const requestAppAccess = (target, request, dialog) => input.requestAppAccess(target, request, {
@@ -118,7 +179,7 @@ export const mountPlatformControls = (root, input) => {
118
179
  customize: (installationId) => dialog.customize({
119
180
  category: "apps",
120
181
  appKey: installationId === null ? null : `installation:${installationId}`,
121
- section: "desktop",
182
+ section: "permissions",
122
183
  }),
123
184
  });
124
185
  const controls = createPlatformControls({
@@ -128,7 +189,14 @@ export const mountPlatformControls = (root, input) => {
128
189
  const computers = () => (_jsx(OysterComputers, { teamId: input.teamId, port: computersPort, onChanged: controls.changed }));
129
190
  const client = createQueryClient();
130
191
  const settings = createRoot(root);
131
- settings.render(_jsx(QueryClientProvider, { client: client, children: _jsx(PlatformControlsView, { teamId: input.teamId, controls: controls, data: { directory, agentMcpServers, computers, keys, requestAppAccess } }) }));
192
+ settings.render(_jsx(QueryClientProvider, { client: client, children: _jsx(PlatformControlsView, { teamId: input.teamId, controls: controls, data: {
193
+ directory,
194
+ agentMcpServers,
195
+ computers,
196
+ keys,
197
+ requestAppAccess,
198
+ appPermissions: OysterAppPermissions,
199
+ } }) }));
132
200
  return Promise.resolve({
133
201
  controls: {
134
202
  ...controls,
@@ -6,7 +6,8 @@ import type { MountPlatformControls, MountShell } from "@fungi.computer/nacre/sh
6
6
  */
7
7
  export declare const mountShell: MountShell;
8
8
  /**
9
- * Tourtois's own Settings for a host that has none, such as a standalone
10
- * Oyster; they load as their own lazy chunk under this module's URL.
9
+ * Standalone Oyster Settings chrome, loaded as its own lazy chunk.
10
+ * App Permissions frame the host's page instead of importing host code;
11
+ * other sections retain their existing host-backed ports.
11
12
  */
12
13
  export declare const mountPlatformControls: MountPlatformControls;
@@ -8,8 +8,9 @@ export const mountShell = async (root, input) => {
8
8
  return shell.mountShell(root, input);
9
9
  };
10
10
  /**
11
- * Tourtois's own Settings for a host that has none, such as a standalone
12
- * Oyster; they load as their own lazy chunk under this module's URL.
11
+ * Standalone Oyster Settings chrome, loaded as its own lazy chunk.
12
+ * App Permissions frame the host's page instead of importing host code;
13
+ * other sections retain their existing host-backed ports.
13
14
  */
14
15
  export const mountPlatformControls = async (root, input) => {
15
16
  const settings = await import("./oyster-platform-controls.js");
package/dist/shell.css CHANGED
@@ -249,13 +249,6 @@
249
249
  gap: var(--spacing-sm);
250
250
  }
251
251
 
252
- .tourtois-settings-app-tabs {
253
- display: flex;
254
- flex-wrap: wrap;
255
- gap: var(--spacing-xs);
256
- margin-left: auto;
257
- }
258
-
259
252
  .tourtois-settings-app-header {
260
253
  display: flex;
261
254
  align-items: center;
@@ -532,15 +525,6 @@
532
525
  color: var(--stipe-fg-muted);
533
526
  font-size: var(--text-sm);
534
527
  }
535
- .tourtois-divider {
536
- position: absolute;
537
- top: 0;
538
- bottom: 0;
539
- width: 8px;
540
- pointer-events: auto;
541
- cursor: col-resize;
542
- touch-action: none;
543
- }
544
528
 
545
529
  .tourtois-team-git {
546
530
  display: flex;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fungi.computer/tourtois",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "author": "Levi <levi@technomancy.dev>",
5
5
  "description": "Tourtois: the trusted desktop shell for Oyster and Fungi, with host-supplied Settings sections",
6
6
  "type": "module",
@@ -73,18 +73,18 @@
73
73
  "LICENSE"
74
74
  ],
75
75
  "dependencies": {
76
- "@fungi.computer/agar": "0.5.0",
76
+ "@fungi.computer/agar": "0.6.1",
77
77
  "@fungi.computer/cairn": "0.1.0",
78
- "@fungi.computer/caps": "0.4.0",
78
+ "@fungi.computer/caps": "0.6.0",
79
79
  "@fungi.computer/ear-tag": "0.1.0",
80
80
  "@fungi.computer/gills": "0.2.0",
81
81
  "@fungi.computer/maosaico": "0.1.0",
82
- "@fungi.computer/parakeet": "0.5.0",
83
- "@fungi.computer/puffball": "0.2.0",
84
- "@fungi.computer/shiitake": "0.6.0",
85
- "@fungi.computer/sigils": "0.2.0",
82
+ "@fungi.computer/parakeet": "0.6.1",
83
+ "@fungi.computer/puffball": "0.4.0",
84
+ "@fungi.computer/shiitake": "0.8.0",
85
+ "@fungi.computer/sigils": "0.2.1",
86
86
  "@fungi.computer/sprite": "0.2.0",
87
- "@fungi.computer/sprite-zdog": "0.2.0",
87
+ "@fungi.computer/sprite-zdog": "0.3.0",
88
88
  "@fungi.computer/whistle": "0.3.0",
89
89
  "@tanstack/history": "1.162.1",
90
90
  "@tanstack/react-form": "1.33.2",
@@ -96,16 +96,16 @@
96
96
  "lucide-react": "0.460.0",
97
97
  "react-dom": "18.3.1",
98
98
  "zod": "4.4.3",
99
- "@fungi.computer/nacre": "0.1.0"
99
+ "@fungi.computer/nacre": "0.1.2"
100
100
  },
101
101
  "peerDependencies": {
102
102
  "react": "^18.3.1 || ^19.2.0"
103
103
  },
104
104
  "devDependencies": {
105
- "@fungi.computer/oyster": "0.6.0",
105
+ "@fungi.computer/oyster": "0.8.0",
106
106
  "@cloudflare/workers-types": "5.20260828.1",
107
- "@fungi.computer/forage": "0.2.0",
108
- "@fungi.computer/mycelium": "0.2.3",
107
+ "@fungi.computer/forage": "0.2.2",
108
+ "@fungi.computer/mycelium": "0.3.1",
109
109
  "@ladle/react": "5.1.1",
110
110
  "@types/react": "18.3.3",
111
111
  "@vitejs/plugin-react": "6.1.1",