* 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>
Board files (File > New board) with sticky notes, shapes with text, connectors that follow what they join (straight or elbow, arrowheads, labels), marker, highlighter and eraser, and stamps; a board toolbar and a contextual bar; the Design panel hidden on boards, Prototype and Dev Mode refused there. Boards are ordinary FigCraft files built from ordinary layers, so saving, undo, export, MCP and live sessions work unchanged. File tabs, recent files and Open files show a pen nib for design files and a note for boards (original icons drawn in code).
Import lines for connected components inside generated layer trees; Compare changes against any saved version; a Dev Mode behaviour suite with an inspect-only canvas (menus, shortcuts, paste and Revert to saved refuse design edits; a drag under way is dropped on entering), Changed in the ready list, statuses only on top-level frames and sections; read-only plugins' panels from the Inspect tab.
* 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>
`utc_date` (collab) and `date` (ui-egui) took `u64` seconds, which the old
hand-rolled civil-from-days code converted for the whole `u64` range. The
chrono rewrite used `from_timestamp(..).unwrap_or_default()`, so any instant
past chrono's own year limit (262143) silently became the epoch — changing
existing output. `u64::MAX` seconds, for example, used to read
`584554051223-11-09 07:00 UTC` / `Nov 9, 584554051223`, not 1970.
Fix: add a `utc_parts(days, sod, nanos)` helper per crate that moves the
instant into chrono's range by whole 400-year Gregorian cycles (146_097 days),
lets chrono do the month/day/time, and restores the real year afterwards.
chrono stays the only calendar logic; the cycle count just shifts the year, and
every `u64` day count fits in `i64` so the conversion is always exact (no epoch
fallback). Split seconds with `u64` arithmetic first to avoid the `i64` seconds
overflow near `u64::MAX`, matching the old code.
Tests now pin the old output instead of the epoch:
- collab utc_date(u64::MAX) == "584554051223-11-09 07:00 UTC"
(and utc_date(253_402_300_800) == "10000-01-01 00:00 UTC", already pinned)
- ui-egui date(253_402_300_800) == "Jan 1, 10000"
date(u64::MAX) == "Nov 9, 584554051223"
chrono's %Y writes a '+' before years past 9999, whereas the old hand-rolled
{y:04} did not. utc_date now writes the year with {:04} to keep the exact old
output. from_timestamp already reads a clock past chrono's range as the epoch
rather than panicking. Tests cover the 5-digit-year and out-of-range cases.
Replaces the two hand-rolled Howard Hinnant days-from-civil copies with chrono
as a workspace dependency:
- crates/collab/src/message.rs: utc_date (YYYY-MM-DD HH:MM UTC)
- crates/ui-egui/src/home_recents.rs: date (Mon D, YYYY, UTC; keeps the
translated month names and trf formatting)
Output is unchanged. Neither function had a test, so each gains one pinning the
exact strings the old code produced (leap days included). chrono uses only std,
so wasm stays safe. AGENTS.md/CLAUDE.md now say time goes through chrono.
Follow-up to storytold/cadcraft#1785
Include document UID in pages_seen_page to correctly handle page selection and scroll tracking when switching between documents. Record page as seen only after the active row renders so that pages filtered out by search scroll into view when the filter is cleared.
Found while testing crates/svg/src/lib.rs:589 (also :604 and :611)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Found while testing crates/collab/src/code.rs:~150 (Local::from_link, u64::from_str_radix on the token)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A .figcraft, .fig or file JSON dropped on the canvas ran file.import,
which added its pages to the file in front. It now opens as a file of
its own, as on the home screen, in the browser too. Images and SVG
still import at the drop point; File > Import still adds pages.
- pending_open::open_dropped queues a dropped file as file.open; the
home screen and the canvas share it.
- Dragging only documents over the canvas says "Drop to open" (not in
the browser, which names no file until the drop).
- The tab-switch import test drops an SVG: it pinned where an import
lands, using a .figcraft only as a file to import.
Found while testing crates/color/src/lib.rs:165 (also :173)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A component set took its name from the first variant's name up to the first '/' or '=',
so variants named "State=Default, Size=Small" made a set called "State". The set now
takes the path before the last '/' ("Button/State=Hover" -> "Button",
"Icons/Arrow/Dir=Up" -> "Icons/Arrow"), keeps a plain name as is ("Button"), and
otherwise gets the next numbered default ("Component 1"), as a new component does.
Duplicate and Alt-drag copied a main component as a second main. Within
its file the copy is now an instance of it (components::copy_in_file,
shared by edit.duplicate and the Alt-drag copy), as Copy then Paste
already was. A variant duplicated in its set stays a new variant.