diff --git a/docs/book/src/guide/getting-started.md b/docs/book/src/guide/getting-started.md index da463a79..92d62bc8 100644 --- a/docs/book/src/guide/getting-started.md +++ b/docs/book/src/guide/getting-started.md @@ -74,7 +74,9 @@ doesn't update automatically. The same app runs in the browser: [open SolidCraft on the web](../app/) (`?sample` opens the sample part, `?webgl` forces WebGL2 where WebGPU is missing). Designs are saved in the browser's private -file storage, which needs HTTPS or localhost. +file storage, which needs HTTPS or localhost. Planned worker and tiered-storage integration is +described in [web-runtime.md](https://github.com/bherbruck/solidcraft/blob/main/docs/web-runtime.md) +(design; not yet wired in the shipped wasm build). After the first visit the web app works offline: the browser keeps a copy of it. Online, each launch gets the latest published build (the browser downloads it once when it changes); offline, diff --git a/docs/mcp.md b/docs/mcp.md index ee9d5ab3..e74c67ef 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -10,6 +10,11 @@ ordinary `execute` tool: `render.preflight`, `render.start`, `render.status`, `render.cancel` and `render.export`. Keep the complete returned job ID; image bytes remain in the session until explicit PNG export. See [render jobs](render-jobs.md). +For how the same command surface should work in the browser (workers, tiered storage, in-tab +agents), see [web-runtime.md](web-runtime.md). For sandboxed plugins and parametric timeline +features that also use `run(command, args)`, see [plugins.md](plugins.md). Desktop MCP in this +document remains the reference implementation today. + ## Start it ```sh diff --git a/docs/plugins.md b/docs/plugins.md new file mode 100644 index 00000000..31bfd809 --- /dev/null +++ b/docs/plugins.md @@ -0,0 +1,146 @@ +# Plugins (design) + +Design companion to [#1221](https://github.com/bherbruck/solidcraft/issues/1221): sandboxed +WebAssembly plugins with a WIT interface, the same `run(command, args)` JSON as +[MCP](mcp.md) and the [control protocol](control-protocol.md). Browser hosting is in +[web-runtime.md](web-runtime.md). + +This document adds **parametric timeline features**: a generic host-owned feature shell that +plugins fill with parameters, display metadata, and rebuild logic, without a second timeline +model or a private geometry API. + +## Goals (unchanged from #1221) + +- Sandboxed by default: manifest permissions, fuel/memory limits on desktop, broker enforcement on web. +- Any language that compiles to Wasm components; Rust first. +- **One command surface:** plugins change the model only through engine commands (undoable, journaled). +- Versioned WIT world (`solidcraft:plugin@1.0.0`). + +## Two plugin shapes + +| Shape | Timeline | Rebuild | Milestone | +|-------|----------|---------|-----------| +| **One-shot** | Optional; often no persistent feature | N/A | 1 (first ship) | +| **Parametric** | One **`Plugin` feature row** per plugin instance | Host calls plugin on param/timeline rebuild | 2 | + +One-shot plugins call `execute(params)` once; they may emit ordinary native features (sketch, +extrude, …) inside that transaction. Parametric plugins still use `run()` inside rebuild; the +difference is that the document **records** a single feature whose params the user edits later. + +## Generic feature shell (host-owned) + +Add one variant to the parametric document (name illustrative): + +```text +FeatureKind::Plugin { + plugin_id: String, // "gear_generator" + api_version: u32, // matches WIT world + params: Value, // JSON owned by the plugin schema + display: PluginDisplay, // timeline + browser presentation + provenance: PluginProvenance, // optional: manifest hash, build id +} +``` + +**`PluginDisplay`** (stored in the file, not recomputed from plugin code at open): + +| Field | Role | +|-------|------| +| `label` | Primary timeline / browser text | +| `icon_id` | Host icon registry name (same system as [`feature_icon`](https://github.com/bherbruck/solidcraft/blob/main/crates/ui-egui/src/workspace.rs)) | +| `tooltip` | Hover text, `inspect` type string | +| `type_name` | Fusion-like feature type for power users | + +The timeline renders **any** `Plugin` feature through one code path: read `display`, draw icon + +label, wire suppress/rename/delete/reorder like built-in features. No per-plugin egui in milestone 2. + +Optional later: **`parameter_schema()`** in WIT returns `{ id, label, kind, min, max, … }` so the +host renders sliders and dimension rows without custom UI per plugin. + +## WIT entry points (phase 2 additions) + +Milestone 1 (#1221): `manifest()`, `execute(params) -> result`. + +Milestone 2 parametric: + +| Export | Purpose | +|--------|---------| +| `evaluate(params, ctx) -> result` | Rebuild: plugin runs `run()` commands; result is success JSON or error | +| `parameter_schema() -> string` | Optional JSON schema for generic parameter UI | +| `fallback_policy() -> string` | Declares missing-plugin behaviour (see below) | + +`ctx` is host-provided read-only hints (configuration name, units, feature id); plugins must not + rely on it for correctness. + +Inside **`evaluate`**, the plugin uses the same **`run(command, args)`** import as one-shot mode. +The host applies commands to the current eval context (or a scratch session merged by the host). +No second registry of geometry ops. + +## Rebuild flow + +```text +Timeline rebuild reaches FeatureKind::Plugin + -> load plugin (wasmtime desktop / broker web) if permitted + present + -> evaluate(stored params) + -> plugin calls run("sketch.line", …), run("solid.extrude", …), … + -> host merges geometry like any native feature + -> errors surface on the feature row like native eval errors +``` + +Param changes (parameter panel, `parameters.change`, config switch) re-run **`evaluate`** for +affected plugin features only when dependencies say so (same invalidation rules as native features). + +## Missing plugin and API drift + +Manifest declares **`fallback_policy`**: + +| Policy | Behaviour | +|--------|-----------| +| `recorded_mesh` | Optional B-rep or mesh blob stored beside params; open works, edit disabled without plugin | +| `recorded_script` | Stored list of native commands; rebuild without plugin if every command is built-in | +| `suppress_with_warning` | Feature suppressed; user sees clear message | +| `error_on_rebuild` | Hard fail on eval (strict teams) | + +Design files shared in CollabCraft should prefer a policy that **opens safely** (`recorded_mesh` or +`recorded_script`). CI for plugins (#1221) should include: uninstall plugin, reopen file, assert +document still opens and policy matches manifest. + +## Icons and metadata + +Plugins declare allowed **`icon_id`** values in `manifest()`; the host rejects unknown ids. Icons +stay in the app registry so untrusted plugins cannot inject arbitrary raster assets into the tree +without a reviewed icon pack. + +**Rename** by the user updates the feature user label; **`display.label`** remains the default from +creation unless the plugin updates it via a host callback (optional future). + +Command naming: prefer **`plugin_id.function`** in `run()` or registered command aliases to avoid +collisions ([#1221 discussion](https://github.com/bherbruck/solidcraft/issues/1221)). + +## Group header variant (lighter-weight v2a) + +Before full **`evaluate`**, a stepping stone: one-shot plugin creates a **feature folder** whose +header carries `PluginDisplay` metadata while children are ordinary native features. Rebuild does +not need the plugin until the user edits plugin-owned params on the header. Migrates cleanly to +full `FeatureKind::Plugin` when `evaluate` lands. + +## Relationship to craft-libs + +Plugin **document model and eval** stay in SolidCraft (`crates/doc`, `crates/engine`). craft-libs +provides worker/broker hosting only ([web-runtime.md](web-runtime.md)). Neutral serde types +(`VersionedParams`, `PluginDisplay`) may move to a shared crate later if multiple apps need them; +CAD plugins remain SolidCraft-specific. + +## Phasing + +1. WIT + wasmtime host + one-shot `execute` + permissions tests (#1221 milestone 1). +2. `FeatureKind::Plugin` + `PluginDisplay` + timeline generic row + `evaluate` + fallback policy. +3. Web broker runs same WIT; parametric features offline only if plugin module cached and policy + allows. +4. Parameter schema UI, cloud-signed plugin index, inspector extensions. + +## Tests (parametric) + +- Create plugin feature, change param, rebuild, measure geometry (oracle or bbox). +- Undo/redo across plugin eval. +- Remove plugin binary, reopen design, assert fallback policy. +- Icon id from manifest appears on timeline (scenario or screenshot harness). diff --git a/docs/web-runtime.md b/docs/web-runtime.md new file mode 100644 index 00000000..5e70e7dc --- /dev/null +++ b/docs/web-runtime.md @@ -0,0 +1,156 @@ +# Browser runtime (design) + +Design note for SolidCraft in the browser: tiered storage, worker offload, GPU jobs, and +agent control that stay aligned with the desktop app, the control channel, and +[MCP](mcp.md). Implementation lives primarily in +[craft-libs](https://github.com/storytold/craft-libs) (`craft-tier-store`, `craft-worker-host`, +`craft-gpu`, `craft-runtime`); SolidCraft wires those crates into +[`apps/solidcraft-web`](../apps/solidcraft-web) and [`files.rs`](../apps/solidcraft-web/src/files.rs) +rather than growing a second stack here. + +Related: [plugins.md](plugins.md) and [#1221](https://github.com/bherbruck/solidcraft/issues/1221) +(desktop WIT + wasmtime first; parametric timeline features in phase 2), [design containers](design-container.md), +[control protocol](control-protocol.md). + +## Problem + +The wasm app today ([`getting-started`](../docs/book/src/guide/getting-started.md)) keeps designs in +the origin private file system (OPFS) behind virtual `/opfs/` paths, with optional File System +Access import/export. That works for small and medium designs but hits three walls: + +| Wall | Symptom in SolidCraft | +|------|------------------------| +| Quota | Large STEP imports and container blobs compete with autosave and cache in OPFS | +| Main thread | Open, save, and timeline evaluation run synchronously on the UI thread with egui | +| Agents | MCP runs out of process (`solidcraft-cli mcp`); the tab has no first-class agent session | + +WebGPU for the viewport already runs on the main thread (wgpu via eframe). Moving the entire UI +renderer into a worker is out of scope for the first integration; worker GPU is for offload jobs +(tessellation warm paths, future kernel work), not for egui. + +## Principles + +1. **One command surface.** Toolbar, scripts, control channel, MCP, and (eventually) in-browser + agents all use the same engine commands. Dialog-free commands only for unattended paths, same + rule as [MCP](mcp.md). +2. **Generic traits in craft-libs.** Storage tiers, worker RPC, and GPU dispatch are implemented + once in craft-libs. Web and native use the same traits; hosts differ (OPFS vs paths, SharedWorker + vs thread pool). +3. **Do not duplicate cloud IO.** Authenticated cloud Open/Save uses `craft-cloud` and CollabCraft + ([craft-libs #48](https://github.com/storytold/craft-libs/pull/48)). Browser-local tiers + (`craft-tier-store`) are separate: hot mirror, warm cache, cold working folder, optional range + reads of cloud blobs through CollabCraft without vendor types in generic crates. +4. **One worker broker.** A SharedWorker (or dedicated-worker fallback) coordinates storage writes, + job queues, and shared wasm module compile per origin. Plugins and offload jobs register on the + broker; do not spawn one SharedWorker per plugin ([#1221 comment thread](https://github.com/bherbruck/solidcraft/issues/1221)). + +## Architecture + +```text +Main thread (tab) + eframe + viewport WebGPU + hot mirror (sync reads for engine) + craft_runtime HostFacade ----MessagePort RPC----> SharedWorker broker + tier-store coordinator (single writer) + job queue + dedup + headless engine session(s) + optional dedicated GPU workers + ProviderRegistry (GpuJob dispatch) + +Tiers (craft-tier-store) + Hot RAM mirror, LRU budget + Warm OPFS under craft//… (today: /opfs/ mapping) + Cold File System Access project folder (no duplicate bytes in OPFS) + Cloud craft-cloud refs, range fetch via CollabCraft API (not in wasm credentials) +``` + +On **desktop**, the same `BlobStore` and job envelopes apply: warm/cold map to normal directories; +[`solidcraft-cli mcp`](mcp.md) and wasmtime plugin hosts ([#1221](https://github.com/bherbruck/solidcraft/issues/1221)) +call the same `run(command, args)` JSON. + +## Mapping SolidCraft today + +| Today | Target | +|-------|--------| +| [`files.rs`](../apps/solidcraft-web/src/files.rs) OPFS + `/opfs/` VFS | Warm tier keys; migration keeps existing filenames initially | +| Recovery autosave | Warm tier, same semantics as [`compatibility.md`](compatibility.md) | +| [Design containers](design-container.md) content-addressed blobs | Tier keys by hash; cold tier holds large blobs without copying into OPFS | +| [Control protocol](control-protocol.md) over TCP | Same methods over `MessagePort` to broker headless session (WebMCP-shaped, not a second registry) | +| Parallel mesh work ([ROADMAP](../ROADMAP.md): web path serial) | Offload eval / IO jobs via `JobSubmit`; viewport stays on main thread | + +## craft-libs crates (reference) + +These names follow the craft-libs performance platform design; SolidCraft depends on them as +published from storytold/craft-libs after cloud contracts land (#48, #52). + +| Crate | Role | +|-------|------| +| `craft-tier-store` | `BlobStore`, tier policy, sync mirror over async persist; native + wasm hosts | +| `craft-worker-host` | SharedWorker bootstrap, RPC messages (`Init`, `StorageOp`, `JobSubmit`, …) | +| `craft-gpu` | `GpuContext`, `GpuJob`, `GpuProvider`, `ProviderRegistry` (no app WGSL here) | +| `craft-gpu-wgpu` | wgpu backend: WebGPU on wasm32, Vulkan/DX12/Metal on desktop | +| `craft-gpu-providers` | Shared readback/composite helpers extracted from Craft app gpu crates | +| `craft-runtime` | Facade: tier store + worker host + optional `cloud-delegate` hooks | + +Generic runtime code must not depend on SolidCraft crates. SolidCraft registers app-specific +providers and key namespaces only. + +## GPU and the viewport + +- **Main thread:** eframe owns the WebGPU device used for the 3D view and UI. +- **Workers:** `craft_runtime::gpu::dispatch(registry, jobs)` runs `GpuJob` lists (chunked buffers, + readback fences) for offload; same entry point on native thread pools. +- **Limits:** Capability probe once at init; UI and workers share reported max buffer sizes. + +## Agents in the browser + +| Mode | Transport | +|------|-----------| +| Desktop agent | `solidcraft-cli mcp` (stdio) or `--connect` to [control protocol](control-protocol.md) | +| Browser agent | RPC to broker session; dialog-free tools only | +| IDE bridge | Optional local relay (stdio MCP ↔ tab); not required for CollabCraft-hosted wasm | + +File policy for web agents mirrors MCP folders: design directory, cold tier roots, recovery; +never `--allow-any-path` in the tab. + +## Plugins ([#1221](https://github.com/bherbruck/solidcraft/issues/1221)) + +| Host | Runtime | +|------|---------| +| Desktop | wasmtime + fuel/memory limits; WIT `solidcraft:plugin@1.0.0` with `run`, `manifest`, permissions | +| Web | Component or module in broker; same WIT contract; sandbox = manifest permissions enforced before `run()` | + +Milestone 1: one-shot commands and importers/exporters. Milestone 2: **parametric timeline +features** via a generic host-owned `FeatureKind::Plugin` shell (params + display metadata +including timeline icon and label); plugins rebuild through WIT `evaluate()` calling the same +`run()` JSON. See [plugins.md](plugins.md). Command ids may use a `pluginname.function` prefix to +avoid collisions. + +## CollabCraft deployment + +Static wasm apps on `craft.quasarke.net` ship `craft-worker-host.js` (brotli-precompressed) beside +the app bundle. Prefer one global runtime worker per origin once message routing and wasm size are +budgeted; per-app worker URLs are acceptable for pilots. CSP must allow `worker-src` / SharedWorker. + +## Phasing (SolidCraft) + +1. **Document and track** (this file); no user-visible change. +2. **Tier-store wiring:** replace ad-hoc OPFS glue with `craft-tier-store` keys; keep on-disk layout. +3. **Worker broker:** headless `engine.execute` batches and container IO off UI thread. +4. **Agent bridge:** MessagePort tool surface equivalent to MCP dialog-free tools. +5. **Plugins on web:** after desktop wasmtime milestone and WIT stability. +6. **Parametric plugin features:** [plugins.md](plugins.md) phase 2 (`evaluate`, display metadata, fallback policy). + +## Out of scope (initial integration) + +- Moving egui/viewport WebGPU into SharedWorker +- Second non-wgpu GPU backend in craft-libs +- **Implementing** parametric `FeatureKind::Plugin` (designed in [plugins.md](plugins.md); follows craft-runtime wiring) +- Duplicating `craft-cloud` or embedding `craft-nextcloud` in SolidCraft wasm + +## Tests + +- Extend control-channel scenarios for broker RPC parity where methods map 1:1 +- Worker tests: SharedWorker multi-tab dedup (EffectCraft `workers.mjs` pattern) +- Tier-store: native LRU tests + wasm OPFS smoke +- GPU: golden frames remain in app crates; craft-gpu-null for CI dispatch smoke