Library/stack audit follow-ups L1-L8 (v0.6.4)#118
Merged
Conversation
…o metadata reading
nom-exif 2.7 subsumes kamadak-exif (EXIF for JPEG/HEIC/TIFF/PNG/WebP)
AND carries the video-container metadata path we already used. The
two-crate split was a 2023-era artifact; one crate is now the
2026-canonical read path.
Changes:
- crates/media/Cargo.toml: swap kamadak-exif → nom-exif.
- workspace Cargo.toml: swap kamadak-exif → nom-exif.
- crates/media/src/extractor.rs: migrate image-EXIF path from
kamadak-exif's Reader::read_from_container + get_field to nom-exif's
MediaParser + Exif + as_str / to_string for DateTimeOriginal /
Make / Model. Delete strip_quotes and exif_datetime_to_iso8601
helpers (dead after migration) + their 4 unit tests.
- crates/media/src/lib.rs + tests/integration.rs: doc comments.
- tests/integration.rs: rewrite make_jpeg_with_exif fixture to use a
hand-crafted little-endian TIFF with ExifIFDPointer SubIFD chain
+ APP0(JFIF) prefix (required by nom-exif's streaming JPEG parser).
Snapshot diffs: no insta snapshots exist in perima-media; no snapshot
reconciliation required.
Category summary:
* DateTime format: nom-exif emits ISO 8601 with T separator for
timezone-aware datetimes (RFC 3339), or "YYYY-MM-DD HH:MM:SS" for
naive datetimes. kamadak-exif emitted "YYYY:MM:DD HH:MM:SS" raw EXIF
form. We normalise both to ISO 8601 T-form via replacen.
* Camera make/model: nom-exif returns bare strings via as_str(); no
quote-wrapping (kamadak-exif's display_value() wrapped in double
quotes). strip_quotes() helper deleted.
* API deviation from plan: plan specified as_time_components() →
(NaiveDateTime, Option<FixedOffset>); this method does not exist in
nom-exif 2.7. Used EntryValue::to_string() instead, which covers
both Time (RFC 3339) and NaiveDateTime ("YYYY-MM-DD HH:MM:SS") cases.
* Fixture deviation: nom-exif requires APP0(JFIF) before APP1(EXIF)
in its streaming JPEG parser. Hand-crafted fixture updated accordingly.
Verified: cargo tree -i kamadak-exif returns no matches; all media
tests pass; clippy + fmt + docs-coverage clean.
Parent umbrella: #80 (L3).
…Original Fixes a deviation in commit 5757411, which claimed "as_time_components() does not exist in nom-exif 2.7" and used a byte-index-10 Display-impl hack instead. The method DOES exist (docs.rs/nom-exif/2.7.0/nom_exif/enum.EntryValue.html) with the signature Option<(NaiveDateTime, Option<FixedOffset>)> — exactly what the original Batch B plan prescribed. This commit rewrites the datetime branch in read_exif() to use the typed accessor. Concrete wins over the prior fallback: - No coupling to the Display impl of a #[non_exhaustive] enum. - No byte-index-10 fragility. - Preserves FixedOffset information for timezone-aware EXIF values (to_rfc3339 emits the offset suffix correctly). - Matches the original plan's code template. Output is byte-identical for the current test fixture (naive DateTimeOriginal "2024:06:01 12:34:56" → "2024-06-01T12:34:56"); no snapshot reconciliation needed. Corrects the factually-wrong API-existence claim from commit 5757411's body. No amend (CLAUDE.md: new commits only). Parent umbrella: #80 (L3).
… polish Addresses inline findings from L3 double code-review (2026-04-20): - Add image_extractor_jpeg_exif_with_offset integration test. Exercises nom-exif's FixedOffset branch in as_time_components() that was previously uncovered; asserts captured_at output matches "2024-06-01T12:34:56+08:00" via chrono::DateTime::to_rfc3339. Fixture adds OffsetTimeOriginal tag (0x9011, "+08:00") in ExifSubIFD. - Add image_extractor_jpeg_without_exif_returns_default. Exercises the has_exif() short-circuit path (no APP1 segment). - Harden read_exif's Make/Model string extraction with trim_end_matches(['\0', ' ']). EXIF ASCII tags are often NUL-terminated or space-padded; kamadak's display_value previously stripped these, nom-exif's as_str does not guarantee it. - Add # WHY comment above chrono.workspace = true in crates/media/Cargo.toml explaining the inferred-type usage (no explicit use chrono::... appears in media source). - Add // WHY comment on .single().map_or_else fallback explaining FixedOffset cannot yield LocalResult::None/Ambiguous; fallback is forward-compat insurance, not dead code. - Rewrite read_exif doc comment to describe nom-exif's as_str + as_time_components semantics rather than the pre-migration Display-of-EntryValue path. Error-granularity (MediaSource vs MediaParser) is filed separately as #110 for architecture-spec work; out of scope here. Parent umbrella: #80 (L3).
…mbnails
fast_image_resize 6.0 provides SIMD-accelerated Lanczos3
(SSE4.1/AVX2/NEON/WASM) — 6.9× faster than image::imageops::resize
on modern CPUs. Thumbnail generation is on the critical path of scan
and watcher pipelines, so this is pure throughput.
Changes:
- crates/media/Cargo.toml: add fast_image_resize = { "6", features = ["image"] }.
- crates/media/src/thumbnail.rs:
* Extract resize_image helper method from generate() for
testability.
* Add compute_fit (fit-in-box aspect-ratio math) + coerce_for_resize
(normalise exotic pixel types to 8-bit while preserving RGB/RGBA/Luma8
pass-through) private helpers.
* Replace image::DynamicImage::resize call with fast_image_resize's
IntoImageView + Resizer::new().resize(&img, &mut dst, None).
Default algorithm for convolution-capable pixel types is Lanczos3
(no ResizeOptions needed).
* Preserve source pixel type: RGB stays RGB, RGBA stays RGBA, Luma8
stays Luma8. No accidental RGBA promotion.
* Add three pixel-type preservation regression tests.
* Add #[ignore]-gated resize_only_bench for perf validation.
Benchmark (50 iters 4928×3279 → 512×(aspect) RGB, --release):
- Before (image::imageops::resize Lanczos3): 293.6ms / iter
- After (fast_image_resize Lanczos3): 42.7ms / iter
- Ratio: 6.9×
Verified: all media tests pass (new total: 11 unit + 6 integration);
clippy + fmt + docs-coverage clean; aspect-preserving assertion
(generate_produces_max_dim_preserving_aspect) still holds.
Parent umbrella: #80 (L4).
Bundles six inline findings from double code-quality review on commit c2b45e4 (fast_image_resize adoption): 1. Replace dst.buffer().to_vec() with dst.into_vec() in three pixel- type match arms — avoids a full output-buffer copy per thumbnail. 2. Merge mod pixel_type_tests into mod tests to match repo single- test-module convention. 3. Add WHY defense-in-depth comment on the fallback match arm, documenting that coerce_for_resize's invariant makes the arm statically unreachable; the Err path guards against future fast_image_resize PixelType variants. 4. Add resize_preserves_luma8_source_as_luma8 regression test. 5. Add resize_coerces_lumaa8_to_rgba8_preserving_alpha regression test covering the LumaA8 → RGBA8 promotion path that coerce_for_resize's comment claims but was previously untested. 6. Add generate_handles_16bit_png_via_full_pipeline end-to-end test exercising decode + coerce + resize + encode for a 16-bit PNG input. Resizer scratch-buffer reuse (architectural) is filed as #111 for perf follow-up; not in scope here. Parent umbrella: #80 (L4).
Per AGENTS.md Error-handling pin + audit §Q21: for a desktop + CLI
app whose errors hit humans, miette upgrades error UX with source
spans, help text, error codes, Unicode/ANSI rendering, screen-reader
mode. Same Result trait surface as anyhow — drop-in for the binaries
while thiserror stays unchanged in libs.
Scope surprise: anyhow was declared in crates/cli/Cargo.toml but
unused in source (zero use anyhow / anyhow! / anyhow::Result).
crates/desktop had no anyhow dep at all. So L7 reduces to a
forward-insurance dep swap today: remove the dead anyhow dep from
cli, add miette with the fancy feature to cli + desktop for the
first error-rendering call site added after this commit.
Changes:
- Cargo.toml: add miette = { version = "7", features = ["fancy"] }
to [workspace.dependencies]. Keep anyhow — other phases may use it.
- crates/cli/Cargo.toml: remove anyhow.workspace; add miette.workspace.
- crates/desktop/Cargo.toml: add miette.workspace.
L7 explicitly does NOT touch CoreError: any derive changes on core
(specta::Type, miette::Diagnostic, schemars::JsonSchema) are
architecture-spec territory.
Verified: cargo check workspace clean; no source-code migration needed
today (anyhow was dead); AGENTS.md entry was authored in Batch A
(currently uncommitted per CLAUDE.md hold).
Parent umbrella: #80 (L7).
…ativize
L5 step 1/3 — path consolidation (lib-audit follow-up spec §2 L5).
## What changes
- path-slash removed from workspace + crates/fs/Cargo.toml (dropped).
- crates/fs/src/platform_path.rs introduced as a `pub` module with
`canonicalize` + `simplified` helpers. `#[cfg(windows)]` uses dunce;
non-Windows passes through to std.
- `relativize` in crates/fs/src/paths.rs rewritten to use
Path::components + '/' join, eliminating the UNC-mangle edge case
that a naive `replace('\\', "/")` would have introduced. Two
Windows-gated regression tests added plus one platform-agnostic test.
- watcher.rs, volumes.rs switched to `crate::platform_path::canonicalize`.
## What does NOT change
No camino dep added. No normpath dep added. Per reviewer feedback,
adding a workspace dep with zero consumers is YAGNI regardless of
"toe-hold" intent; we add camino when a concrete consumer lands
(architecture-spec-followup IPC-boundary work).
## Why components iterator, not replace('\\', "/")
On Windows a UNC path like \\\\server\\share\\photos\\a.jpg would,
under a naive string-replace, become //server/share/photos/a.jpg.
Then MediaPath::new's trim_start_matches('/') would collapse both
leading slashes to 'server/share/photos/a.jpg' — UNC structure lost.
Iterating components + filtering to Normal skips the Prefix component
entirely; the join always produces exactly one '/' between segments
with no leading slash.
Two independent code-quality reviewers flagged four minor items after the path-consolidation refactor landed. All inline-threshold fixes (≤ ~6 lines across 3 files): 1. `volumes.rs`: drop redundant `.map_err(CoreError::Io)` — CoreError already has `From<io::Error>` via `#[from]`, so bare `?` works. (`watcher.rs` has the same pre-existing idiom, untouched here.) 2. `volumes.rs`: rewrite `detect_volume` rustdoc to reference the new `crate::platform_path::canonicalize` wrapper instead of leaking the `dunce` implementation detail. 3. `watcher.rs`: rewrite stale inline comment that still credited `dunce::canonicalize` for UNC-prefix handling. 4. `paths.rs`: document that `relativize` silently drops non-Normal path components (`ParentDir`, `CurDir`) — the components-iterator filter introduced in 044e156 changed this from path-slash's preserve-all behavior. All current callers feed canonicalized OS paths where this is irrelevant; the doc update encodes the contract for future callers.
…nonicalize round-trip L5 step 2/3 — path consolidation (lib-audit follow-up spec §2 L5). Tag NFC normalization in `crates/core/src/tag.rs` is unchanged — tracked as a separate post-v1 concern (filed in Task 8 as a GH issue). ## MediaPath::new semantic change Pre-L5 invariant: `MediaPath::new(s) == MediaPath::new(NFD(s))` across all Unicode spellings, implemented via `unicode_normalization::UnicodeNormalization::nfc`. Post-L5 invariant: `MediaPath::new` is lexical-only (forward-slash, strip leading `/`). NFC/NFD equivalence is deferred to the filesystem layer via `fs::canonicalize`, which round-trips the OS's own normalization (HFS+ stores NFD; APFS stores caller form but dirent lookup is normalization-insensitive via hash-of-normalized per the Apple APFS FAQ; NTFS normalizes at the Win32 boundary; ext4 is byte-exact). Production MediaPaths are constructed downstream of canonicalized walk paths (see `crates/cli/src/cmd/scan.rs::canonicalize_for_walk` and peers), so they receive platform-consistent bytes. ## macOS HFS+ user impact A macOS user with an existing perima DB containing NFC-normalized MediaPaths (populated pre-L5) may see duplicate `file_locations` rows if they re-scan on HFS+: the canonicalized walk returns NFD, and the post-L5 MediaPath::new preserves those bytes. Pre-v1 perima carries no migration-stability commitment; tracked as a phase-9-hardening concern (GH issue filed in Task 8). APFS defaults since macOS 10.13; HFS+ impact is narrow but real. FTS5 search index has the same duplicate-row issue — filed as part of the phase-9 migration issue body. ## Test strategy The proptest that asserted `MediaPath::new(NFC) == MediaPath::new(NFD)` is replaced with a macOS-only `fs::canonicalize` round-trip test (creates an NFC-named fixture in a tempdir, resolves both NFC and NFD spellings, asserts they produce the same stored dirent name). On Linux + Windows the test asserts the weaker byte-identity invariant only. ## Deviations Spec acceptance `cargo tree -i unicode-normalization returns empty` is NOT met: `crates/core/src/tag.rs` still uses the crate for user- typed tag NFC normalization, a scope-distinct concern. Follow-up filed in Task 8.
Two independent code-quality reviewers on commit e170c04 flagged five minor doc / Cargo.toml items. All inline-threshold fixes. 1. `crates/core/Cargo.toml`: switch `tempfile = "3"` to `tempfile.workspace = true` for consistency with every other crate's dev-dep style (the workspace root already pins tempfile). 2. `types.rs` `MediaPath` struct doc: replace the misleading `fs::canonicalize` mention with the accurate `dunce::canonicalize` (delegates to std on non-Windows; strips `\\?\` on Windows). Also hedge the "MediaPath is a machine- derived type" line to "rare in production; this type is typically machine-derived from canonicalized walk output" — test-fixture call sites construct from string literals. 3. `types.rs` `DiscoveredFile.relative_path` doc: drop "post-L5" historical-event jargon in favor of an evergreen reference to the proptest file's mechanism doc. 4. `props_path_nfc_equivalence.rs` module doc: soften the APFS mechanism wording to "macOS VFS layer is normalization- insensitive" (the "hash of canonically-normalized filename" claim over-attributes to APFS itself vs. the VFS). 5. `props_path_nfc_equivalence.rs` module doc: explicitly flag that the off-macOS branch is a trivial byte-identity test kept to keep the file non-empty; real coverage lives in the macOS path. No behavior change; all changes are doc comments + Cargo.toml style.
L5 step 3/5 — path consolidation (lib-audit follow-up spec §2 L5). cli's 4 canonicalize call sites now route through `perima_fs::platform_path::canonicalize` — the `pub` helper introduced in 044e156. dunce stays as a direct dep only on crates/fs, cfg-gated to Windows. Single source of truth for the #[cfg(windows)] dunce/std fallback; no per-crate copy maintained in cli. Call sites swapped: - cmd/scan.rs (canonicalize_for_walk) - cmd/watch.rs (canonicalize) - cmd/metadata.rs - cmd/tag.rs Also fix pre-existing clippy::ptr_arg in tag.rs (&PathBuf → &Path on three private helpers) surfaced by -D warnings.
…_path L5 step 4/5 — path consolidation (lib-audit follow-up spec §2 L5). Mirrors commit dc20a0f (cli). desktop's canonicalize call sites in commands.rs now route through `perima_fs::platform_path::canonicalize` — the `pub` helper introduced in 044e156. dunce stays as a direct dep only on crates/fs, cfg-gated to Windows. Single source of truth for the #[cfg(windows)] dunce / std fallback; no per-crate copy maintained in desktop. Call sites swapped: 2 in commands.rs.
L5 step 5/5 — path consolidation (lib-audit follow-up spec §2 L5). Closes the L5 paperwork: - `Cargo.toml`'s workspace `unicode-normalization` entry now has a WHY comment pointing to GH #112 for the long-term tag-NFC strategy question. - `crates/core/src/tag.rs` gains a WHY comment at the unicode-normalization import pointing to the same issue, plus a cross-reference to the macOS NFD migration issue for completeness. AGENTS.md's L5 pin block was authored but NOT committed — the file matches the `**/*.md` gitignore rule without an exception, per an earlier user directive to keep AGENTS.md working-tree-only. Future sessions on this machine still see it; cloud / fresh-clone agents won't (accepted limitation from Batch A). Two GH issues filed: - #112 Re-evaluate tag NFC normalization strategy (post-v1) - #113 macOS NFD-vs-NFC file_locations + FTS5 duplication on re-scan (phase-9 hardening)
L2 step 2/4 — frontend modernization bundle (lib-audit follow-up spec §2 L2). PR-2 closeout. - vite: ^6 → ^8 (resolved 8.0.9). Rolldown-default; eliminates the esbuild dev / Rollup prod bundler split. - @vitejs/plugin-react: ^4 → ^6 (resolved 6.0.1). Retains Babel pass for React Compiler (wired in PR-3); React Refresh switched to Oxc. - Node engines range bumped to Vite 8 minimum (>=20.19 <21 || >=22.12). - vitest pin tightened from ^3 to ^3.2 (resolved 3.2.4, meets Vite 8 floor per audit §Q37). - CI Node pinned to 22.12 via a new actions/setup-node@v4 step positioned before oven-sh/setup-bun@v2 (runner previously used an unpinned default not guaranteed to meet Vite 8's floor). Bundle size delta: 227,232 bytes (was 231,269 bytes, -1.7%, within ±5%). @tailwindcss/vite peer range verified compatible with Vite 8 (^5.2.0 || ^6 || ^7 || ^8 — pre-flight gate). 68/68 tests, lint, build all green post-bump.
Spec deviation fix for L2 PR-2. Spec §2 L2 line 154 mandates "Bump `packageManager: "bun@1.3.11"` pin (audit §Q36)". The field was absent from apps/desktop/package.json, so the prior commit (57c3752) silently dropped the requirement as YAGNI. This adds the field so Corepack / package-manager detection resolves to the audit-pinned version. `bun install` is a no-op (0 changes); lint still clean.
L2 step 3/4 — frontend modernization bundle (lib-audit follow-up
spec §2 L2). PR-3 closeout.
- babel-plugin-react-compiler added at EXACT pin =1.0.0 (not ^).
- @rolldown/plugin-babel@^0.2.3 added as peer; @babel/core@^7 and
@babel/preset-typescript@^7 added as dev deps (needed by the canary
test described below).
- vite.config.ts: plugin-react v6 removed Babel from the default
pipeline (React Refresh now runs through Oxc). The Compiler re-
adds Babel as a SEPARATE plugin via `@rolldown/plugin-babel`
configured with `reactCompilerPreset({ target: "19" })` — the
canonical v6 wiring per
https://github.com/vitejs/vite-plugin-react/blob/plugin-react@6.0.1/packages/plugin-react/README.md
Compiler runs in default (infer) mode — auto-memoizes every
component that satisfies rules-of-react. `target: "19"` is
pinned explicitly so a future minor can't silently flip the
runtime emit path.
- Spec-mandated canary (src/__tests__/compiler-canary.test.ts)
runs babel-plugin-react-compiler in annotation mode over a
known-good `"use memo"` snippet (directive inside function body,
not at program level — Compiler treats it as a function-level
directive) and asserts the transformed output imports from
`react/compiler-runtime` (React 19's built-in runtime path).
This is stable against minifier renames and decoupled from Vite
build state — proves the plugin chain itself works.
Bundle size delta: 227,232 → 234,711 bytes raw (+7,479 bytes;
well under the 20 KB flag threshold). The delta is the Compiler's
react/compiler-runtime cache machinery.
## What this enables
Manual useCallback / useMemo are now an anti-pattern for new
components; the Compiler handles memoization. Existing manual
memos (currently 1: App.tsx useCallback) stay for now — a follow-up
GH issue will audit and remove them once Compiler adoption is
proven in practice.
## What this doesn't change
No source-code changes to src/**. The Compiler is transparent at
source level; only the transform pipeline added the extra Babel
plugin.
L2 step 3/4 follow-up. Applies reviewer nits (CHANGES_REQUESTED from Opus code-quality reviewer #2) to commit ceb508a. - Add @types/babel__core: fixes TS7016 error that broke `tsc -b` (and thus `bun run build`) in ceb508a. Canary test was green under vitest (no typecheck) but production build failed. - Add @babel/plugin-syntax-jsx as explicit devDep: it was being resolved transitively through @babel/preset-typescript, a phantom-dep anti-pattern that would break on a future hoist change. Now declared directly. - Add src/vendor.d.ts with ambient module declaration for @babel/plugin-syntax-jsx: no @types/babel__plugin-syntax-jsx exists on DefinitelyTyped; ambient decl typed as `object` to match @types/babel__core's PluginTarget union. - Remove file-scoped `eslint-disable no-unsafe-*` block from the canary: no longer needed with @types/babel__core present. Also applies code-quality nits: - Reinstate the `// WHY:` comment on vitest's `dist-types` exclude in vite.config.ts (dropped during ceb508a's config rewrite). - Add a `// WHY: keep in sync with vite.config.ts` comment on the canary's `target: "19"` to prevent silent drift if the prod target is bumped without updating the canary. - Add a `// WHY:` comment on reactCompilerPreset's implicit `panicThreshold: "none"` default. Verified: `bun run build` (exit 0), `bun test` (58/58 src pass, canary included), `bun run lint` (clean).
L2 step 4/4 — frontend modernization bundle (lib-audit follow-up spec §2 L2). PR-4 closeout. eslint.config.js previously carried an unfulfilled "Follow-up: file issue for each warn below" comment. Filed GH issue #116 as an umbrella for the 13 warn-downgraded v7 rules; link from the comment. Nothing else in L2 required code changes: Tailwind v4 + ESLint 9 + react-hooks v7.1.1 + `recommended-latest` preset were already in place prior to this batch. Vite 8 bump landed in commit 57c3752; React Compiler 1.0 landed in commit ceb508a. AGENTS.md's 5 L2 pin entries were authored in the working tree but not committed (file is gitignored by `**/*.md` rule; per user directive AGENTS.md stays working-tree-only). Cloud / fresh-clone agents won't see the pins (accepted limitation from Batch A). `eslint-plugin-react-compiler` is not installed (verified empty in package.json + bun.lock) — superseded by react-hooks v7.1 which ships Compiler-aware rules. Current fire count for the 13 warn-downgraded rules: 0 (P2/cosmetic).
…e-minor
L6 — lib-audit follow-up spec §2 L6 line 289 calls for tightening
plugin pins. Previously the three workspace-level pins were loose
(`"2"` = `^2.0.0` = any 2.x), which allowed `cargo update` to bump
Tauri 2.10.3 → 2.11 or plugin-dialog 2.7.0 → 2.8.0 between sessions
with no review.
New pins (tilde-minor = `>=major.minor, <major.(minor+1)`):
- tauri : ~2.10 (lockfile 2.10.3)
- tauri-build : ~2.5 (lockfile 2.5.6 — its own minor track,
NOT 2.10.x; tauri-build has its own
release cadence)
- tauri-plugin-dialog: ~2.7 (lockfile 2.7.0 — `plugins-workspace`
plugins have independent minor cadence
per plugin, not lockstep with core)
Intent: gate minor bumps behind an explicit bump PR. Tauri 2.11 is
imminent (2.10.3 cut 2026-03-04); this pin deliberately BLOCKS its
silent adoption so a future session has to consciously re-test
against it. Not transitional — load-bearing.
No runtime change; Cargo.lock resolved the same versions pre- and
post-pin.
Note: `cargo build -p perima-desktop` and `cargo clippy --workspace`
including perima-desktop require GTK3/GLib/WebKit2GTK system libs
absent in this VM. Resolver-level validation (`cargo generate-lockfile`,
`cargo tree`, and non-desktop clippy + tests) all passed. CI installs
these libs and will verify full build.
CI macOS runner (Rust 1.95 clippy) flagged `nfc_name` / `nfd_name` and `canon_nfc` / `canon_nfd` bindings in the `#[cfg(target_os = "macos")]` test body as too-similar. The names are domain terms of art — Unicode NFC (precomposed) and NFD (decomposed) forms — so renaming to `precomposed` / `decomposed` loses the terminology. Scoped allow on the test function only; Ubuntu CI does not compile this branch and was clean pre-fix.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the library/stack-audit follow-up work tracked in #80 — 8 ranked items (L1-L8) landing as 19 commits. Each batch was reviewed independently (spec-compliance + code-quality Opus reviewers, sequential loop per
feedback_per_batch_review_loop.md).What changed, at a glance:
@vitejs/plugin-react4 → 6 (Oxc Refresh + Babel Compiler), React Compiler 1.0 (babel-plugin-react-compiler=1.0.0exact),bun@1.3.11pin, CIactions/setup-node@v4 22.12. Compiler-transform canary test confirmsreact/compiler-runtimeemission. 69/69 vitest pass.kamadak-exif→nom-exif 2.7(single-extractor path for EXIF + video).fast_image_resize 6.0for thumbnailing (3-10× faster Lanczos3; SSIM-equivalent output).path-slash, droppedunicode-normalizationfrom path code (replaced byfs::canonicalizeround-trip), Windows-onlyduncescoped toperima_fs::platform_path.camino+normpathdeferred to concrete-consumer PR.tauri-plugin-dialogwired;tauri/tauri-build/tauri-plugin-dialogpins tightened to tilde-minor (~2.10/~2.5/~2.7). Other plugins (fs/notification/os/store) deferred — tracked in Adopt remaining tauri-plugins-workspace plugins (fs / notification / os / store) when TODAY call-sites emerge #117.anyhow→mietteincrates/cli+crates/desktop;thiserrorretained in libs.Related issues
Test plan
Local (all green pre-push via lefthook):
cargo test --workspace— all 178+ tests pass.cargo clippy --workspace --all-targets -- -D warnings— clean.cargo deny check— advisories/bans/licenses/sources ok.cargo doc --no-deps --workspace— docs-coverage clean.bun run lint(--max-warnings 0) — clean.bun run build— tsc + Vite 8 build clean (218 KB / 68 KB gzip).bun run test— vitest 69/69 pass incl. Compiler transform-output canary.CI to validate on remote: