diff --git a/.github/workflows/windows-arm64.yml b/.github/workflows/windows-arm64.yml index 7fc9b59251..b121ce45f1 100644 --- a/.github/workflows/windows-arm64.yml +++ b/.github/workflows/windows-arm64.yml @@ -27,8 +27,8 @@ permissions: jobs: windows-arm64: - # Immutable supporting workflow revision until the new install contract lands upstream. - uses: storytold/craft-common-actions/.github/workflows/windows-arm64.yml@8bb4e5ab95443171104ad88113fe1ef9b0e5ce3f + # Intentionally tracked at @main (not SHA-pinned) so org-wide CI improvements, security fixes, and build optimizations propagate without PR churn across all craftapp repos. + uses: storytold/craft-common-actions/.github/workflows/windows-arm64.yml@main with: app-name: vectorcraft app-dir: VectorCraft diff --git a/.github/workflows/windows7.yml b/.github/workflows/windows7.yml deleted file mode 100644 index c638bcef57..0000000000 --- a/.github/workflows/windows7.yml +++ /dev/null @@ -1,54 +0,0 @@ -# Experimental, unsupported Windows 7 SP1 x64 portable build (docs/windows7.md). It runs by hand and -# weekly and for changes to the compatibility path; the weekly run shows when a dependency update starts -# importing a post-Windows-7 API. packaging/windows/windows7.ps1 does the work: it is the only build -# that uses the patched vendor/windows-link. A modern Windows runner proves compilation and host -# smoke tests only: run the checklist in docs/windows7.md on actual Windows 7 before claiming support. -name: Windows 7 x64 compatibility - -on: - pull_request: - paths: - - "Cargo.toml" - - "Cargo.lock" - - "apps/vectorcraft/Cargo.toml" - - "apps/vectorcraft/src/printing.rs" - - "apps/vectorcraft/src/printing/**" - - "crates/ui-egui/src/print.rs" - - "crates/ui-egui/src/tests/printps.rs" - - "packaging/windows/windows7.ps1" - - ".github/workflows/windows7.yml" - schedule: - - cron: "23 5 * * 1" # weekly - workflow_dispatch: - -permissions: - contents: read - -env: - CARGO_TERM_COLOR: always - CARGO_INCREMENTAL: 0 - -jobs: - build: - runs-on: windows-2022 - timeout-minutes: 90 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0 - with: - arch: x64 - - uses: dtolnay/rust-toolchain@e2a55d2ffb04f378e9626c28d38b36d230d1e12f # master - with: - toolchain: nightly-2026-10-01 - components: rust-src - - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 - with: - key: windows7-x64 - - name: Build, check and test the portable package - shell: pwsh - run: ./packaging/windows/windows7.ps1 -Test - - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 - with: - name: vectorcraft-windows7-x64-portable - path: dist/release/vectorcraft-windows7-x64-portable.zip - if-no-files-found: error diff --git a/AGENTS.md b/AGENTS.md index 61f141079e..1677f01c52 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -86,3 +86,8 @@ When a task lands, update in the same PR: the area row in `docs/target-app-parit GitHub profiles). Never hand-edit `contributors.json`. - Maintainers refresh the stats with `python3 ../../craftrules/scripts/contributors.py .` (it also re-verifies who wrote each `people.toml` entry; `--check` only verifies). + +## CI workflows +- Workflows in `.github/workflows/` call the shared workflows in [`storytold/craft-common-actions`](https://github.com/storytold/craft-common-actions) at `@main`, with their inputs and secrets. No other jobs: if no shared workflow does what's needed, it doesn't go here. +- Don't add or change CI in a feature or fix PR. If the existing workflows can't build or test a change, link an existing craft-common-actions issue or PR, or open a new issue, or a PR only for a small fix that is safe for every app and does not increase CI time. +- Some workflow files here still run their own jobs. Don't add to them. Moving a job into craft-common-actions is its own task, never part of another PR, and the job stays here until a shared workflow runs it. diff --git a/CLAUDE.md b/CLAUDE.md index cbf9de1298..2b6ffcc436 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -35,6 +35,7 @@ Formerly **DrawCraft** (renamed 2026-10-01): old `.drawcraft` files and `"format - **Never crash: no panics in shipped code.** A crash loses the user's unsaved work. Files, pasted data, MCP and control messages, and command params are all untrusted input. - Fallible work returns `Result` (or `Option`); callers handle it or propagate it with `?`. Failures reach the user as an error message (`EngineError` for commands, the crate's own error type or `String` below the engine), never as an abort. - Workspace clippy lints ban these outside tests, so `cargo xtask ci` fails on them: `unwrap()`, `expect()`, `panic!`, `unreachable!`, `todo!`, `unimplemented!`. Use instead: `?`, `.ok_or(…)?`, `let … else { return … }`, `if let`, `unwrap_or` / `unwrap_or_default` / `unwrap_or_else`, `.first()` / `.last()` / `.split_last()`, and a real error for a match arm you believe is impossible. + - New workspace crates must include `[lints] workspace = true` in `Cargo.toml` to inherit `[workspace.lints]`. Production crate roots must declare `#![forbid(unsafe_code)]` before modules or other crate items; crate-level `//!` documentation may precede it. - Don't panic implicitly either: use `.get(i)` instead of `v[i]` or `&s[a..b]` when the index comes from data. Don't divide integers by something that can be zero, cap sizes and counts read from input (allocations, images, loops), and check that floats are finite before casting them or looping on them. - Don't silence errors just to satisfy the lint: use `let _ =` or `.ok()` only where ignoring the failure is the right behaviour, with a comment saying why. - Tests may unwrap and panic (`#[test]`, `#[cfg(test)]`, `tests/`, `testkit`). Integration-test files start with an `#![allow(…)]` for that. @@ -68,3 +69,8 @@ The progress docs follow craftrules' [`standards/progress-docs.md`](https://gith When a task lands, update in the same PR: the area row in `docs/target-app-parity.md` (score, missing items, hours) and its evidence list, the gap entry in `docs/gaps.md` if it closed or shrank, the checklist it belongs to, the milestone row in `docs/roadmap.md`, and the ROADMAP's tables and progress log when a headline number moves. Update each touched doc's status line and revision history. - Grade by behaviour against the public documentation (`plan/illustrator/` notes), not by whether a menu item exists. Scores are self-assessed, so err low; mark each number measured or estimated. - Keep the README's Status section in step with the ROADMAP headline. + +## CI workflows +- Workflows in `.github/workflows/` call the shared workflows in [`storytold/craft-common-actions`](https://github.com/storytold/craft-common-actions) at `@main`, with their inputs and secrets. No other jobs: if no shared workflow does what's needed, it doesn't go here. +- Don't add or change CI in a feature or fix PR. If the existing workflows can't build or test a change, link an existing craft-common-actions issue or PR, or open a new issue, or a PR only for a small fix that is safe for every app and does not increase CI time. +- Some workflow files here still run their own jobs. Don't add to them. Moving a job into craft-common-actions is its own task, never part of another PR, and the job stays here until a shared workflow runs it. diff --git a/Cargo.lock b/Cargo.lock index d329c26a7d..f0f4dfd948 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -303,7 +303,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 1.1.5", + "rustix 1.1.6", "slab", "windows-sys 0.61.2", ] @@ -334,7 +334,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix 1.1.5", + "rustix 1.1.6", ] [[package]] @@ -360,7 +360,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 1.1.5", + "rustix 1.1.6", "signal-hook-registry", "slab", "windows-sys 0.61.2", @@ -665,7 +665,7 @@ checksum = "43b32313e937c8721c9d17ee9bf60699a6402b4b7c2cb6ac1bee0ffcaa4b4e5e" dependencies = [ "bitflags 2.13.2", "polling", - "rustix 1.1.5", + "rustix 1.1.6", "tracing", ] @@ -688,7 +688,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" dependencies = [ "calloop 0.14.5", - "rustix 1.1.5", + "rustix 1.1.6", "wayland-backend", "wayland-client", ] @@ -763,9 +763,9 @@ dependencies = [ [[package]] name = "clipboard-win" -version = "5.4.1" +version = "5.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +checksum = "052af7878466feccda12ba7a6a67d019d712f296809559b47c12c8cc196d3a9a" dependencies = [ "error-code", ] @@ -1017,7 +1017,7 @@ source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13af dependencies = [ "craft-winprint-gdi", "miniz_oxide 0.9.1", - "roxmltree 0.21.1", + "roxmltree 0.21.2", "tempfile", "winprint", "winsafe", @@ -1047,13 +1047,14 @@ dependencies = [ [[package]] name = "craft-update" version = "0.1.0" -source = "git+https://github.com/storytold/craft-libs?rev=92d392cff132eaf53e13afff77f38b5c11b73aa0#92d392cff132eaf53e13afff77f38b5c11b73aa0" +source = "git+https://github.com/storytold/craft-libs?rev=04aa1703b32d299e54f78fcde4182f2b393a9116#04aa1703b32d299e54f78fcde4182f2b393a9116" dependencies = [ "chrono", "dunce", "egui", "flate2", "getrandom 0.3.4", + "image", "log", "plist", "semver", @@ -1310,7 +1311,6 @@ dependencies = [ "egui-wgpu", "egui-winit", "egui_glow", - "glow", "glutin", "glutin-winit", "image", @@ -1888,7 +1888,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ - "rustix 1.1.5", + "rustix 1.1.6", "windows-link", ] @@ -3283,6 +3283,7 @@ dependencies = [ "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", ] [[package]] @@ -3899,7 +3900,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.1.5", + "rustix 1.1.6", "windows-sys 0.61.2", ] @@ -4445,9 +4446,9 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "roxmltree" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" +checksum = "9da7f59328b09405b181fee1018925fad50f157afb43c0c0f5c522c256dd5907" dependencies = [ "memchr", ] @@ -4488,9 +4489,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" +checksum = "e5e76742e5294153d4dde1207dbec5bdd81eef6fabdc0d2118babadabfd858aa" dependencies = [ "bitflags 2.13.2", "errno", @@ -5096,9 +5097,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.16.3" +version = "1.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" +checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8" dependencies = [ "serde", ] @@ -5141,7 +5142,7 @@ dependencies = [ "libc", "log", "memmap2", - "rustix 1.1.5", + "rustix 1.1.6", "thiserror 2.0.21", "wayland-backend", "wayland-client", @@ -5416,7 +5417,7 @@ dependencies = [ "fastrand", "getrandom 0.4.3", "once_cell", - "rustix 1.1.5", + "rustix 1.1.6", "windows-sys 0.61.2", ] @@ -5992,7 +5993,7 @@ dependencies = [ "kurbo 0.13.1", "log", "pico-args", - "roxmltree 0.21.1", + "roxmltree 0.21.2", "simplecss", "siphasher", "skrifa 0.44.0", @@ -6036,7 +6037,7 @@ dependencies = [ [[package]] name = "vectorcraft" -version = "0.17.0" +version = "0.18.0" dependencies = [ "arboard", "block2 0.6.2", @@ -6053,12 +6054,15 @@ dependencies = [ "objc2 0.6.5", "objc2-app-kit 0.3.2", "objc2-core-foundation", + "objc2-core-graphics", "objc2-core-text", "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", "pollster 1.0.1", "raw-window-handle", "rfd", "serde_json", + "smithay-clipboard", "socket2", "vectorcraft-engine", "vectorcraft-text", @@ -6077,7 +6081,7 @@ dependencies = [ [[package]] name = "vectorcraft-affinity" -version = "0.17.0" +version = "0.18.0" dependencies = [ "crc32fast", "flate2", @@ -6088,7 +6092,7 @@ dependencies = [ [[package]] name = "vectorcraft-brush" -version = "0.17.0" +version = "0.18.0" dependencies = [ "kurbo 0.13.1", "serde", @@ -6100,7 +6104,7 @@ dependencies = [ [[package]] name = "vectorcraft-cad" -version = "0.17.0" +version = "0.18.0" dependencies = [ "image", "kurbo 0.13.1", @@ -6116,7 +6120,7 @@ dependencies = [ [[package]] name = "vectorcraft-cli" -version = "0.17.0" +version = "0.18.0" dependencies = [ "log", "objc2-core-foundation", @@ -6138,7 +6142,7 @@ dependencies = [ [[package]] name = "vectorcraft-color" -version = "0.17.0" +version = "0.18.0" dependencies = [ "kurbo 0.13.1", "moxcms", @@ -6150,7 +6154,7 @@ dependencies = [ [[package]] name = "vectorcraft-doc" -version = "0.17.0" +version = "0.18.0" dependencies = [ "chrono", "hayro-ccitt", @@ -6169,7 +6173,7 @@ dependencies = [ [[package]] name = "vectorcraft-effects" -version = "0.17.0" +version = "0.18.0" dependencies = [ "image", "kurbo 0.13.1", @@ -6185,7 +6189,7 @@ dependencies = [ [[package]] name = "vectorcraft-engine" -version = "0.17.0" +version = "0.18.0" dependencies = [ "craft-docpath", "crc32fast", @@ -6194,8 +6198,8 @@ dependencies = [ "kurbo 0.13.1", "log", "proptest", - "roxmltree 0.21.1", - "rustix 1.1.5", + "roxmltree 0.21.2", + "rustix 1.1.6", "ruzstd", "serde", "serde_json", @@ -6224,7 +6228,7 @@ dependencies = [ [[package]] name = "vectorcraft-eps" -version = "0.17.0" +version = "0.18.0" dependencies = [ "fax", "flate2", @@ -6248,11 +6252,11 @@ dependencies = [ [[package]] name = "vectorcraft-format" -version = "0.17.0" +version = "0.18.0" dependencies = [ "flate2", "proptest", - "rustix 1.1.5", + "rustix 1.1.6", "serde", "serde_json", "thiserror 2.0.21", @@ -6263,7 +6267,7 @@ dependencies = [ [[package]] name = "vectorcraft-geom" -version = "0.17.0" +version = "0.18.0" dependencies = [ "kurbo 0.13.1", "proptest", @@ -6275,7 +6279,7 @@ dependencies = [ [[package]] name = "vectorcraft-mcp" -version = "0.17.0" +version = "0.18.0" dependencies = [ "image", "log", @@ -6294,7 +6298,7 @@ dependencies = [ [[package]] name = "vectorcraft-metafile" -version = "0.17.0" +version = "0.18.0" dependencies = [ "image", "kurbo 0.13.1", @@ -6308,7 +6312,7 @@ dependencies = [ [[package]] name = "vectorcraft-pathops" -version = "0.17.0" +version = "0.18.0" dependencies = [ "kurbo 0.13.1", "linesweeper", @@ -6320,19 +6324,20 @@ dependencies = [ [[package]] name = "vectorcraft-pdf" -version = "0.17.0" +version = "0.18.0" dependencies = [ "aes", "chrono", "craft-pdf", "flate2", + "getrandom 0.4.3", "image", "jpeg-encoder", "krilla", "kurbo 0.13.1", "log", "md-5", - "roxmltree 0.21.1", + "roxmltree 0.21.2", "serde", "serde_json", "sha2", @@ -6351,7 +6356,7 @@ dependencies = [ [[package]] name = "vectorcraft-plugins" -version = "0.17.0" +version = "0.18.0" dependencies = [ "serde", "serde_json", @@ -6365,7 +6370,7 @@ dependencies = [ [[package]] name = "vectorcraft-render" -version = "0.17.0" +version = "0.18.0" dependencies = [ "flate2", "gif", @@ -6394,14 +6399,14 @@ dependencies = [ [[package]] name = "vectorcraft-scriptcheck" -version = "0.17.0" +version = "0.18.0" dependencies = [ "proptest", ] [[package]] name = "vectorcraft-svg" -version = "0.17.0" +version = "0.18.0" dependencies = [ "craft-docpath", "flate2", @@ -6426,7 +6431,7 @@ dependencies = [ [[package]] name = "vectorcraft-testkit" -version = "0.17.0" +version = "0.18.0" dependencies = [ "fax", "image", @@ -6444,7 +6449,7 @@ dependencies = [ [[package]] name = "vectorcraft-text" -version = "0.17.0" +version = "0.18.0" dependencies = [ "harfrust 0.13.3", "icu_segmenter", @@ -6465,7 +6470,7 @@ dependencies = [ [[package]] name = "vectorcraft-three-d" -version = "0.17.0" +version = "0.18.0" dependencies = [ "vectorcraft-geom", "vectorcraft-pathops", @@ -6473,7 +6478,7 @@ dependencies = [ [[package]] name = "vectorcraft-tools" -version = "0.17.0" +version = "0.18.0" dependencies = [ "image", "kurbo 0.13.1", @@ -6489,7 +6494,7 @@ dependencies = [ [[package]] name = "vectorcraft-trace" -version = "0.17.0" +version = "0.18.0" dependencies = [ "image", "kurbo 0.13.1", @@ -6501,7 +6506,7 @@ dependencies = [ [[package]] name = "vectorcraft-ui-egui" -version = "0.17.0" +version = "0.18.0" dependencies = [ "bytemuck", "craft-keymap", @@ -6512,7 +6517,7 @@ dependencies = [ "egui_kittest", "image", "log", - "rustix 1.1.5", + "rustix 1.1.6", "serde", "serde_json", "toml", @@ -6539,7 +6544,7 @@ dependencies = [ [[package]] name = "vectorcraft-web" -version = "0.17.0" +version = "0.18.0" dependencies = [ "eframe", "egui", @@ -6805,7 +6810,7 @@ checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078" dependencies = [ "cc", "downcast-rs", - "rustix 1.1.5", + "rustix 1.1.6", "scoped-tls", "smallvec", "wayland-sys", @@ -6818,7 +6823,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073" dependencies = [ "bitflags 2.13.2", - "rustix 1.1.5", + "rustix 1.1.6", "wayland-backend", "wayland-scanner", ] @@ -6840,7 +6845,7 @@ version = "0.31.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" dependencies = [ - "rustix 1.1.5", + "rustix 1.1.6", "wayland-client", "xcursor", ] @@ -7515,7 +7520,7 @@ dependencies = [ "libc", "log", "os_pipe", - "rustix 1.1.5", + "rustix 1.1.6", "thiserror 2.0.21", "tree_magic_mini", "wayland-backend", @@ -7574,7 +7579,7 @@ dependencies = [ "libc", "libloading", "once_cell", - "rustix 1.1.5", + "rustix 1.1.6", "x11rb-protocol", ] @@ -7616,7 +7621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ "libc", - "rustix 1.1.5", + "rustix 1.1.6", ] [[package]] @@ -7670,7 +7675,7 @@ checksum = "9440ea3e5aeabb0ac63af70daf835274065238cdd0cec83418f417eae38bacee" [[package]] name = "xtask" -version = "0.17.0" +version = "0.18.0" dependencies = [ "serde_json", ] @@ -7720,7 +7725,7 @@ dependencies = [ "hex", "libc", "ordered-stream", - "rustix 1.1.5", + "rustix 1.1.6", "serde", "serde_repr", "tracing", @@ -7904,9 +7909,9 @@ dependencies = [ [[package]] name = "zip" -version = "9.1.0" +version = "9.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886b00003a72c4ccee3f42cfc34908d33b2508a74d827dc2a4244706475c704" +checksum = "1156b39d142a34b999c2310a50455e0cb3760bf7efad0122f796d813bee26ccb" dependencies = [ "crc32fast", "flate2", diff --git a/Cargo.toml b/Cargo.toml index 644bffe9d5..4b14fbb371 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ members = ["crates/*", "apps/*", "xtask"] default-members = ["crates/*", "apps/vectorcraft", "apps/vectorcraft-cli", "xtask"] [workspace.package] -version = "0.17.0" +version = "0.18.0" edition = "2024" license = "MIT OR Apache-2.0" rust-version = "1.95" @@ -19,7 +19,8 @@ craft-keymap = { git = "https://github.com/storytold/craft-libs", rev = "92d392c # Decides who draws the window frame (desktop vs. app) so the suite shares one native titlebar per # desktop. The `egui` feature adds `craft_window::decorate`. (craft-libs#217.) craft-window = { git = "https://github.com/storytold/craft-libs", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", features = ["egui"] } -craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "92d392cff132eaf53e13afff77f38b5c11b73aa0", features = ["egui"] } +# craft-update: app-specific pretend versions apply to startup and manual checks. +craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "04aa1703b32d299e54f78fcde4182f2b393a9116", features = ["egui"] } # internal vectorcraft-geom = { path = "crates/geom" } vectorcraft-color = { path = "crates/color" } @@ -116,6 +117,9 @@ webbrowser = "1" unsafe_code = "deny" [workspace.lints.clippy] +cognitive_complexity = "deny" +too_many_arguments = "deny" +wildcard_imports = "deny" dbg_macro = "warn" # VectorCraft never crashes: shipped code returns `Result`/`Option` instead of panicking (see # AGENTS.md › Robustness). Tests may unwrap (clippy.toml). diff --git a/NOTICE b/NOTICE index 8698b2dfee..0d0758820e 100644 --- a/NOTICE +++ b/NOTICE @@ -30,11 +30,6 @@ based in part on the work of the Independent JPEG Group. WebAssembly plug-ins run in the wasmi interpreter (MIT OR Apache-2.0); its wasmparser dependency is under the Apache License 2.0 with the LLVM exception. -The experimental Windows 7 build (packaging/windows/windows7.ps1) compiles a modified copy of the -windows-link crate 0.2.1 (vendor/windows-link; Microsoft, MIT OR Apache-2.0) that binds CoTaskMemFree -through ole32.dll. Its license texts are in vendor/windows-link and ship in that build's ZIP; every -other build uses the unmodified crate from crates.io. - Builds made with craft-fonts (the CRAFT_FONTS_DIR build option; all official releases) embed the fonts listed in https://github.com/storytold/craft-fonts/blob/main/ATTRIBUTION.md (BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC and Noto Sans Arabic in the current manifest; diff --git a/README.md b/README.md index d85cef156e..fd45eb9cdd 100644 --- a/README.md +++ b/README.md @@ -227,8 +227,6 @@ claude mcp add vectorcraft -- /path/to/vectorcraft-cli mcp --automation-read-roo The details are in [`docs/mcp.md`](docs/mcp.md) and [`docs/control-protocol.md`](docs/control-protocol.md). -For the experimental, unsupported 64-bit Windows 7 build, see [Windows 7 instructions](docs/windows7.md). - ## Status VectorCraft is under active development. [**ROADMAP.md**](ROADMAP.md) has the stage, the numbers by dimension @@ -249,7 +247,7 @@ doesn't write Affinity files. Current `.af` validation includes 33 pinned files, and every-board SVG/PDF/PSD export, with fixes for Affinity 3 artboards, source-backed JPEGs and text runs. [Scope and limits](crates/affinity/README.md); [source audit and remaining gaps](docs/affinity-validation.md). The interface -speaks English, German, Japanese, Korean, Vietnamese, Traditional and Simplified Chinese, Spanish, French, Hungarian, Italian, Polish, Brazilian Portuguese, Russian, Swedish, Ukrainian, Finnish, Bulgarian, Greek, Czech and Georgian; +speaks English, German, Japanese, Korean, Vietnamese, Traditional and Simplified Chinese, Spanish, French, Hungarian, Italian, Polish, Brazilian Portuguese, Russian, Swedish, Ukrainian, Finnish, Bulgarian, Greek, Czech, Georgian and Catalan; [localization-parity.md](docs/localization-parity.md) has the details. The scores are self-assessed from our code against public documentation; [target-app-parity.md](docs/target-app-parity.md#how-this-was-measured-2026-10-10) explains how far to trust them. diff --git a/ROADMAP.md b/ROADMAP.md index 28c15f3268..c8fa604074 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -110,9 +110,10 @@ Weights, evidence and the missing items per area: [target-app-parity.md](docs/ta | Japanese | `ja` | full | 100% | | German | `de` | full | 98% | | Korean | `ko` | full | 100% | +| Bosnian | `bs` | full (AI-assisted, native review pending) | 100% | | Vietnamese | `vi` | full | 100% | -Also shipped: Traditional Chinese (partial, 80%), Bulgarian, Finnish, Greek, Hungarian, Italian, Polish, Russian, Swedish, Ukrainian and Georgian (full), Czech (catalog complete; render-site gaps reduced; native review pending). +Also shipped: Traditional Chinese (partial, 80%), Bulgarian, Finnish, Greek, Hungarian, Italian, Polish, Russian, Swedish, Ukrainian and Georgian (full), Catalan (full; AI-drafted, native review welcome), Czech (catalog complete; render-site gaps reduced; native review pending). The Georgian catalog has had a native speaker's review; no other catalog has. Details: [localization-parity.md](docs/localization-parity.md). ## Upcoming @@ -128,6 +129,7 @@ Ranked; detail in [docs/roadmap.md](docs/roadmap.md) and [docs/gaps.md](docs/gap ## Progress log +- 2026-10-30: Gradient panel presets: a Presets menu applies one of the built-in gradient libraries' presets (grouped by theme) or a saved one to the gradient behind the active proxy, keeping its placement, as one undo step; the panel menu saves the current gradient as a preset, kept with the preferences, and deletes saved ones (`gradient.presets.list/apply/save/delete`). - 2026-10-23: Pathfinder › Trap: the Pathfinder panel's Trap… and Effect › Pathfinder › Trap… add a thin overprinting strip of the lighter colour inside the darker one where two solid colours meet, so misregistered plates leave no gap ([trapping](https://helpx.adobe.com/illustrator/using/trapping.html)), with Thickness, Height/Width, Tint Reduction, Traps with Process Color and Reverse Traps. Illustrator effects 49/50 (SVG Filters remains); Live vector effects hours 6–10 → 4–8; headline numbers unchanged. - 2026-10-23: OpenType SVG colour fonts draw as vector art: each glyph's SVG document is read by the SVG importer, in the text's colours where it asks for them, and draws, exports and outlines as COLR glyphs do; all colour font formats are now drawn. - 2026-10-23: COLR colour fonts (v0 and v1, as in Noto Color Emoji's vector build, Segoe UI Emoji, Nabla) draw as vector art: their layers' colours, gradients, clips and blend modes on the canvas and in SVG, EPS, legacy `.ai` and PDF, and as editable paths with Create Outlines; glyphs using sweep gradients or compositing modes are drawn as images of the glyph. @@ -144,6 +146,8 @@ Ranked; detail in [docs/roadmap.md](docs/roadmap.md) and [docs/gaps.md](docs/gap | Date | What landed | |---|---| +| 2026-10-29 | Linux: the Flatpak on GNOME copies and pastes text with other apps through the compositor's core Wayland protocol (smithay-clipboard on the window's connection), where arboard has neither data-control nor X11. Scores and estimates unchanged. | +| 2026-10-29 | Linux: a clipboard that can't be opened (the Flatpak on GNOME, where arboard has neither data-control nor X11) is retried after 1 s, doubling to a minute, and logged once, where the background Paste check used to reconnect and log a warning every 250 ms (267k warnings in 18 h). Scores and estimates unchanged. | | 2026-10-21 | 3D lighting: four original lighting presets shared by Revolve, Extrude & Bevel, Inflate and Rotate, with command access and manual Custom settings; scores and estimates unchanged. | | 2026-10-21 | Print: the Print dialog's Media list has the Japanese papers — JIS B4 (257 × 364 mm), JIS B5 (182 × 257 mm) and JIS B6 next to the ISO B4/B5 (which keep their ISO 216 dimensions for settings saved before them), はがき 100 × 148 mm, 往復はがき 148 × 200 mm, 封筒 長形3号 120 × 235 mm / 長形4号 90 × 225 mm / 角形2号 240 × 332 mm / 角形3号 216 × 277 mm and 四六判 127 × 188 mm. A paper is named by its JSON id or its dialog label, any case and spacing (`B5 (JIS)` or `b5jis`), and every added size prints as its own PDF page size. Headline numbers unchanged. | | 2026-10-18 | Inch rulers divide the inch in half, not in ten. Zooming in stepped an inch ruler through 0.2, 0.1 and 0.05 of an inch, marks an inch ruler does not carry; below an inch the steps are binary fractions now, 1/2 down to 1/16, with the unlabelled ticks between them binary too (down to 1/128, so the 32nds, 64ths and 128ths appear as the report expects), while the metric units, points, pixels and picas keep their tenths. A label carries the decimals its step needs, so a sixteenth reads 0.0625 rather than the rounded 0.06, with trailing zeros dropped (#2827). Headline numbers unchanged. | @@ -198,6 +202,8 @@ The full pre-standard record of what landed is kept in [docs/roadmap.md](docs/ro | Date | Change | Summary | |---|---|---| +| 2026-10-29 | minor | Progress log row for the Linux Flatpak clipboard's core Wayland protocol text fallback; headline numbers unchanged. | +| 2026-10-29 | minor | Progress log row for the Linux clipboard retry backoff; headline numbers unchanged. | | 2026-10-23 | minor | Progress log row for Pathfinder › Trap; Live vector effects hours 6–10 → 4–8; headline numbers unchanged. | | 2026-10-23 | minor | Progress log row for OpenType SVG colour fonts; headline numbers unchanged. | | 2026-10-23 | minor | Progress log row for COLR colour fonts; headline numbers unchanged. | diff --git a/apps/vectorcraft-cli/Cargo.toml b/apps/vectorcraft-cli/Cargo.toml index 82f0b100d0..cb9183315f 100644 --- a/apps/vectorcraft-cli/Cargo.toml +++ b/apps/vectorcraft-cli/Cargo.toml @@ -35,7 +35,6 @@ winsafe = { version = "0.0.29", default-features = false, features = ["kernel", # DirectWrite's system font collection and GDI's font list (the desktop app's src/system_fonts.rs, # shared): fonts that font services such as Adobe Fonts load in place (#579, #1258). MIT OR # Apache-2.0. -[target.'cfg(all(windows, not(target_vendor = "win7")))'.dependencies] windows = { version = "0.62", features = ["Win32_Graphics_DirectWrite", "Win32_Graphics_Gdi"] } # CoreText's font manager (the desktop app's src/mac_fonts.rs, shared): fonts that apps and font diff --git a/apps/vectorcraft-cli/src/main.rs b/apps/vectorcraft-cli/src/main.rs index 6ec320ea26..bba1976c1d 100644 --- a/apps/vectorcraft-cli/src/main.rs +++ b/apps/vectorcraft-cli/src/main.rs @@ -46,7 +46,7 @@ mod perf; #[path = "../../vectorcraft/src/prefs_dir.rs"] mod prefs_dir; /// The desktop app's DirectWrite font lister, shared: exports and MCP find the same fonts. -#[cfg(all(windows, not(target_vendor = "win7")))] +#[cfg(windows)] #[path = "../../vectorcraft/src/system_fonts.rs"] mod system_fonts; @@ -162,7 +162,7 @@ fn help() -> Result<(), String> { fn main() -> ExitCode { // Before any font lookup: the fonts font services load (#579). - #[cfg(all(windows, not(target_vendor = "win7")))] + #[cfg(windows)] system_fonts::install(); #[cfg(target_os = "macos")] mac_fonts::install(); diff --git a/apps/vectorcraft-cli/src/perf.rs b/apps/vectorcraft-cli/src/perf.rs index 8fe3d23b74..ac3a9cf3ae 100644 --- a/apps/vectorcraft-cli/src/perf.rs +++ b/apps/vectorcraft-cli/src/perf.rs @@ -116,7 +116,7 @@ fn edit_drags(n: usize) -> Result<(f64, f64), String> { let view = vectorcraft_engine::ViewInfo { zoom: 8.0, ..Default::default() }; let err = |e: vectorcraft_engine::EngineError| e.to_string(); let mut drag = |down: Point| -> Result { - let ev = |kind, y: f64| PointerEvent { kind, pos: down + Vec2::new(0.0, y), mods: Mods::default(), pressure: 1.0 }; + let ev = |kind, y: f64| PointerEvent { kind, pos: down + Vec2::new(0.0, y), mods: Mods::default(), backquote: false, pressure: 1.0 }; s.execute("select.anchors", &json!({"id": id.0, "anchors": [[0, 400]]})).map_err(err)?; s.select_tool("directSelection", view).map_err(err)?; let undo = s.doc().map_err(err)?.history.undo.len(); @@ -215,7 +215,7 @@ fn deep(n: usize) -> Result<(), String> { let first = s.doc().map_err(err)?.selection.objects.first().copied(); let at = first.and_then(|id| s.doc().ok()?.doc.node(id)?.geometric_bounds()).map(|b| b.center()); if let Some(down) = at { - let ev = |kind, dx: f64| PointerEvent { kind, pos: down + Vec2::new(dx, 0.0), mods: Mods::default(), pressure: 1.0 }; + let ev = |kind, dx: f64| PointerEvent { kind, pos: down + Vec2::new(dx, 0.0), mods: Mods::default(), backquote: false, pressure: 1.0 }; s.select_tool("selection", view).map_err(err)?; s.pointer(&ev(PointerKind::Down, 0.0), view).map_err(err)?; let mut dx = 0.0; diff --git a/apps/vectorcraft-cli/tests/it/cli.rs b/apps/vectorcraft-cli/tests/it/cli.rs index c45c5d8676..9caf4bc92f 100644 --- a/apps/vectorcraft-cli/tests/it/cli.rs +++ b/apps/vectorcraft-cli/tests/it/cli.rs @@ -447,113 +447,158 @@ fn mcp_protocol_over_stdio() { let mut stdin = child.stdin.take().unwrap(); let mut lines = BufReader::new(child.stdout.take().unwrap()).lines(); let mut notes = vec![]; - - fn send(stdin: &mut std::process::ChildStdin, v: Value) { - writeln!(stdin, "{v}").unwrap(); + exercise_mcp_core(&mut stdin, &mut lines, &mut notes); + exercise_mcp_resource_errors(&mut stdin, &mut lines, &mut notes); + exercise_mcp_document_resources(&mut stdin, &mut lines, &mut notes); + exercise_mcp_logging(&mut stdin, &mut lines, &mut notes); + exercise_mcp_parse_error(&mut stdin, &mut lines, &mut notes); + drop(stdin); + for line in lines { + let v: Value = serde_json::from_str(&line.unwrap()).unwrap(); + assert_eq!(v["jsonrpc"], "2.0", "{v}"); + notes.push(v); } - // Next reply with this id; notifications (no id) are stashed for later. - fn recv(lines: &mut std::io::Lines>, notes: &mut Vec, id: u64) -> Value { - loop { - let line = lines.next().unwrap().unwrap(); - let v: Value = serde_json::from_str(&line).unwrap(); - assert_eq!(v["jsonrpc"], "2.0", "{v}"); - if v.get("id").and_then(|i| i.as_u64()) == Some(id) { - return v; - } - notes.push(v); + let out = child.wait_with_output().unwrap(); + assert!(out.status.success()); + assert!(!String::from_utf8_lossy(&out.stderr).contains("panicked")); + let messages = notes.iter().filter(|n| n["method"] == "notifications/message").count(); + assert!(messages > 0, "no log records after logging/setLevel: {notes:?}"); +} + +fn mcp_send(stdin: &mut std::process::ChildStdin, v: Value) { + writeln!(stdin, "{v}").unwrap(); +} + +fn mcp_recv(lines: &mut std::io::Lines>, notes: &mut Vec, id: u64) -> Value { + loop { + let line = lines.next().unwrap().unwrap(); + let v: Value = serde_json::from_str(&line).unwrap(); + assert_eq!(v["jsonrpc"], "2.0", "{v}"); + if v.get("id").and_then(|i| i.as_u64()) == Some(id) { + return v; } + notes.push(v); } +} - send( - &mut stdin, +fn exercise_mcp_core( + stdin: &mut std::process::ChildStdin, + lines: &mut std::io::Lines>, + notes: &mut Vec, +) { + mcp_send( + stdin, json!({"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"0"}}}), ); - let r = recv(&mut lines, &mut notes, 1); + let r = mcp_recv(lines, notes, 1); assert_eq!(r["result"]["serverInfo"]["name"], "vectorcraft"); assert!(r["result"]["capabilities"]["prompts"].is_object(), "{r}"); - send(&mut stdin, json!({"jsonrpc":"2.0","method":"notifications/initialized"})); + mcp_send(stdin, json!({"jsonrpc":"2.0","method":"notifications/initialized"})); - send(&mut stdin, json!({"jsonrpc":"2.0","id":2,"method":"prompts/list","params":{}})); - let r = recv(&mut lines, &mut notes, 2); + mcp_send(stdin, json!({"jsonrpc":"2.0","id":2,"method":"prompts/list","params":{}})); + let r = mcp_recv(lines, notes, 2); assert!(r["result"]["prompts"].as_array().is_some_and(|p| !p.is_empty()), "{r}"); - send(&mut stdin, json!({"jsonrpc":"2.0","id":3,"method":"prompts/get","params":{"name":"poster","arguments":{"brief":"a jazz festival"}}})); - let r = recv(&mut lines, &mut notes, 3); + mcp_send(stdin, json!({"jsonrpc":"2.0","id":3,"method":"prompts/get","params":{"name":"poster","arguments":{"brief":"a jazz festival"}}})); + let r = mcp_recv(lines, notes, 3); assert!(r["result"]["messages"][0]["content"]["text"].as_str().is_some_and(|t| t.contains("a jazz festival")), "{r}"); - send( - &mut stdin, + mcp_send( + stdin, json!({"jsonrpc":"2.0","id":4,"method":"completion/complete","params":{"ref":{"type":"ref/prompt","name":"export-set"},"argument":{"name":"formats","value":"sv"}}}), ); - let r = recv(&mut lines, &mut notes, 4); + let r = mcp_recv(lines, notes, 4); assert!(r["result"]["completion"]["values"].as_array().is_some_and(|v| v.contains(&json!("svg"))), "{r}"); - send(&mut stdin, json!({"jsonrpc":"2.0","id":5,"method":"resources/list","params":{}})); - assert!(recv(&mut lines, &mut notes, 5)["result"]["resources"].as_array().is_some_and(|v| v.len() == 3)); - send(&mut stdin, json!({"jsonrpc":"2.0","id":6,"method":"resources/templates/list","params":{}})); - assert!(recv(&mut lines, &mut notes, 6)["result"]["resourceTemplates"].as_array().is_some_and(|v| v.len() >= 4)); + mcp_send(stdin, json!({"jsonrpc":"2.0","id":5,"method":"resources/list","params":{}})); + assert!(mcp_recv(lines, notes, 5)["result"]["resources"].as_array().is_some_and(|v| v.len() == 3)); + mcp_send(stdin, json!({"jsonrpc":"2.0","id":6,"method":"resources/templates/list","params":{}})); + assert!(mcp_recv(lines, notes, 6)["result"]["resourceTemplates"].as_array().is_some_and(|v| v.len() >= 4)); - send( - &mut stdin, + mcp_send( + stdin, json!({"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"draw_shape","arguments":{"shape":"rectangle","x":10,"y":10,"width":60,"height":40}}}), ); - let r = recv(&mut lines, &mut notes, 7); + let r = mcp_recv(lines, notes, 7); assert_eq!(r["result"]["isError"], false, "{r}"); let id: i64 = serde_json::from_str::(r["result"]["content"][0]["text"].as_str().unwrap()).unwrap()["id"].as_i64().unwrap(); - send(&mut stdin, json!({"jsonrpc":"2.0","id":8,"method":"resources/read","params":{"uri": format!("vectorcraft://object/{id}")}})); - let r = recv(&mut lines, &mut notes, 8); + mcp_send(stdin, json!({"jsonrpc":"2.0","id":8,"method":"resources/read","params":{"uri": format!("vectorcraft://object/{id}")}})); + let r = mcp_recv(lines, notes, 8); let body: Value = serde_json::from_str(r["result"]["contents"][0]["text"].as_str().unwrap()).unwrap(); assert_eq!(body["id"].as_i64(), Some(id), "{body}"); +} +fn exercise_mcp_resource_errors( + stdin: &mut std::process::ChildStdin, + lines: &mut std::io::Lines>, + notes: &mut Vec, +) { // Unknown resources (-32002) and bad template values (-32602) stay apart. - send(&mut stdin, json!({"jsonrpc":"2.0","id":9,"method":"resources/read","params":{"uri":"vectorcraft://nope" }})); - assert_eq!(recv(&mut lines, &mut notes, 9)["error"]["code"], -32002); - send(&mut stdin, json!({"jsonrpc":"2.0","id":10,"method":"resources/read","params":{"uri":"vectorcraft://object/"}})); - assert_eq!(recv(&mut lines, &mut notes, 10)["error"]["code"], -32602); + mcp_send(stdin, json!({"jsonrpc":"2.0","id":9,"method":"resources/read","params":{"uri":"vectorcraft://nope" }})); + assert_eq!(mcp_recv(lines, notes, 9)["error"]["code"], -32002); + mcp_send(stdin, json!({"jsonrpc":"2.0","id":10,"method":"resources/read","params":{"uri":"vectorcraft://object/"}})); + assert_eq!(mcp_recv(lines, notes, 10)["error"]["code"], -32602); +} +fn exercise_mcp_document_resources( + stdin: &mut std::process::ChildStdin, + lines: &mut std::io::Lines>, + notes: &mut Vec, +) { // Large containers slice through run_command document.node: a truncated level reports // childCount, and a bad limit is a tool error, not a crash or a full read. - send( - &mut stdin, + mcp_send( + stdin, json!({"jsonrpc":"2.0","id":20,"method":"tools/call","params":{"name":"draw_shape","arguments":{"shape":"ellipse","x":90,"y":10,"width":30,"height":30}}}), ); - assert_eq!(recv(&mut lines, &mut notes, 20)["result"]["isError"], false); - let run = |id: u64, command: &str, params: Value| json!({"jsonrpc":"2.0","id":id,"method":"tools/call","params":{"name":"run_command","arguments":{"command":command,"params":params}}}); - send(&mut stdin, run(21, "select.all", json!({}))); - recv(&mut lines, &mut notes, 21); - send(&mut stdin, run(22, "object.group", json!({}))); - let r = recv(&mut lines, &mut notes, 22); + assert_eq!(mcp_recv(lines, notes, 20)["result"]["isError"], false); + mcp_send(stdin, run_command(21, "select.all", json!({}))); + mcp_recv(lines, notes, 21); + mcp_send(stdin, run_command(22, "object.group", json!({}))); + let r = mcp_recv(lines, notes, 22); let group = serde_json::from_str::(r["result"]["content"][0]["text"].as_str().unwrap()).unwrap()["id"].clone(); - send(&mut stdin, run(23, "document.node", json!({"id": group, "summary": true, "childLimit": 1}))); - let r = recv(&mut lines, &mut notes, 23); + mcp_send(stdin, run_command(23, "document.node", json!({"id": group, "summary": true, "childLimit": 1}))); + let r = mcp_recv(lines, notes, 23); let sliced: Value = serde_json::from_str(r["result"]["content"][0]["text"].as_str().unwrap()).unwrap(); assert_eq!(sliced["children"].as_array().map(Vec::len), Some(1), "{sliced}"); assert_eq!(sliced["childCount"], 2, "{sliced}"); - send(&mut stdin, run(24, "document.node", json!({"id": group, "summary": true, "depth": -1}))); - assert_eq!(recv(&mut lines, &mut notes, 24)["result"]["isError"], true); + mcp_send(stdin, run_command(24, "document.node", json!({"id": group, "summary": true, "depth": -1}))); + assert_eq!(mcp_recv(lines, notes, 24)["result"]["isError"], true); // Skeleton then drill: a depth-0 inspect is the layer skeleton with counts, and // document.find locates nodes without dumping the tree. - send(&mut stdin, run(25, "document.inspect", json!({"depth": 0}))); - let r = recv(&mut lines, &mut notes, 25); + mcp_send(stdin, run_command(25, "document.inspect", json!({"depth": 0}))); + let r = mcp_recv(lines, notes, 25); let skel: Value = serde_json::from_str(r["result"]["content"][0]["text"].as_str().unwrap()).unwrap(); assert!(skel["layers"][0].get("children").is_none(), "{skel}"); assert!(skel["artboards"].as_array().is_some_and(|a| !a.is_empty()), "{skel}"); - send(&mut stdin, run(26, "document.find", json!({"kind": "ellipse"}))); - let r = recv(&mut lines, &mut notes, 26); + mcp_send(stdin, run_command(26, "document.find", json!({"kind": "ellipse"}))); + let r = mcp_recv(lines, notes, 26); let found: Value = serde_json::from_str(r["result"]["content"][0]["text"].as_str().unwrap()).unwrap(); assert_eq!(found["total"], 1, "{found}"); assert!(found["matches"][0]["path"].as_array().is_some_and(|p| !p.is_empty()), "{found}"); - send(&mut stdin, run(27, "document.find", json!({}))); - assert_eq!(recv(&mut lines, &mut notes, 27)["result"]["isError"], true); + mcp_send(stdin, run_command(27, "document.find", json!({}))); + assert_eq!(mcp_recv(lines, notes, 27)["result"]["isError"], true); +} +fn exercise_mcp_logging( + stdin: &mut std::process::ChildStdin, + lines: &mut std::io::Lines>, + notes: &mut Vec, +) { // The binary installed its logger: records arrive as notifications/message, and only // after the client asked for them. - send(&mut stdin, json!({"jsonrpc":"2.0","id":11,"method":"logging/setLevel","params":{"level":"debug"}})); - assert!(recv(&mut lines, &mut notes, 11).get("error").is_none()); - send(&mut stdin, json!({"jsonrpc":"2.0","id":12,"method":"tools/list","params":{}})); - assert!(recv(&mut lines, &mut notes, 12)["result"]["tools"].as_array().is_some_and(|v| !v.is_empty())); + mcp_send(stdin, json!({"jsonrpc":"2.0","id":11,"method":"logging/setLevel","params":{"level":"debug"}})); + assert!(mcp_recv(lines, notes, 11).get("error").is_none()); + mcp_send(stdin, json!({"jsonrpc":"2.0","id":12,"method":"tools/list","params":{}})); + assert!(mcp_recv(lines, notes, 12)["result"]["tools"].as_array().is_some_and(|v| !v.is_empty())); +} +fn exercise_mcp_parse_error( + stdin: &mut std::process::ChildStdin, + lines: &mut std::io::Lines>, + notes: &mut Vec, +) { // Malformed input is a parse error, and the server keeps serving afterwards. stdin.write_all(b"{not json\n").unwrap(); let parse_err: Value = loop { @@ -566,21 +611,12 @@ fn mcp_protocol_over_stdio() { }; assert_eq!(parse_err["error"]["code"], -32700); assert!(parse_err["id"].is_null(), "{parse_err}"); - send(&mut stdin, json!({"jsonrpc":"2.0","id":13,"method":"ping","params":{}})); - assert!(recv(&mut lines, &mut notes, 13).get("error").is_none()); - drop(stdin); - // Drain whatever the debug window queued; every line off the protocol stream parses. - for line in lines.by_ref() { - let line = line.unwrap(); - let v: Value = serde_json::from_str(&line).unwrap(); - assert_eq!(v["jsonrpc"], "2.0", "{v}"); - notes.push(v); - } - let out = child.wait_with_output().unwrap(); - assert!(out.status.success()); - assert!(!String::from_utf8_lossy(&out.stderr).contains("panicked")); - let messages = notes.iter().filter(|n| n["method"] == "notifications/message").count(); - assert!(messages > 0, "no log records after logging/setLevel: {notes:?}"); + mcp_send(stdin, json!({"jsonrpc":"2.0","id":13,"method":"ping","params":{}})); + assert!(mcp_recv(lines, notes, 13).get("error").is_none()); +} + +fn run_command(id: u64, command: &str, params: Value) -> Value { + json!({"jsonrpc":"2.0","id":id,"method":"tools/call","params":{"name":"run_command","arguments":{"command":command,"params":params}}}) } /// `vectorcraft-cli commands | head -1` panicked with "failed printing to diff --git a/apps/vectorcraft-web/index.html b/apps/vectorcraft-web/index.html index 42eaaf74e0..2ccfc271b0 100644 --- a/apps/vectorcraft-web/index.html +++ b/apps/vectorcraft-web/index.html @@ -9,6 +9,13 @@ VectorCraft + + + + + + + x"#); + let t = only_text(&d); + assert_eq!(t.first_style().fill.color().map(|c| c.to_hex()).as_deref(), Some("#ff0000")); +} + #[test] fn css_selectors_reach_text() { let d = open( diff --git a/crates/svg/tests/it/main.rs b/crates/svg/tests/it/main.rs index 74033fe995..e769c6d987 100644 --- a/crates/svg/tests/it/main.rs +++ b/crates/svg/tests/it/main.rs @@ -2,6 +2,7 @@ // Unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness). #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] +mod basic_shapes; mod blend_export; mod colour_glyphs; mod container_export; diff --git a/crates/svg/tests/it/options.rs b/crates/svg/tests/it/options.rs index daf0268c46..94dba991f7 100644 --- a/crates/svg/tests/it/options.rs +++ b/crates/svg/tests/it/options.rs @@ -43,7 +43,7 @@ fn many_css_styles_preserve_first_use_numbers() { for (styling, definition_marker) in [(Styling::InternalCss, ".cls-"), (Styling::StyleEntities, " = svg.lines().filter(|line| line.contains(" = svg.lines().filter(|line| line.contains(" Document { fn hidden_layers_are_kept_hidden_when_asked() { let d = layered(); let plain = export(&d, &ExportOptions::default()); - assert!(!plain.contains("Notes") && plain.matches("