diff --git a/AGENTS.md b/AGENTS.md index 55792f8..708da80 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -46,7 +46,8 @@ code. `assets/`, `docs/brand/` or `docs/images/`. - FigCraft's UI icons are drawn in code (`crates/ui-egui/src/icons.rs`) from simple primitives and are original. Keep it that way: new icons are new code, designed from scratch. -- The app icon is generated in code (`cargo xtask icon`, `xtask/src/icon.rs`). +- The app icon is the owner's chameleon drawing, vectorised (`assets/app-icon/README.md`); `cargo xtask icon` + renders the PNGs and `.ico` from `assets/app-icon/figcraft.svg`. - **Fonts live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), never in this repo.** The app uses it only through the optional `CRAFT_FONTS_DIR` build input (`.cargo/config.toml` points it at `../craft-fonts` for local builds; `crates/text/build.rs` diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 4225310..1f72aeb 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -11,7 +11,7 @@ MIT OR Apache-2.0. No third-party icon sets are used. | Path | Author | Source | Licence | Notes | |---|---|---|---|---| -| `assets/app-icon/` (all files: `figcraft-*.png`, `figcraft.ico`, `hicolor/*/apps/ai.storyteller.figcraft.png`) | FigCraft contributors | Generated by `cargo xtask icon` (`xtask/src/icon.rs`) with FigCraft's own renderer | MIT OR Apache-2.0 | Original geometric mark (stacked layer cards and a cursor) | +| `assets/app-icon/` (all files: `figcraft.svg`, `figcraft-small.svg`, `figcraft-*.png`, `figcraft.ico`, `hicolor/`) | The project owner (engraving-style drawing, vectorised) | Original work, see [`assets/app-icon/README.md`](assets/app-icon/README.md); derived files rendered by `cargo xtask icon` | MIT OR Apache-2.0, [`assets/app-icon/LICENSE.txt`](assets/app-icon/LICENSE.txt) | FigCraft app icon (chameleon) | | `docs/brand/artcraft-logo.svg`, `docs/brand/artcraft-logo.png`, `docs/brand/artcraft-logo-white.svg`, `docs/brand/artcraft-logo-white.png`, `docs/brand/artcraft-mark.svg`, `docs/brand/artcraft-mark.png`, `docs/brand/artcraft-mark-black.svg`, `docs/brand/artcraft-mark-black.png` | ArtCraft Team | storytold/craftrules `assets/brand/` | ArtCraft brand terms (`docs/brand/LICENSE-brand.txt`), not open source | Trademarks; unmodified use within FigCraft only | | `docs/images/` (all screenshots and exports: `shot-*.png`, `art-*.png`) | FigCraft contributors | Screenshots of FigCraft showing synthetic demo documents made in FigCraft, and PNG exports of those documents by FigCraft's renderer | MIT OR Apache-2.0 | No third-party artwork | | `examples/*.figcraft` | FigCraft contributors | Synthetic demo documents built through FigCraft's command API; invented copy, shapes and vectors drawn from scratch, no images or embedded fonts | MIT OR Apache-2.0 | Text uses Inter (OFL, from craft-fonts or the system); no third-party artwork | diff --git a/CLAUDE.md b/CLAUDE.md index 55792f8..708da80 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -46,7 +46,8 @@ code. `assets/`, `docs/brand/` or `docs/images/`. - FigCraft's UI icons are drawn in code (`crates/ui-egui/src/icons.rs`) from simple primitives and are original. Keep it that way: new icons are new code, designed from scratch. -- The app icon is generated in code (`cargo xtask icon`, `xtask/src/icon.rs`). +- The app icon is the owner's chameleon drawing, vectorised (`assets/app-icon/README.md`); `cargo xtask icon` + renders the PNGs and `.ico` from `assets/app-icon/figcraft.svg`. - **Fonts live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), never in this repo.** The app uses it only through the optional `CRAFT_FONTS_DIR` build input (`.cargo/config.toml` points it at `../craft-fonts` for local builds; `crates/text/build.rs` diff --git a/Cargo.lock b/Cargo.lock index 2cc48eb..353c9d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5805,12 +5805,7 @@ checksum = "9440ea3e5aeabb0ac63af70daf835274065238cdd0cec83418f417eae38bacee" name = "xtask" version = "0.4.0" dependencies = [ - "figcraft-color", - "figcraft-doc", "figcraft-engine", - "figcraft-render", - "figcraft-svg", - "image", "serde_json", ] diff --git a/apps/figcraft/src/services.rs b/apps/figcraft/src/services.rs index 2f98515..ea1658d 100644 --- a/apps/figcraft/src/services.rs +++ b/apps/figcraft/src/services.rs @@ -210,7 +210,11 @@ impl Services for Desktop { /// The window icon from the bundled app icon (if present). pub fn window_icon() -> Option { + // macOS wants Apple's padded grid; the other platforms the tile edge to edge. + #[cfg(target_os = "macos")] let bytes = include_bytes!("../../../assets/app-icon/figcraft-256.png"); + #[cfg(not(target_os = "macos"))] + let bytes = include_bytes!("../../../assets/app-icon/hicolor/256x256/apps/ai.storyteller.figcraft.png"); let img = image::load_from_memory(bytes).ok()?.to_rgba8(); let (w, h) = img.dimensions(); Some(egui::IconData { rgba: img.into_raw(), width: w, height: h }) diff --git a/assets/app-icon/LICENSE.txt b/assets/app-icon/LICENSE.txt new file mode 100644 index 0000000..134a8b1 --- /dev/null +++ b/assets/app-icon/LICENSE.txt @@ -0,0 +1,12 @@ +FigCraft app icon (figcraft.svg, figcraft-small.svg and every file derived from them in this +directory: figcraft-.png, figcraft.ico, hicolor/). + +Original artwork made for this project by the project owner: an engraving-style drawing, then +vectorised. No third-party material. Licensed like the rest of FigCraft, under either of + + * Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0) + * MIT license (https://opensource.org/licenses/MIT) + +at your option. + +Regenerate the derived files with `cargo xtask icon`. diff --git a/assets/app-icon/README.md b/assets/app-icon/README.md new file mode 100644 index 0000000..cf00fec --- /dev/null +++ b/assets/app-icon/README.md @@ -0,0 +1,41 @@ +# FigCraft app icon + +A **chameleon**: an engraving portrait in profile with a curled tail, in the Crafting Apps' +owl-template framing (full-bleed field, head and body filling the tile, body running off the edges). + +## Palette + +Exactly three colours: + +| Colour | Hex | Used for | +|---|---|---| +| Ink | `#0b0b0c` | line work and contours | +| Paper | `#efe9dc` | the figure | +| FigCraft green (app colour) | `#02da96` | the full-bleed field | + +## Geometry + +A 512-unit tile (`viewBox="0 0 512 512"`), rounded square with `rx=112`, no border. The macOS +renders pad it onto Apple's 824/1024 icon grid; the Windows and Linux renders crop 22 units off +each side so the figure reads at 16–48 px. + +## Provenance + +The owner's original engraving-style drawing (1500 px, keyed to the palette), supplied for +FigCraft and vectorised with craftrules `assets/logo-options/_tools/vectorize_tile.py`. The source +image is not kept in this repo. License: see `LICENSE.txt`. + +## Files + +- `figcraft.svg`: canonical master (traced at 1500 px, the source's size). +- `figcraft-small.svg`: lighter trace (traced at 1024 px); also the hicolor scalable icon. +- `figcraft-.png` (16–1024): renders on the macOS grid; `packaging/macos/package.sh` builds + the `.icns` from them. +- `figcraft.ico`: Windows icon, 16–256 px, embedded in the `.exe` by `apps/figcraft/build.rs`. +- `hicolor//apps/ai.storyteller.figcraft.png` (16–512) and `hicolor/scalable/...svg`: + Linux icon theme; the 256 px one is also the runtime window icon off macOS. + +## Regenerate + +Replace `figcraft.svg` (and `figcraft-small.svg`), then run `cargo xtask icon`. It needs `resvg` +(`cargo install resvg`). diff --git a/assets/app-icon/figcraft-1024.png b/assets/app-icon/figcraft-1024.png index 967d1af..02dc35d 100644 Binary files a/assets/app-icon/figcraft-1024.png and b/assets/app-icon/figcraft-1024.png differ diff --git a/assets/app-icon/figcraft-128.png b/assets/app-icon/figcraft-128.png index 8d735b8..fccb9dd 100644 Binary files a/assets/app-icon/figcraft-128.png and b/assets/app-icon/figcraft-128.png differ diff --git a/assets/app-icon/figcraft-16.png b/assets/app-icon/figcraft-16.png index 2b56b65..fc206ae 100644 Binary files a/assets/app-icon/figcraft-16.png and b/assets/app-icon/figcraft-16.png differ diff --git a/assets/app-icon/figcraft-256.png b/assets/app-icon/figcraft-256.png index 02200c6..7cefb3c 100644 Binary files a/assets/app-icon/figcraft-256.png and b/assets/app-icon/figcraft-256.png differ diff --git a/assets/app-icon/figcraft-32.png b/assets/app-icon/figcraft-32.png index 6aa430e..0fc0cc4 100644 Binary files a/assets/app-icon/figcraft-32.png and b/assets/app-icon/figcraft-32.png differ diff --git a/assets/app-icon/figcraft-48.png b/assets/app-icon/figcraft-48.png index 3f57334..5c2f46e 100644 Binary files a/assets/app-icon/figcraft-48.png and b/assets/app-icon/figcraft-48.png differ diff --git a/assets/app-icon/figcraft-512.png b/assets/app-icon/figcraft-512.png index 5b81f3f..f5aed87 100644 Binary files a/assets/app-icon/figcraft-512.png and b/assets/app-icon/figcraft-512.png differ diff --git a/assets/app-icon/figcraft-64.png b/assets/app-icon/figcraft-64.png index 291ccad..a862ed2 100644 Binary files a/assets/app-icon/figcraft-64.png and b/assets/app-icon/figcraft-64.png differ diff --git a/assets/app-icon/figcraft-small.svg b/assets/app-icon/figcraft-small.svg new file mode 100644 index 0000000..5df77c8 --- /dev/null +++ b/assets/app-icon/figcraft-small.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/app-icon/figcraft.ico b/assets/app-icon/figcraft.ico index 923c4bf..29ca546 100644 Binary files a/assets/app-icon/figcraft.ico and b/assets/app-icon/figcraft.ico differ diff --git a/assets/app-icon/figcraft.svg b/assets/app-icon/figcraft.svg index 7e30a36..de38c93 100644 --- a/assets/app-icon/figcraft.svg +++ b/assets/app-icon/figcraft.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/assets/app-icon/hicolor/128x128/apps/ai.storyteller.figcraft.png b/assets/app-icon/hicolor/128x128/apps/ai.storyteller.figcraft.png index 8d735b8..b71cddf 100644 Binary files a/assets/app-icon/hicolor/128x128/apps/ai.storyteller.figcraft.png and b/assets/app-icon/hicolor/128x128/apps/ai.storyteller.figcraft.png differ diff --git a/assets/app-icon/hicolor/16x16/apps/ai.storyteller.figcraft.png b/assets/app-icon/hicolor/16x16/apps/ai.storyteller.figcraft.png index 2b56b65..7607ae0 100644 Binary files a/assets/app-icon/hicolor/16x16/apps/ai.storyteller.figcraft.png and b/assets/app-icon/hicolor/16x16/apps/ai.storyteller.figcraft.png differ diff --git a/assets/app-icon/hicolor/256x256/apps/ai.storyteller.figcraft.png b/assets/app-icon/hicolor/256x256/apps/ai.storyteller.figcraft.png index 02200c6..e0115f0 100644 Binary files a/assets/app-icon/hicolor/256x256/apps/ai.storyteller.figcraft.png and b/assets/app-icon/hicolor/256x256/apps/ai.storyteller.figcraft.png differ diff --git a/assets/app-icon/hicolor/32x32/apps/ai.storyteller.figcraft.png b/assets/app-icon/hicolor/32x32/apps/ai.storyteller.figcraft.png index 6aa430e..0393dfe 100644 Binary files a/assets/app-icon/hicolor/32x32/apps/ai.storyteller.figcraft.png and b/assets/app-icon/hicolor/32x32/apps/ai.storyteller.figcraft.png differ diff --git a/assets/app-icon/hicolor/48x48/apps/ai.storyteller.figcraft.png b/assets/app-icon/hicolor/48x48/apps/ai.storyteller.figcraft.png index 3f57334..99017e6 100644 Binary files a/assets/app-icon/hicolor/48x48/apps/ai.storyteller.figcraft.png and b/assets/app-icon/hicolor/48x48/apps/ai.storyteller.figcraft.png differ diff --git a/assets/app-icon/hicolor/512x512/apps/ai.storyteller.figcraft.png b/assets/app-icon/hicolor/512x512/apps/ai.storyteller.figcraft.png index 5b81f3f..20e6045 100644 Binary files a/assets/app-icon/hicolor/512x512/apps/ai.storyteller.figcraft.png and b/assets/app-icon/hicolor/512x512/apps/ai.storyteller.figcraft.png differ diff --git a/assets/app-icon/hicolor/64x64/apps/ai.storyteller.figcraft.png b/assets/app-icon/hicolor/64x64/apps/ai.storyteller.figcraft.png index 291ccad..96f63c8 100644 Binary files a/assets/app-icon/hicolor/64x64/apps/ai.storyteller.figcraft.png and b/assets/app-icon/hicolor/64x64/apps/ai.storyteller.figcraft.png differ diff --git a/assets/app-icon/hicolor/scalable/apps/ai.storyteller.figcraft.svg b/assets/app-icon/hicolor/scalable/apps/ai.storyteller.figcraft.svg index 7e30a36..5df77c8 100644 --- a/assets/app-icon/hicolor/scalable/apps/ai.storyteller.figcraft.svg +++ b/assets/app-icon/hicolor/scalable/apps/ai.storyteller.figcraft.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index ea28683..59bfb3c 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -7,9 +7,4 @@ publish = false [dependencies] serde_json = { workspace = true } -figcraft-doc = { workspace = true } -figcraft-color = { workspace = true } -figcraft-render = { workspace = true } figcraft-engine = { workspace = true } -figcraft-svg = { workspace = true } -image = { workspace = true } diff --git a/xtask/src/icon.rs b/xtask/src/icon.rs index 9f9d4c8..d189d79 100644 --- a/xtask/src/icon.rs +++ b/xtask/src/icon.rs @@ -1,102 +1,64 @@ -//! The app icon, generated from code with FigCraft's own renderer (original artwork, -//! MIT OR Apache-2.0): a rounded tile in the app colour with a paper "frame" mark — three stacked -//! layer cards and a cursor. Writes assets/app-icon/*.png, the hicolor theme and figcraft.ico. +//! The app icon: rasterises the master `assets/app-icon/figcraft.svg` (the chameleon, see +//! `assets/app-icon/README.md`) with the `resvg` CLI (`cargo install resvg`). Writes +//! figcraft-.png (macOS grid), the hicolor theme and figcraft.ico. The outputs are +//! committed, so building and packaging never need `resvg`. -use figcraft_color::{Color, Paint}; -use figcraft_doc::{Document, NodeKind}; +use std::path::Path; +use std::process::Command; -pub const APP_COLOR: &str = "0EA5C6"; -const INK: &str = "0B0B0C"; -const PAPER: &str = "EFE9DC"; +const VIEW_BOX: &str = r#"viewBox="0 0 512 512""#; +/// macOS: the 512-unit tile padded onto Apple's 824/1024 icon grid (transparent margin). +const MAC_VIEW_BOX: &str = r#"viewBox="-62 -62 636 636""#; +/// Windows and Linux: 22 units cropped off each side so the portrait reads at 16-48 px. +const TIGHT_VIEW_BOX: &str = r#"viewBox="22 22 468 468""#; -fn build() -> Option { - let mut d = Document::new("icon"); - let p = d.first_page()?; - let bg = d.create(p, NodeKind::Rectangle, "tile", 0.0, 0.0, 512.0, 512.0).ok()?; - let n = d.get_mut(bg).ok()?; - n.corners.radii = [112.0; 4]; - n.corners.smoothing = 0.6; - n.fills = vec![Paint::solid(Color::from_hex(APP_COLOR)?)]; - // Three stacked cards (layers), back to front. - for (i, (x, y, w, h)) in [(150.0, 96.0, 250.0, 200.0), (122.0, 150.0, 250.0, 200.0), (94.0, 204.0, 250.0, 200.0)].iter().enumerate() { - let c = d.create(p, NodeKind::Rectangle, "card", *x, *y, *w, *h).ok()?; - let n = d.get_mut(c).ok()?; - n.corners.radii = [28.0; 4]; - n.corners.smoothing = 0.6; - n.fills = vec![Paint::solid(Color::from_hex(PAPER)?)]; - n.strokes = vec![Paint::solid(Color::from_hex(INK)?)]; - n.stroke.weight = 14.0; - n.stroke.align = figcraft_doc::StrokeAlign::Inside; - if i == 2 { - // Front card: a frame grid "#". - for (gx, gy, gw, gh) in - [(150.0, 228.0, 14.0, 152.0), (250.0, 228.0, 14.0, 152.0), (118.0, 260.0, 178.0, 14.0), (118.0, 330.0, 178.0, 14.0)] - { - let g = d.create(p, NodeKind::Rectangle, "grid", gx, gy, gw, gh).ok()?; - let gn = d.get_mut(g).ok()?; - gn.fills = vec![Paint::solid(Color::from_hex(INK)?)]; - gn.corners.radii = [7.0; 4]; - } - } +fn render(svg: &Path, size: u32, out: &Path) -> bool { + let s = size.to_string(); + let ok = Command::new("resvg").args(["-w", &s, "-h", &s]).arg(svg).arg(out).status().is_ok_and(|st| st.success()); + if !ok { + eprintln!("icon: resvg failed for {} (is it installed? `cargo install resvg`)", out.display()); } - // Cursor. - let cur = figcraft_doc::VectorNetwork::from_points( - &[ - (0.0, 0.0).into(), - (0.0, 120.0).into(), - (32.0, 92.0).into(), - (52.0, 136.0).into(), - (72.0, 128.0).into(), - (52.0, 84.0).into(), - (92.0, 82.0).into(), - ], - true, - ); - let c = d.create(p, NodeKind::Vector { network: cur }, "cursor", 318.0, 300.0, 92.0, 136.0).ok()?; - let n = d.get_mut(c).ok()?; - n.fills = vec![Paint::solid(Color::from_hex(INK)?)]; - n.strokes = vec![Paint::solid(Color::from_hex(PAPER)?)]; - n.stroke.weight = 10.0; - n.stroke.align = figcraft_doc::StrokeAlign::Outside; - n.stroke.join = figcraft_doc::StrokeJoin::Round; - Some(d) + ok } -pub fn generate(root: &std::path::Path) -> bool { - let Some(doc) = build() else { return false }; - let Some(page) = doc.first_page() else { return false }; +pub fn generate(root: &Path) -> bool { let dir = root.join("assets/app-icon"); - let _ = std::fs::create_dir_all(dir.join("hicolor")); - let render = |size: u32| -> Option { - let view = figcraft_render::View { origin: (0.0, 0.0), scale: f64::from(size) / 512.0, width: size, height: size }; - let opts = figcraft_render::Options { background: Some(Color::TRANSPARENT), ..Default::default() }; - let pix = figcraft_render::render_view(&doc, page, &view, &opts)?; - image::RgbaImage::from_raw(size, size, figcraft_render::to_rgba8(&pix)) + let Ok(master) = std::fs::read_to_string(dir.join("figcraft.svg")) else { + eprintln!("icon: cannot read {}", dir.join("figcraft.svg").display()); + return false; }; - let mut ok = true; + if !master.contains(VIEW_BOX) { + eprintln!("icon: expected {VIEW_BOX} in figcraft.svg"); + return false; + } + let tmp = root.join("target/icon"); + let (mac, tight) = (tmp.join("mac.svg"), tmp.join("tight.svg")); + let mut ok = std::fs::create_dir_all(&tmp).is_ok() + && std::fs::write(&mac, master.replacen(VIEW_BOX, MAC_VIEW_BOX, 1)).is_ok() + && std::fs::write(&tight, master.replacen(VIEW_BOX, TIGHT_VIEW_BOX, 1)).is_ok(); + if !ok { + eprintln!("icon: cannot write {}", tmp.display()); + return false; + } for size in [16u32, 32, 48, 64, 128, 256, 512, 1024] { - let Some(img) = render(size) else { return false }; - let name = if size == 1024 { "figcraft-1024.png".to_string() } else { format!("figcraft-{size}.png") }; - ok &= img.save(dir.join(&name)).is_ok(); + ok &= render(&mac, size, &dir.join(format!("figcraft-{size}.png"))); if size <= 512 { let hd = dir.join(format!("hicolor/{size}x{size}/apps")); let _ = std::fs::create_dir_all(&hd); - ok &= img.save(hd.join("ai.storyteller.figcraft.png")).is_ok(); + ok &= render(&tight, size, &hd.join("ai.storyteller.figcraft.png")); } } // Windows .ico (PNG-compressed entries). - let mut ico = Vec::new(); - let sizes = [16u32, 32, 48, 256]; + let sizes = [16u32, 20, 24, 32, 40, 48, 64, 128, 256]; let pngs: Vec> = sizes .iter() .filter_map(|s| { - let img = render(*s)?; - let mut out = Vec::new(); - img.write_to(&mut std::io::Cursor::new(&mut out), image::ImageFormat::Png).ok()?; - Some(out) + let png = tmp.join(format!("ico-{s}.png")); + render(&tight, *s, &png).then(|| std::fs::read(png).ok()).flatten() }) .collect(); - ico.extend_from_slice(&[0, 0, 1, 0, pngs.len() as u8, 0]); + ok &= pngs.len() == sizes.len(); + let mut ico = vec![0, 0, 1, 0, pngs.len() as u8, 0]; let mut offset = 6 + 16 * pngs.len() as u32; for (s, png) in sizes.iter().zip(&pngs) { let b = if *s >= 256 { 0 } else { *s as u8 }; @@ -109,17 +71,10 @@ pub fn generate(root: &std::path::Path) -> bool { ico.extend_from_slice(png); } ok &= std::fs::write(dir.join("figcraft.ico"), ico).is_ok(); - // Scalable SVG (same artwork, written by FigCraft's SVG exporter). - let tops: Vec<_> = doc.node(page).map(|p| p.children.clone()).unwrap_or_default(); - match figcraft_svg::export_svg(&doc, &tops, &Default::default()) { - Some(svg) => { - let sd = dir.join("hicolor/scalable/apps"); - let _ = std::fs::create_dir_all(&sd); - ok &= std::fs::write(dir.join("figcraft.svg"), &svg).is_ok(); - ok &= std::fs::write(sd.join("ai.storyteller.figcraft.svg"), &svg).is_ok(); - } - None => ok = false, - } + // The lighter trace keeps the scalable theme icon cheap to render. + let sd = dir.join("hicolor/scalable/apps"); + let _ = std::fs::create_dir_all(&sd); + ok &= std::fs::copy(dir.join("figcraft-small.svg"), sd.join("ai.storyteller.figcraft.svg")).is_ok(); eprintln!("icon: wrote {}", dir.display()); ok } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 771e095..e3f00e8 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -5,7 +5,7 @@ //! - `assets` every file under assets/ and docs/ has a row in ATTRIBUTION.md //! - `parity` menu-catalog coverage → docs/parity-checklist.md //! - `version` print the version, or `version set X.Y.Z` -//! - `icon` regenerate the app icon PNGs/ICO from code (original artwork) +//! - `icon` regenerate the app icon PNGs/ICO from assets/app-icon/figcraft.svg (needs resvg) //! - `wasm` check the core crates and web app for wasm32, and build the plugin SDK and examples //! - `plugins` format-check, lint, test and build the plugin SDK and example plugins (`plugins/`, a //! workspace of its own) for wasm32: `/plugins/dist//` holds each manifest and