Files
yaak-mountain-loop/apps/yaak-client/commands/openSettings.tsx
T
Gregory Schier 0a7a9f7a2b Run the desktop's model layer in the browser
The browser host now stores data through yaak-models compiled to wasm — the
same queries, migrations, cascade rules, duplicate naming and first-run
bootstrap the desktop and CLI use — instead of a TypeScript port of them.

- crates/yaak-web: the wasm crate. boot() registers an IndexedDB-backed VFS
  and calls init_standalone; rpc(cmd, payload, label) answers the models_*
  commands via ClientDb and returns the model_writes it caused; blob get/put
  through blob_manager. Built like yaak-templates (pkg/ committed); the
  build script keeps pkg/ and says so when no wasm-capable clang is present,
  so a desktop bootstrap never depends on one.
- models_ops moves from crates/yaak into yaak-models so the wasm crate can
  use it without the send engine.
- The database lives in a SharedWorker (packages/platform/src/web/worker.ts):
  one process holds the data and pushes writes to every tab, as on the
  desktop. Where SharedWorker is missing or its script cannot be fetched, a
  dedicated worker guarded by a Web Lock takes over and a second tab is told
  so. The worker imports the wasm lazily so a tab's connect is answered
  instantly; the tab reconnects if it isn't.
- packages/platform/src/web loses models.ts, schema.ts, db.ts and the
  BroadcastChannel; commands.ts forwards model commands to the worker and
  keeps the fixed answers and refusals.

Verified in Chrome, dev and production builds: bootstrap, CRUD across every
model type, serde defaults, engine-format ids, Rust copy naming, folder
cascade with per-descendant events, single-event workspace delete, reload
persistence, two tabs coherent, Send declined with a toast, 8/8 reloads
rendering in ~100 ms.
2026-08-15 15:48:49 -07:00

40 lines
1.5 KiB
TypeScript

import { platform } from "@yaakapp-internal/platform";
import type { SettingsTab } from "../components/Settings/Settings";
import { activeWorkspaceIdAtom } from "../hooks/useActiveWorkspace";
import { createFastMutation } from "../hooks/useFastMutation";
import { jotaiStore } from "../lib/jotai";
import { router } from "../lib/router";
import { rpc } from "../lib/rpc";
// Allow tab with optional subtab (e.g., "plugins:installed")
type SettingsTabWithSubtab = SettingsTab | `${SettingsTab}:${string}` | null;
export const openSettings = createFastMutation<void, string, SettingsTabWithSubtab>({
mutationKey: ["open_settings"],
mutationFn: async (tab) => {
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
if (workspaceId == null) return;
const to = "/workspaces/$workspaceId/settings" as const;
const params = { workspaceId };
const search = { tab: (tab ?? undefined) as SettingsTab | undefined };
// Settings is its own window where the host has windows to give. Where it
// doesn't — a browser tab — the same route opens in place, which is the
// whole difference: it is already a route, not a separate app.
if (!platform.capabilities.multiWindow) {
await router.navigate({ to, params, search });
return;
}
const location = router.buildLocation({ to, params, search });
await rpc("cmd_new_child_window", {
url: location.href,
label: "settings",
title: "Yaak Settings",
innerSize: [750, 600],
});
},
});