A design note for SolidCraft in the browser: tiered storage, a SharedWorker job broker, GPU dispatch and in-browser agent control through the same engine commands, with the generic pieces in craft-libs. Linked from the MCP and web getting-started docs.
This commit is contained in:
1 parent
784648af2a
commit
41f0c3d0c6
4 files changed
+310
-1
No files matched your search
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
+146
@@ -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<string, string>`.
|
||||
|
||||
Milestone 2 parametric:
|
||||
|
||||
| Export | Purpose |
|
||||
|--------|---------|
|
||||
| `evaluate(params, ctx) -> result<string, string>` | 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).
|
||||
@@ -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/<appId>/… (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
|
||||
Reference in new issue
Block a user