* M3: library analytics list how each style and variable is used
A file reports how many of its layers use each of a team library's styles and
variables, and Show usage lists them by a Type choice with their layers and
files. Layers in instances count for their component. Also ignores plugins/target.
* M3: the style analytics test waits for the instance report too
Choosing Image shows a checkered placeholder and Upload from computer instead of a dialog at once; an image paint shows its asset preview. A file dropped on the open picker replaces the image, keeping the fill mode, crop, position and adjustments (image.replace, one undo step; paths capped as image.place's). The browser's file picker reaches the tab too. Also fixes a race in library_detach_tests.
Gap 10: a file counts the library instances it places, creates, pastes or duplicates and reports them like its detaches. The server counts what a file's totals grow past their highest in the current week and keeps a year of weeks (actions.json). library.usage {days} and Show usage give insertions and detachments over the past 30 days, 90 days or year. New variants are appended; an older server refuses them and Show usage says so.
* M3: team library analytics count detached instances
Detaching an instance of a library component counts it in the file (libraries.detached, saved with it, capped). The background library check reports the counts after its fetch (ReportDetached); the server keeps them per file beside the file's usage report in usage.json (same caps) and answers Detached with them. Show usage and library.usage list detaches per component and per file (detached, detachesKnown). The new request and reply variants are appended, so every older library message keeps its bytes; an older server refuses the new requests, which the app ignores. Gap 10.
* M3: the detach test waits for the instance count too (the two arrive in separate reports)
downloads_have_a_share_per_address failed once under a full gate: its 40 library fetches could take long enough for the address's download share, measured against Instant::now() and refilling 1 MiB a second, to refill one more. Store::clock() is now what every rate limit the store checks reads: the clock, or in test builds a time the test holds it at (a #[cfg(test)] field). The hardening, download burst and admin budget tests hold it still; the admin test is back to its exact budget (#1564 had allowed for refilling). Found flaking in gates.
* M8: cap the people a client keeps track of and the deleted ids a session remembers (#1121)
A hostile host or server could grow a client's presence, seen-peers and who-is-here lists without bound, and a session's deleted-id set only grew. Clients keep at most MAX_PEERS (4,096) others, leaving newcomers out with a warning; max_people is at most that; a session remembers at most 262,144 deleted ids, the lowest forgotten first.
* M8: a max_people above what clients track is lowered with a warning, not refused (#1121)
Refusing it would stop a server whose existing config says more from starting after an upgrade. Config::people() lowers max_people to MAX_PEERS for the core's limits, and the startup warnings name the configured and the used value.
* M8: the live welcome's handling in its own function (Sonar S3776)
The per-address download bucket never held more than its 16 MiB burst, so a larger library or plugin was refused for good. Bucket::take_spread serves one from a full bucket and leaves the debt to refill at the rate before the next download; the rate over time still holds.
Someone who joins with a comment link (or its join code) sees the file live and adds, replies to, reacts to and resolves comment threads, but can't change the design: their app refuses other edits and the server accepts a comment link's batch only when every change in it touches comment threads. Sharing online makes a comment link and code beside the edit and view ones; the Share popover offers Can edit, Can comment and Can view, and the commenter's toolbar shows a Can comment badge with Move, Hand and Comment.
Backward compatible: clients announce the comments word; one that doesn't is welcomed with a comment link as a viewer and gets comment links listed as view links, a level this build doesn't know reads as view, and an older server that can't make a comment link leaves the share without one.
* M9: a per-address budget for server admin requests, and join codes found by hash (#1075)
* M9: the server tests share one snapshot and address helper (#1075)
The batches a session keeps for apps that reconnect were bounded only by count on a desktop host
and a private figcraft-server (`max_log_bytes` defaulted to usize::MAX). It now defaults to
64 MiB, and figcraft-server takes it from a new `max_log_mb` setting (default 64; open to the
public, also at most a quarter of `max_file_mb` as before). An app behind the trimmed log catches
up with a snapshot, as it already did past the count limits. The protocol is unchanged.
Nine 8 MiB renames with default limits: all 75.5 MB kept before, 58.7 MB after.
Fixes#1069
* chore: resolve SonarCloud maintainability issues
- Define SHA256_FMT constant in packaging script to remove duplicated string literal (shelldre:S1192)
- Remove redundant 'static lifetime annotations from const items (rust:S8863)
- Replace closures with method and function pointers (rust:S1612)
- Extract helper functions in synthetic document generation, bezier polyline flattening, and component restoration to reduce cognitive complexity (rust:S3776)
* fix(packaging): use verify_sha256 helper function to avoid shellcheck SC2059
* fix(engine): remove duplicate closing brace in large_document
* fix(engine): take &mut Document in restore_id_map; keep 'static on associated consts
restore_id_map allocates ids, so it needs a mutable document. Elided
lifetimes in associated consts trip elided_lifetimes_in_associated_constant,
so those annotations are restored.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Assign positional parameters to locals in sha256 verification
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Dylan Taylor <dylan@craftmaker.dev>
* Dev profile: line tables for workspace crates, no debug info for dependencies
* Tests: one integration-test binary per crate with several test files
* xtask ci: nextest, doctests, tests without examples, --keep-going; add xtask test
read() returns Result<Message>, not a lock guard; these test loops
deliberately ignore what the client sent first.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
Validated formatting, workspace Clippy, workspace tests excluding the two pre-existing localized startup assertion failures tracked in #1017, layers, assets, WASM and plugins. The startup tests are unchanged in this independent fix.
Unrelated to the security changes in this branch; `main` already failed
`clippy --workspace --all-targets -- -D warnings` without them. Eight
clippy::nonminimal_bool sites across seven files, each taking clippy's own
suggested rewrite.
Every rewrite is pure boolean algebra with the negation kept outside, so the
float comparisons and their NaN behaviour are untouched: !A && !B becomes
!(A || B), and !opt.is_some_and(f) becomes opt.is_none_or(!f). Two sites needed
explicit parentheses the suggestion omitted, where the rewritten term is the left
operand of an && and || binds looser.
designcraft cleared the same lint in bc840130; this is figcraft's equivalent.