Files
figcraft/xtask/src/layers.rs
T
BFlatandClaude Opus 5.5 041b3f4e5d M7: WASM plugins (#101)
Sandboxed WebAssembly plugins whose API is the command registry:

- figcraft-plugin (new crate, L2): the figcraft-plugin.json manifest
  (menu commands with parameters, read/write access, command allowlist)
  and the sandbox: wasmi (pure Rust, no JIT, also in the web build) with
  fuel metering, a memory cap, no WASI and seven host functions (call,
  selection, reply_len, reply_read, log, notify, set_output).
- Engine: plugin.list / plugin.import / plugin.remove / plugin.run,
  plugins.runLast (Alt+Cmd+P; Ctrl+Alt+Shift+P on Windows/Linux) and
  plugins.manage. A run is one undo step (Session::grouped, which
  command.batch now uses too) and is rolled back when it fails. Files,
  other documents, the undo history, dialogs and plugins are off limits;
  read-only plugins can't change the document. Installed plugins live in
  the preferences; development plugins re-read their module every run.
- UI: installed plugins in the Plugins menu, a parameters form drawn
  from the manifest, Manage plugins, Plugins > Development > Import
  plugin from manifest.
- plugins/: a no_std Rust SDK and two example plugins (Tidy names,
  Swatch grid), built for wasm32 by `cargo xtask plugins` (part of
  `cargo xtask wasm`).
- Docs: docs/plugins.md (manifest, permissions, limits, ABI, SDK),
  docs/mcp.md, docs/control-protocol.md, README, ROADMAP, parity.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:42:17 +07:00

65 lines
2.2 KiB
Rust

//! Crate layering: a crate may depend only on lower layers (or earlier crates of its own layer
//! listed in ORDER). Nothing below the UI depends on egui/eframe.
use std::collections::BTreeMap;
use std::process::Command;
/// (crate suffix, layer)
const TABLE: &[(&str, u8)] = &[
("geom", 0),
("color", 0),
("doc", 1),
("layout", 2),
("text", 2),
("pathops", 2),
("plugin", 2),
("render", 3),
("format", 3),
("svg", 4),
("pdf", 4),
("engine", 5),
("ui-egui", 6),
("mcp", 6),
];
pub fn check(root: &std::path::Path) -> bool {
let out = Command::new(std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()))
.args(["metadata", "--format-version", "1", "--no-deps"])
.current_dir(root)
.output();
let Ok(out) = out else { return false };
let Ok(v) = serde_json::from_slice::<serde_json::Value>(&out.stdout) else { return false };
let layer: BTreeMap<String, u8> = TABLE.iter().map(|(n, l)| (format!("figcraft-{n}"), *l)).collect();
let mut ok = true;
for pkg in v["packages"].as_array().cloned().unwrap_or_default() {
let name = pkg["name"].as_str().unwrap_or("").to_string();
let Some(&l) = layer.get(&name) else { continue };
for dep in pkg["dependencies"].as_array().cloned().unwrap_or_default() {
let dn = dep["name"].as_str().unwrap_or("");
if dep["kind"].as_str() == Some("dev") {
continue;
}
if l < 6 && (dn == "egui" || dn == "eframe" || dn == "rfd" || dn == "winit") {
eprintln!("layers: {name} (L{l}) depends on UI crate {dn}");
ok = false;
}
if let Some(&dl) = layer.get(dn)
&& dl >= l
&& !(dl == l && same_layer_ok(&name, dn))
{
eprintln!("layers: {name} (L{l}) may not depend on {dn} (L{dl})");
ok = false;
}
}
}
if ok {
eprintln!("layers: ok");
}
ok
}
fn same_layer_ok(from: &str, to: &str) -> bool {
// Within L0: color uses geom types; within L2: text/layout/pathops are independent.
matches!((from, to), ("figcraft-color", "figcraft-geom") | ("figcraft-render", "figcraft-format"))
}