Lower the cognitive complexity of segment_at, apply_mmco, derive_amvp,
plotted_extent and look_with_def by moving their per-segment, per-MMCO,
per-neighbour, per-stack and per-preset-style steps into helpers. The
expressions and their evaluation order are unchanged.
Picks up the startup prompt without the glyphs a font may lack (storytold/craft-libs#343, #344): the close mark is painted and the version pill is ASCII, so apps that panic on a missing glyph no longer take the prompt down.
Part of tracking issue storytold/craft-libs#319
Picks up the update prompt fix that draws its close mark and version pill
without glyphs a font may lack, so the prompt no longer takes the app down
when a bundled face has no ✕ or →.
Part of tracking issue storytold/craft-libs#319🤖 Generated with Codebuff
Co-authored-by: Codebuff <noreply@codebuff.com>
* fix(render): optimize table styles, chart lookups, line trimming, and canvas limits
* fix(render): give outer-shadow passes their own traversal budget
A shadowed group's silhouette pass shared the 100,000-visit limit with normal drawing, so a group with many children could use it up before anything visible was drawn.
* perf(render): resolve a table's style once per draw
Picks up the fix that keeps the close request pending while the app holds unsaved work, the executable-name check in the Windows update helper, and the zip 9 / dirs-free build.
Part of tracking issue storytold/craft-libs#319
* feat: startup update prompt modal and direct self-updating
Ref: storytold/craft-libs#319
* build: pin craft-update to craft-libs main (b9ba47f) and refresh the lock
* refactor(updates): drive the flow with craft-update's Updater
Results are read independent of the prompt, a dropped worker is an error, failures return to the prompt, and no update starts over unsaved work. A manual check during the startup check is honored.
* refactor: explicit imports instead of glob imports in command and test modules (SonarCloud S2208)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix: geom extra imports
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix: drop unused imports
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix: restore explicit imports in test modules (SonarCloud S2208)
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* security(anim): index slides and shapes once, share paragraph targets and motion paths
Custom-show resolution, timeline target lookup and by-paragraph builds scaled with the product
of two document-controlled counts, and paragraph expansion deep-copied the parsed motion path
into every effect.
- show_order / custom_indices resolve slide ids through one id->index map.
- Timeline::with indexes shapes once (same first-wins order as Slide::shape) for animation
targets and base colours, and records each animated shape's colour with a set.
- Paragraph targets are worked out once per shape and shared across its animations.
- Effects share their EffectSpec (and so the motion path) by Rc instead of cloning it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(anim): use Arc for EffectSpec to satisfy Sync and early return on empty animations
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* fix(pptx): harden import resource budgets, deduplication, and lookup caching
- Chart sidecars (Finding 2): Enforce an import-wide retained byte budget
and cache captured sidecars by chart part to prevent memory amplification.
- Content types cache (Finding 6): Cache parsed [Content_Types].xml defaults
and overrides in Package.ct_cache.
- Text run splitting and font lengths (Finding 8): Bound typeface attribute
lengths and cap paragraph runs to prevent multiplication on text splitting.
- Inner namespace decls (Finding 15): Use HashSet/HashMap indexing and cap
inner declarations to 1,000.
- Slide and notes deduplication (Finding 16): Deduplicate slide paths and
cache parsed notes slide bodies by target.
- Diagram media caching (Finding 22): Cache diagram media payloads in
imp.media_by_part.
- Master slide deduplication (Finding 28): Deduplicate master paths before
loading.
- Layout target deduplication (Finding 30): Deduplicate layout targets
across master slides.
- Chart value axis indexing (Finding 32): Index valAx by axId using a HashMap.
- ChartEx series output budget (Finding 34): Charge series point vectors
against an aggregate output budget and cap series count.
- ChartEx point allocation bounds (Finding 36): Allocate point vectors
only to actual point span bounded by budget rather than declared ptCount.
- SmartArt recursion depth (Finding 38): Limit SmartArt recursion depth to 8.
* fix(pptx): address review feedback on sidecar cache, series budget, and inner decls
* fix(pptx): preserve cached sidecar bytes and non-empty category fallback
- Row 31: Use HashMap for comment author lookup in author_slot
- Row 37: Index preserved parts by ID before resolving graphic frame relationships
- Row 10: Index kept chart parts by normalized path and avoid duplicate payload scans
- Row 39: Update existing relationship parts instead of appending duplicates in PackageOut
* fix(metafile): harden EMF+ parsing, clipping complexity, and memory accounting
- Row 72: Use checked tail slicing in comment header offset calculations
- Row 78: Break FillRects and DrawRects loops early when canvas is full
- Row 75: Pre-check dimensions in decode_compressed before RGBA pixel allocation
- Row 85: Account for EMF+ object table image/path memory against MAX_BYTES
- Row 42: Replace O(N^2) prefix copying in wrap_line with linear measurement
- Row 43 & 54: Share immutable clip regions Arc across placed items and charge canvas regions
- Row 48: Bound text transformation scale and glyph curve geometry in Picture::place and draw_text
- Row 52 & 68: Impose work budget bounds on Sutherland-Hodgman polygon clipping and bitmap masking
* fix(metafile): collapse nested if statement per clippy
Skipped where the receiver type is generic or not evident without a compiler (wrapping_neg, total_cmp, exists, to_vec, as_ref, split_first, Option::into_inner).
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* chore: resolve SonarCloud maintainability issues
* fix: address shellcheck SC2059 and fix generic cast ambiguity
* style: apply rustfmt formatting
* fix: restore compile and behaviour in Sonar refactors
- fonts: char::is_alphanumeric takes &self, so filter needs a closure
- h264: keep the mvd accumulator as i32 (prefix is usize)
- model: annotate the non-stacked range accumulators as f64
- engine: chart.options still clears the raw chart XML for upDownBars
and quartiles; smartart goes back to the glob import (cmd! macro)
- web: keep the original viewport meta (behaviour change)
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>
* Use chrono for UTC/calendar conversion instead of hand-rolled copies
Replaces the hand-rolled civil_from_days / civil copies and the hand-written
weekday / leap-year / days-in-month helpers with chrono. chrono is already a
workspace dependency (used by crates/text), so this reuses that entry and just
adds `chrono = { workspace = true }` to the engine and desktop app rather than
a second version.
- apps/deckcraft logging `timestamp` now formats through chrono (private
civil_from_days deleted).
- engine cmd::design `civil` (days since 1970 -> date, used for comment
timestamps in review.rs) uses chrono::NaiveDate.
- text datetime `DateTime::is_valid` / `weekday` use chrono::NaiveDate instead
of a hand-rolled leap-year table and days-since-1970 weekday.
Output is byte-identical and pinned by the existing logging, design and
datetime-field tests; each converted helper keeps a proleptic-Gregorian
fallback for dates outside chrono's NaiveDate range.
Follow-up to storytold/cadcraft#1785
* text: make chrono available on wasm32
is_valid/weekday in crates/text/src/datetime.rs use chrono unconditionally,
but chrono was only declared under the non-wasm target, so the crate (and
engine/pdf/render/ui-egui depending on it) failed the wasm32-unknown-unknown
check in `cargo xtask ci`.
- crates/text: declare chrono unconditionally (wasm-safe `std` baseline),
and keep `clock` in a separate cfg(not(target_arch="wasm32")) entry for
now_local()'s local clock, which is already wasm-gated to return None.
- workspace: drop `clock` from the shared chrono entry so it is no longer
forced onto wasm builds (engine uses chrono unconditionally too); `clock`
is now requested per-crate only where the local clock is read.
* Never panic on a clock outside chrono's range; harden the civil fallback
logging.rs timestamp(): chrono's From<SystemTime> unwraps, so a clock past
year 262143 panicked inside the logger. Use the fallible DateTime::from_timestamp
and read an out-of-range or pre-epoch clock as the epoch, as the old saturating
code did. Years past 9999 are written with {:04} like before, because %Y adds a
'+'. Tests cover both cases.
cmd/design.rs civil_fallback(): make the hand-rolled fallback's year arithmetic
saturating so civil() cannot overflow or panic at i64::MAX/MIN (the engine rule
forbids crashes and unchecked arithmetic). Behaviour is unchanged for every real
input; a test exercises both extremes. The output year is formatted with {y:04}
by review.rs, so no %Y prefix issue.
crates/text/src/datetime.rs: checked after review; is_valid already limited the
year to 1..=9999 before this PR, weekday() is only reached for valid dates, and
all its arithmetic is checked/euclidean, so no change is needed.
* Don't build an unrepresentable SystemTime in the far-future test
UNIX_EPOCH + Duration::from_secs(u64::MAX / 2) overflows Windows'
SystemTime and panicked before timestamp() ran, failing the Windows
test job. Build it with checked_add and only assert where it exists.
* Make civil() exact for every i64 instead of saturating
The out-of-range fallback saturated z + 719_468, so civil(i64::MAX)
returned a date 719,468 days early. civil() now has chrono convert the day
within its 400-year Gregorian cycle and adds whole cycles back as years
(i128 for the extremes), so the hand-rolled fallback is gone. Exact results
for i64::MAX/MIN and both sides of chrono's range are tested against Hinnant's formula.
* time: preserve out-of-range years in the log timestamp instead of the epoch
`DateTime::from_timestamp(..).unwrap_or_default()` changed the existing output: a
clock set far ahead (an input the old hand-rolled civil-from-days converted)
collapsed to `1970-01-01`. Restore the old conversion domain with a cycle-based
helper: reduce by whole 400-year Gregorian cycles (146_097 days), convert the
in-range remainder with chrono, and carry the year back as i128. Only the year
leaves chrono's range; month/day/time stay exact. Pre-1970 still clamps to the
epoch as before. chrono stays the only calendar logic (no new features).
apps/deckcraft logging.rs `timestamp` (SystemTime, u64 secs), pinned via a
platform-safe `UNIX_EPOCH.checked_add`:
u64::MAX/2 s → "292277026596-12-04T15:30:07.000Z" (was "1970-01-01..."),
10e12 s → "318857-05-20T17:46:40.000Z", 253_402_300_800 s → "10000-01-01...".
* Name the 400-year range extension as the one exception to the time rule
The rule said never to hand-roll calendar math, but the range fix
(shifting by whole 400-year Gregorian cycles so dates past chrono's year
range keep their old output) is cycle arithmetic, so the rule and the code
disagreed. The rule now names that exception: the library still computes
month, day and time, and only whole cycles are carried in the year.
* Say the library computes month and day; time-of-day is plain arithmetic
Narrows the range-extension wording: the date library computes the
calendar fields, and splitting seconds into hours, minutes and seconds
is ordinary arithmetic, which is what the code does.
Use [[ ]] for conditional tests in the bash packaging scripts, give positional
parameters a name before use, end the shared helpers in common.sh with an explicit
return (bare return keeps the last command's status), and give the FreeBSD arch
case a no-op default. No behaviour change.
Calls storytold/craft-common-actions/.github/workflows/sonarcloud.yml@main
with secrets: inherit. Adds SonarCloud badge to README where missing and
removes the redundant sonar-project.properties (config now passed by the
reusable workflow).
* ci: refactor workflows to use reusable artcraft-common-actions
* ci: point reusable workflows to storytold/craft-common-actions
* ci: pin storytold/craft-common-actions to commit 27a978f and pass secrets explicitly
* ci: use @main (unpinned) for craft-common-actions with explanation comment