VectorCraft: An open-source, vector art tool written by scratch in pure Rust
This report summarizes scanner evidence for storytold/vectorcraft, a Rust project with 11,155 GitHub stars. SynthScan examined 776,709 lines of code across 2040 source files, recording 704 pattern matches distributed across 7 syntactic categories. The legacy adjusted pattern signal is 1.0 (Low pattern signal); it is not an authorship estimate.
Use the findings to ask whether maintainers can explain intent, verify dependencies, simplify unnecessary scaffolding, and focus review effort. Risk Payload v1 adds ownership, supply-chain, over-engineering, and review-effort vectors when available. These are prompts for human verification, not probabilities, security findings, or measured maintenance costs.
Use each vector to guide manual verification of the underlying findings.
Payload schema: 1.0.0 · Engine: 2.0.0 · Scoring: heuristic-density-v1 · Scan date: 2026-10-31.
Can maintainers explain intent, assumptions, and trade-offs? 674 finding(s).
Are imports declared and package/API names verified? 20 finding(s).
Are wrappers, guards, and repeated structures necessary? 10 finding(s).
Which changes need focused human review? 704 finding(s).
Run additional scans to build a history of the legacy pattern signal. Score changes are not a measure of AI adoption or code quality.
Groups findings by configured severity weight. Severity is not a confidence estimate, security rating, or proof that a package is missing. Verify imports against the project's manifests and inspect each finding in its local design context.
This horizontal bar chart shows where configured pattern scores accumulate across directories. Treat the bars as locations to inspect, not as evidence of authorship or code quality. Use the file-level findings to decide whether a directory needs a focused ownership, dependency, complexity, or review check.
The scanner identified 704 distinct pattern matches across 7 syntactic categories. Each entry below represents a discrete location in the source code where the engine recorded a detector match to inspect in context. Expand any category row to inspect the individual file paths, line numbers, code snippets, and the lexical context (CODE, COMMENT, or STRING) in which each match was detected.
Reading the findings table: The Severity column indicates the configured finding weight (CRITICAL / HIGH / MEDIUM / LOW). The Context column identifies whether the match occurred inside executable code, an inline comment, or a string literal — legacy comment-context matches receive a ×1.5 score weight. The ⚡ bolt icon marks clustered matches: three or more patterns within a 10-line window, each receiving an additional ×1.5 density multiplier under the legacy scoring model.
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | packaging/env.sh | 1 | # shellcheck shell=bash | COMMENT |
| LOW | packaging/macos/package.sh | 1 | #!/usr/bin/env bash | COMMENT |
| LOW | packaging/freebsd/package.sh | 1 | #!/usr/bin/env bash | COMMENT |
| LOW | packaging/linux/flatpak-oci.sh | 1 | #!/usr/bin/env bash | COMMENT |
| LOW | packaging/linux/package.sh | 1 | #!/usr/bin/env bash | COMMENT |
| LOW | packaging/linux/flatpak-bundle.sh | 1 | #!/usr/bin/env bash | COMMENT |
| LOW | …ng/linux/flatpak/ai.storyteller.vectorcraft.bundle.yml | 1 | # Flatpak manifest for the single-file bundle attached to every GitHub release | COMMENT |
| LOW | …ng/linux/flatpak/ai.storyteller.vectorcraft.bundle.yml | 21 | # X11 shared memory (MIT-SHM). Without it the X11 fallback is slow or fails. | COMMENT |
| LOW | …ng/linux/flatpak/ai.storyteller.vectorcraft.bundle.yml | 41 | - --env=XCURSOR_PATH=/run/host/user-share/icons:/run/host/share/icons | COMMENT |
| LOW | packaging/linux/flatpak/ai.storyteller.vectorcraft.yml | 1 | # Flatpak manifest for VectorCraft, ready for a Flathub submission. | COMMENT |
| LOW | packaging/linux/flatpak/ai.storyteller.vectorcraft.yml | 21 | # Keep identical to ai.storyteller.vectorcraft.bundle.yml (the release bundle); comments there. | COMMENT |
| LOW | crates/svg/tests/convolve_overflow.rs | 1 | //! A `<feConvolveMatrix>` whose `order` overflows usvg's `order_x * order_y` u32 multiply must be | COMMENT |
| LOW | crates/svg/src/export.rs | 261 | warnings: Vec<String>, | COMMENT |
| LOW | crates/svg/src/lib.rs | 1 | //! VectorCraft SVG import and export. | COMMENT |
| LOW | crates/svg/src/lib.rs | 21 | //! | COMMENT |
| LOW | crates/svg/src/lib.rs | 41 | //! describes dither as the gradient converted to pixels with subtle noise | COMMENT |
| LOW | crates/svg/src/lib.rs | 61 | //! none). | COMMENT |
| LOW | crates/svg/src/lib.rs | 81 | //! * Text lines after the first start at the first line's x; text in a clip path is ignored; | COMMENT |
| LOW | crates/svg/src/lib.rs | 121 | #[serde(rename = "entities")] | COMMENT |
| LOW | crates/svg/src/lib.rs | 141 | } | COMMENT |
| LOW | crates/svg/src/lib.rs | 181 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | COMMENT |
| LOW | crates/svg/src/lib.rs | 201 | /// presentation attributes and a fixed size, the most portable output. The SVG Options dialog | COMMENT |
| LOW | crates/svg/src/lib.rs | 221 | pub images: ImageMode, | COMMENT |
| LOW | crates/svg/src/lib.rs | 561 | // usvg converts the tree by recursion, in large stack frames: the nesting it is given is | COMMENT |
| LOW | crates/svg/src/import.rs | 61 | roxmltree::Document::parse_with_options(svg, roxmltree::ParsingOptions { allow_dtd: true, ..Default::default() }) | COMMENT |
| LOW | crates/svg/src/export/output.rs | 421 | /// `p` written with `m` as the SVG basic shape it exactly is, as the reference app's SVG | COMMENT |
| LOW | crates/svg/src/export/text.rs | 61 | self.stroke_props(&layer, layer.width, Some(layer.paint_bounds(bounds)), &mut p); | COMMENT |
| LOW | crates/svg/src/export/text.rs | 621 | y: f64, | COMMENT |
| LOW | crates/svg/src/import/files.rs | 21 | /// The files an SVG's images link to. | COMMENT |
| LOW | crates/svg/src/import/layers.rs | 41 | c.kind = sub.kind; | COMMENT |
| LOW | crates/svg/src/import/symbol_units.rs | 1 | //! Percentage lengths inside a `<symbol>` with a `viewBox`, as numbers, for usvg. | COMMENT |
| LOW | crates/svg/src/import/text.rs | 1 | //! `<text>` import. | COMMENT |
| LOW | crates/svg/src/import/depth.rs | 1 | //! How deep an SVG's elements nest, found in the XML before usvg runs: usvg converts the tree by | COMMENT |
| LOW | crates/svg/src/import/depth.rs | 21 | use super::css::{Styles, XNode, parse_decls}; | COMMENT |
| LOW | crates/svg/src/import/prepass.rs | 101 | last = r.end; | COMMENT |
| LOW | crates/three-d/src/solid/inflate.rs | 101 | let camera = Camera::new(view, bounds, o.depth + 2.0 * height.max(reach)); | COMMENT |
| LOW | crates/metafile/src/lib.rs | 1 | //! Windows metafiles: hand-written EMF and WMF writers and readers. | COMMENT |
| LOW | crates/tools/src/pie.rs | 1 | //! Pie widgets: two handles just outside a selected live ellipse, at its Pie Start Angle and Pie | COMMENT |
| LOW | crates/tools/src/cut.rs | 1 | //! Mirror & Cut, Line Cut and Rectangle Cut: drag across the selected art. | COMMENT |
| LOW | crates/tools/src/cursors.toml | 1 | # The cursors VectorCraft needs: one [[cursor]] per `Cursor` value (crates/tools/src/cursor.rs). | COMMENT |
| LOW | crates/tools/src/distort.rs | 1 | //! Distortion tools: Width (Shift+W), the Liquify family (Warp Shift+R, Twirl, Pucker, Bloat, | COMMENT |
| LOW | crates/tools/src/lib.rs | 161 | /// be `Other`, never `InputMethod`. | COMMENT |
| LOW | crates/tools/src/lib.rs | 241 | false | COMMENT |
| LOW | crates/tools/src/intertwine.rs | 1 | //! Object › Intertwine (#1763): the overlap areas of an Intertwine group, and the tool that | COMMENT |
| LOW | crates/tools/src/pen.rs | 1 | //! The Pen tool (P). | COMMENT |
| LOW | crates/tools/src/pen.rs | 41 | /// ...this one, once the engine made it: another path selected meanwhile isn't drawn on. | COMMENT |
| LOW | crates/tools/src/cursor.rs | 81 | Blend, | COMMENT |
| LOW | crates/tools/src/cursor.rs | 101 | /// The Lasso tool. | COMMENT |
| LOW | crates/tools/src/cursor.rs | 121 | PencilContinue, | COMMENT |
| LOW | crates/tools/src/cursor.rs | 181 | MeasureInspect, | COMMENT |
| LOW | crates/tools/src/select.rs | 1 | //! The Selection tool (V): click/shift-click, marquee (Shift-drag toggles the objects it reaches: | COMMENT |
| LOW | crates/tools/src/select.rs | 21 | //! bracket drag) cancels it: the art returns to its state at the press. | COMMENT |
| LOW | crates/tools/src/direct.rs | 1 | //! Direct Selection (A) and Group Selection tools. | COMMENT |
| LOW | crates/tools/src/direct.rs | 21 | //! Both move objects selected as a whole as the Selection tool does, snapping alike. When the | COMMENT |
| LOW | crates/tools/src/direct.rs | 201 | /// What the point holding a move of objects snaps to while the move takes direct-selected | COMMENT |
| LOW | crates/tools/src/text.rs | 1 | //! The Type tools: Type (T), Area Type and Type on a Path. | COMMENT |
| LOW | crates/tools/src/text.rs | 181 | /// Smart Guides for where new type goes. | COMMENT |
| LOW | crates/tools/src/meshblend.rs | 1 | //! Blend tool (W) and Mesh tool (U). | COMMENT |
| LOW | crates/tools/src/shape.rs | 1 | //! Shape tools: Rectangle, Rounded Rectangle, Ellipse, Polygon, Star, Line Segment. | COMMENT |
| LOW | crates/tools/src/settings.rs | 1 | //! Tool options that persist: a tool keeps them when another tool is chosen and they are saved | COMMENT |
| 606 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | crates/tools/src/cursors.toml | 22 | # ── Selection ───────────────────────────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | crates/tools/src/cursors.toml | 138 | # ── Pen family ──────────────────────────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | crates/tools/src/cursors.toml | 238 | # ── Type ────────────────────────────────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | crates/tools/src/cursors.toml | 257 | # ── Transform and resize ────────────────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | crates/tools/src/cursors.toml | 355 | # ── Navigation ──────────────────────────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | crates/tools/src/cursors.toml | 405 | # ── Colour and gradient ─────────────────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | crates/tools/src/cursors.toml | 449 | # ── Width, blend, slice and shape builder ───────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | crates/tools/src/cursors.toml | 528 | # ── Drawing and editing tools ───────────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | crates/tools/src/cursors.toml | 725 | # ── Symbolism tools ─────────────────────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | crates/tools/src/cursors.toml | 776 | # ── Not needed ──────────────────────────────────────────────────────────────────────────────────── | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| CRITICAL | crates/ui-egui/src/panels/gradient/tests.rs | 368 | assert_eq!(app.session.last_gradient.gradient.stops.len(), 2, "the Tools panel's Gradient button shows it"); | CODE |
| CRITICAL | crates/ui-egui/src/dialogs/tests_perspective.rs | 202 | assert_eq!(app.session.prefs.perspective_widget.position.id(), "bottomLeft"); | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | crates/tools/src/text.rs | 670 | json!({"path": pid.0, "mode": mode, "text": "", "at": [start.x, start.y], "vertical": vertical, "pla | CODE |
| LOW | crates/tools/src/text.rs | 687 | params["placeholder"] = json!(cx.placeholder_text); | CODE |
| LOW | crates/ui-egui/src/canvas/tests.rs | 7943 | let made = app.run("text.create", json!({"x": 200, "y": 150, "text": "Lorem ipsum", "size": 36})).unwrap(); | CODE |
| LOW | crates/cad/src/tests.rs | 188 | let holder = objects.iter().find(|o| kind(o) == "ACDBPLACEHOLDER").expect("placeholder"); | CODE |
| LOW | crates/text/src/tests.rs | 61 | const LOREM: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore | CODE |
| LOW | crates/text/src/tests.rs | 61 | const LOREM: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore | CODE |
| LOW | crates/engine/src/cmd/create.rs | 708 | check_switches(p, &["vertical", "placeholder"], "text.create")?; | CODE |
| LOW | crates/engine/src/cmd/create.rs | 733 | if bool_or(p, "placeholder", false) { | CODE |
| LOW | crates/engine/src/cmd/textedit.rs | 1042 | check_switches(p, &["vertical", "placeholder"], C)?; | CODE |
| LOW | crates/engine/src/cmd/textedit.rs | 1076 | if bool_or(p, "placeholder", false) { | CODE |
| LOW | crates/engine/src/cmd/fileio/tests_aiimport.rs | 454 | assert!(e.contains("placeholder"), "{e}"); | CODE |
| LOW | crates/engine/src/cmd/fileio/tests_aiimport.rs | 467 | assert!(e.contains("placeholder"), "{e}"); | CODE |
| LOW | crates/engine/src/cmd/fileio/tests_aiimport.rs | 493 | assert!(e.contains("placeholder"), "{e}"); | CODE |
| LOW | crates/engine/src/cmd/fileio/tests_aiimport.rs | 538 | assert!(e.contains("placeholder"), "{e}"); | CODE |
| LOW | crates/engine/src/cmd/fileio/tests_aiimport.rs | 571 | assert!(e.contains("placeholder"), "{e}"); | CODE |
| LOW | crates/engine/src/cmd/fileio/tests_aiimport.rs | 792 | assert!(e.contains("placeholder"), "{e}"); | CODE |
| LOW | crates/engine/src/tests/textedit.rs | 321 | ("text.create", json!({"x": 0, "y": 0, "placeholder": 1}), "placeholder"), | CODE |
| LOW | crates/engine/src/tests/textedit.rs | 323 | ("text.createInPath", json!({"path": frame, "placeholder": "true"}), "placeholder"), | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | crates/svg/src/import/text.rs | 942 | // Absolutely placed characters: lay the text out left-aligned and kern each one onto its x. | COMMENT |
| MEDIUM | crates/pathops/src/boolean.rs | 1 | //! Curve-preserving boolean operations built on `linesweeper`'s robust sweep-line topology. | COMMENT |
| MEDIUM | crates/pathops/src/fit.rs | 16 | /// Direction the curve leaves `p0` (robust to retracted handles). | COMMENT |
| MEDIUM | crates/pathops/src/lib.rs | 4 | //! Booleans are curve-preserving: `linesweeper`'s robust sweep-line works directly on cubic | COMMENT |
| MEDIUM | crates/geom/src/bez.rs | 42 | /// Unit tangent of `s` at `t`, robust at ends where control points coincide with the end point. | COMMENT |
| MEDIUM | xtask/src/stats.rs | 104 | println!("\n(#[test] counts source attributes; macro-generated tests count once. Use --exact for harness counts. | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH | crates/brush/src/warp.rs | 232 | /// Art turned so it runs left → right, flipped as requested; returns it with its bounds. | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | packaging/linux/flatpak-oci.sh | 5 | # Usage: | COMMENT |