mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-25 10:56:48 -06:00
8cfd43f1c0a160e2de4eb962f3ffd20ddc4d1ccd
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
01bc5a9dac |
M11: plugin installs saved at once; directory and library requests off the UI thread
- **Installed plugins survive a crash.** Importing, installing, updating or removing a plugin, and a plugin changing its settings, now sets `Runtime::unsaved`; at the end of each frame the app hands the preferences to a new `Services::save_prefs`, and the desktop app writes `state.json` at once (temporary file + rename, the same atomic write the quit path uses, now shared). Nothing waits for quit any more. - **The UI never waits on the network.** New `Session::start_background(id, params)`: the command runs as usual until it makes a server request; the request goes out on the link's own thread (`LiveLink::background`, implemented by the desktop `NetLink` with one short-lived thread per request) and the command stops; when the answer arrives (`Event::Net`, taken by `live_poll`, which the network's waker already triggers) the command runs again from the start on the file it started on, answered from the answers so far, until it needs no new request. All server requests go through one funnel (`Session::net`), so `plugin.directory.*`, `library.publishToServer`, `library.add` (links), `library.usage` and library fetches work this way without being split. A re-run of `library.publishToServer` caches the library it actually sent (a fresh copy differs in its time and, on a first publish, its id). One job per command; `cancel_background` drops a late answer. - **UI:** `FigApp::run_background(ctx, id, params, done)` with a boxed completion. Manage plugins > Browse shows "Fetching from the server..." with Cancel instead of Browse; the install confirmation shows "Installing..." with Cancel and stays open until the download is in (an error leaves it open to retry); row buttons wait during an install; closing the window cancels. Assets > Libraries: Publish to server shows "Publishing..." + Cancel, Show usage and pasting a team library link show "Fetching from the server..." + Cancel. No polling repaints: the window draws again when the answer's waker fires; pending state is a scan of a short Vec, no per-frame allocation. - **Programmatic callers are unchanged:** MCP, CLI, control channel and scripts still run the same commands synchronously (and the browser link, which has no background requests, too). - New string "Installing..." (en/es); docs: plugins.md (immediate saves, Browse/Install never wait), server.md (team library requests never wait). |
||
|
|
def6f84b22 |
M11: plugin directory on figcraft-server
A self-hosted plugin directory, with no third-party service: `figcraft-server` keeps plugin bundles,
the app browses, installs and updates from it, and `figcraft-cli` publishes to it.
- **Bundles** (`figcraft_plugin::bundle`): a plugin's manifest, module and up to 32 plain-named
assets in one uncompressed, length-prefixed binary form (nothing can expand beyond the bytes
sent). Its SHA-256 names it. `Bundle::decode` never allocates beyond its input and refuses
trailing bytes; `Bundle::check` parses the manifest (refusing a plugin API this build doesn't
run), requires a version of 1-4 numbers, compiles the module without running it (only the
sandbox's imports allowed) and checks asset names (no paths, no dot files, no Windows device
names, unique ignoring case). Manifests gain an optional `author`.
- **Protocol**: a new connection kind next to managing and team-library connections:
`OpenPlugins {key}` then `Plugins {list | fetch | publish | unpublish}`. Browsing needs no
credential; publishing and unpublishing need the server key (a wrong key is refused). Older
servers refuse the connection and the app says the server has no plugin directory. The protocol
version is unchanged; old clients are unaffected.
- **Server** (`apps/figcraft-server/src/directory.rs`): `<data>/plugins/index.json` plus
`<data>/plugins/<sha256>.bin`, so no publisher-chosen name reaches the file system. Every publish
is checked in full and must be a newer version; caps `max_plugins` (versions, 0 turns it off),
`max_plugin_mb` (<= 32), `max_plugins_mb`. Fetches re-hash the file before sending (a damaged
bundle is never sent) and share a download budget (~32 MiB/s) so a flood is refused, not queued.
Damaged index entries are skipped and reported, never deleted; an unreadable index lists nothing
and refuses writes until the operator fixes it. Operator moderation:
`figcraft-server plugins [list]` and `plugins remove ID [--version V]` (offline or through the
running server). Logs name ids and versions, never keys.
- **App**: Plugins > Manage plugins gets Installed / Browse tabs. Browse lists a server's plugins
(version, author as typed, size, access) with a search; nothing is fetched until Browse is
pressed. Install / Update open a confirmation listing the permissions asked for, the source and
the SHA-256; only its button downloads. The download must match the listed SHA-256 (and the one
shown), then is checked like an imported plugin before anything is written. Installed plugins
live per user under `<settings>/FigCraft/Plugins/<hash of id>/` and run in the same sandbox;
Remove deletes the folder.
- **Commands**: `plugin.directory.list {server?, query?}`, `plugin.directory.install {id, version?,
confirm, server?, sha256?}` (refused without `confirm: true`; never opens a dialog),
`plugin.directory.publish {path | plugin, server}`, `plugin.directory.unpublish {id, version?,
server}`. `ui.set {pluginsTab, pluginConfirm}` on the control channel.
- **CLI**: `figcraft-cli plugin publish FOLDER --server ws://host:port/` with the key from
`FIGCRAFT_SERVER_KEY` (or `?key=`), never printed.
- Docs: plugins.md (directories, publishing, commands, manifest), server.md (operating the
directory: access, checks, caps, moderation, backups, security), collab-protocol.md, mcp.md,
cli.md, control-protocol.md, target-app-parity area 18 and Ecosystem, ROADMAP, roadmap.md M11,
gaps.md gap 6. Plugins & widgets 55%/32% -> 60%/35%, 10-30 h -> 8-26 h.
|