A misspelt parameter name was ignored and the command reported success,
answering as if the parameter hadn't been given (`comment.list
{"filterr": "open"}` listed resolved threads). `Session::execute` now
refuses names the command's params doc doesn't list, naming them and
what the command takes, before the command runs, so every path into a
command (CLI, batches, control channel, MCP, plugins, the UI) gets the
check.
The names come from the existing docs, parsed once with the registry:
each top-level item's first word and the words joined to it by `|`, `/`
or `+`, bracketed text and what follows `:`, `=` or `→` being
description, alternative groups (`{x, y} | {points}`) included. `ids`
and `id` are always taken (`params::targets` reads either); node.set,
whose doc is its props list, keeps its own check.
The junk sweep runs each junk object as given and cut down to the names
a command takes, so the junk values still reach the parsers. Tests that
passed names their command never read (comment.resolve's author,
collab.host's link, node.get's depth) pass only what it takes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Before commands can refuse names their docs don't list (#1413), the docs
have to list what the code reads. These are the top-level names the run
code (and every helper it hands the params to) reads that its doc left
out, all checked against the code; nothing changes behaviour:
- creation: node.create (characters, points, closed), frame.create,
shape.*, text.create and vector.create (index, name, select),
image.place (name)
- aliases the code already took: comment.* (id | link), annotation.add
and annotation.edit (label | text), text.set (characters | text),
text.listStyle (style | list), fill.add and stroke.add (paint | color),
proto.interaction.add/set (durationMs | duration_ms, delayMs | delay_ms)
- options: file.export and export.update (contentsOnly,
includeBoundingBox, svgOutlineText, svgSimplifyStroke), comment.next
(frame, resolved), layout.suggest (direction), variable.create
(description), vector.edit.widthPoint (side), collab.share (key),
collab.manageFile (access, label, link, name), fill.pattern (visible,
blend)
- docs whose shape hid a name: proto.interaction.add (a stray `}` closed
the object before delayMs; dest was only in a trailer),
proto.interaction.set (patch fields in a second object), collab.join
(a top-level comma inside the link examples), pref.uiScale (commas in
the value's description)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pre-signing check matched any folder named Program Files, so it rejected
a valid MSI when Windows resolves Program Files elsewhere, accepted the 64-bit
root for an x86 package and accepted a different app name. Build the expected
folder from the package architecture's resolved root and the policy name.
Shadows and layer blurs dominated PNG/JPG/WebP export time (gdb stack
sampling of the example files: about 45% in fx::box_blur_h, 13% in
fx::morph). Both now produce the same bytes, faster:
- fx::blur runs its three box passes per row while the row is in cache,
works on whole pixels instead of bounds-checked bytes, skips fully
transparent rows, transposes in cache-friendly column blocks, and
splits rows and transposes across rayon on desktop for pixmaps of
64K pixels and more, only when not already on a rayon worker (a band
of render_view stays on its thread: no nested parallelism).
- fx::morph (shadow spread on layers that aren't one outline) uses the
van Herk / Gil-Werman running max/min: constant work per pixel instead
of 2r + 1.
Integer arithmetic and pass order are unchanged; new tests compare both
against the previous implementations byte for byte, including sizes on
the parallel path.
An ignored timing test exports every top-level frame of examples/ as
PNG, JPG and WebP at 2x through export_file:
cargo test --release -p figcraft-engine --lib export_timings -- --ignored --nocapture
Ryzen, 16 threads, Linux; median of 7 (min):
PNG 2195 ms (2182) -> 992 ms (989) -55%
JPG 2880 ms (2560) -> 1550 ms (1535) -46%
WebP 1964 ms (1901) -> 1022 ms (1018) -48%
total 7035 ms (6677) -> 3567 ms (3555) -49%
All 80 exported files (PNG, JPG, WebP, PDF, SVG) are byte-identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Real msiexec runs (per-user and all-users, silent and wizard) found two defects
that the metadata fixtures cannot see:
- "Only for me" published ALLUSERS=2, so FindRelatedProducts treated the run as
per-machine and skipped the existing per-user copy. An interactive upgrade
left two registered products and uninstall left files behind. The page now
leaves ALLUSERS unset for a per-user choice.
- Windows Installer resolves Program Files once, for the context it started in,
so choosing "Everyone" in the wizard installed into the user profile with a
machine-wide registration. The wizard now sets the new-install folder itself
after the choice (Program Files, Program Files (x86) for 32-bit, or the
per-user Programs folder). Silent installs are unchanged.
check-install-policy.ps1 now fails if either regresses.
* feat(ui-egui): font family menu keeps its search field and grows back after a filter clears
Repin craft-libs to the shared dropdown (craft-ui), move the font family menu to
craft_ui::combo::Menu with the search in a fixed header, and use craft_ui::ComboBox
for the credits sort. clippy bans egui::ComboBox.
* chore(deps): craft-libs 60d60d48 (dropdown fixes)
* chore(deps): craft-libs 4d7b82f7 (dropdown fixes)
* chore(deps): craft-libs 92d392cf (#387 merged)
* fix(ui-egui): craft-ui on every target (wasm uses the shared ComboBox too)