Skip to content

Refactor with Tauri 2#12

Merged
xianfei merged 17 commits into
masterfrom
tauri
Jul 2, 2026
Merged

Refactor with Tauri 2#12
xianfei merged 17 commits into
masterfrom
tauri

Conversation

@xianfei

@xianfei xianfei commented Jul 2, 2026

Copy link
Copy Markdown
Owner

No description provided.

xianfei and others added 17 commits July 2, 2026 17:15
Ports the ~800-line Electron main process to Rust while keeping the
React/Redux renderer intact behind the existing ImagineAPI bridge:

- src-tauri: commands (ready/file_add/file_select/optimize/save/sync/
  store/about/progress-bar/open_external), events (FILE_SELECTED/SAVE/
  SAVED), content-addressed tmp cache, dynamic localized menu (labels
  travel with SYNC so locales stay frontend-only), single-instance,
  macOS Opened + argv file associations, JSON config store
- Phase 1 image pipeline on the image crate (png/jpg/webp/bmp, resize
  modes); mozjpeg/quantization/avif/heic parity lands in Phase 2
- modules/bridge/tauri.ts implements ImagineAPI over invoke/listen:
  paths are converted with convertFileSrc (asset protocol scoped to
  $TEMP/imagine), store-get stays sync via a boot-primed config cache,
  update check moves to a renderer-side GitHub API fetch
- native drag-drop via onDragDropEvent (webviews have no File.path),
  data-tauri-drag-region on the action bar, async bridgeReady boot
- jest stubs @tauri-apps/api (ESM); suite results match baseline

macOS bundle: 6.7MB .app / 3.0MB dmg (Electron: ~100MB+).
Run with: npm run tauri:dev / npm run tauri:build

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Feature parity with the sharp/libvips backend, statically linked:

- JPEG: mozjpeg (same encoder as sharp's mozjpeg:true), quality +
  progressive; output size verified within 1% of sharp on a 12MP test
  (134.0KB vs 133.2KB at q80/resize-1600)
- PNG: palette quantization for the `colors` option β€” quantette
  (Wu+kmeans+Floyd-Steinberg, MIT) for opaque images, NeuQuant for
  RGBA-with-alpha; interlaced output unsupported by the png crate
  (documented divergence). imagequant (GPL-3.0) noted in README as the
  quality upgrade if relicensing is acceptable
- WebP: lossy libwebp; AVIF: ravif/rav1e with EXIF embedding
- HEIC/AVIF input: macOS ImageIO (sips) decode to a cached PNG
  intermediate that doubles as the HEIC preview (sourcePreviewUrl,
  same contract as the Electron backend); other platforms error
  clearly until a WIC/webview fallback lands
- resize: fast_image_resize SIMD Lanczos3, three modes ported with
  no-enlargement semantics
- metadata: EXIF/ICC extract+embed via img-parts for jpg/png/webp
- 8 pipeline tests incl. HEIC round-trip and metadata roundtrip;
  optional bench harness (IMAGINE_BENCH_SRC)

macOS bundle with all codecs: 9.0MB .app / 4.0MB dmg.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two majors and a batch of parity gaps confirmed by a 4-dimension
review (IPC contract, Electron regression, Rust correctness, UX flows)
with per-finding adversarial verification:

- Fix pending-files flush race: the ready check and pending-queue
  insert now happen under one mutex, so files ingested around renderer
  READY can no longer be dropped or stuck
- Restore alone-mode close interception: Tauri never fires beforeunload
  on window close, so the bridge replays onCloseRequested as a
  cancelable synthetic beforeunload (Alone.tsx unchanged, works on both
  runtimes); add core:window:allow-destroy capability
- READY now carries localized menu labels: the app menu is localized
  from first paint instead of after the first Redux sync
- Add File > Open Folder… (folder_select command): Electron's Open
  dialog allowed picking directories on macOS, Tauri's cannot mix
  files and folders
- AVIF export converts pixels to sRGB via qcms when the source has an
  ICC profile (ravif cannot embed ICC; colors now match sharp output)
- Gate heic/avif ingest + dialog filter behind native_decode support
  so non-macOS builds don't accept files they cannot decode
- reext parity: 'photo.jpeg' -> png now appends ('photo.jpeg.png')
  like the TS original; add reext unit tests
- Update check only fires on semver-greater remote (was: any string
  difference, could flag a downgrade right after a version bump)
- Move @tauri-apps/api to devDependencies (it is bundled by Vite;
  keeping it in dependencies shipped it inside the Electron asar)

cargo test 8 passed; tsc/vite/jest match baseline; smoke-tested.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One code path for opaque and alpha images (pngquant engine, same as
sharp), Floyd-Steinberg dithering built in; drops quantette and
color_quant. libimagequant is GPL-3.0-or-later β€” noted in README that
distributed Tauri builds are effectively GPL, per the decision to keep
the project open source.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Platforms without macOS ImageIO now decode in the webview before
optimize runs: libheif-js (WASM, lazy-loaded 1.4MB chunk) for HEIC,
the webview's own codec via canvas for AVIF (WebView2 is Chromium).
Raw RGBA travels over binary IPC (has_intermediate / read_source /
write_intermediate commands, id/dims in headers) and lands as the same
{id}.1.png intermediate the pipeline and the HEIC preview already use.
Ingest and dialog filters accept heic/avif on all platforms again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Frameless window on Windows (decorations(false) + shadow) with
  custom minimize / maximize / close buttons drawn in the action bar,
  Windows-styled (red close hover); close() goes through the
  close-requested path so alone-mode interception still works. The
  bridge exposes windowControls only where needed, so macOS traffic
  lights and Electron's titleBarOverlay are untouched
- Fix drag region: data-tauri-drag-region only applies to the exact
  mousedown target, and the bar's biggest surfaces (.blank spacer,
  title text) lacked it β€” dragging now works across the whole bar,
  double-click-to-maximize included

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The previous set mixed Material fill glyphs, Material Symbols
(960-viewBox), and ad-hoc shapes with viewBoxes from 8x8 to 147x147.
All 20 icons are now hand-drawn on the same 24x24 grid with 2px
round-cap strokes in currentColor; the processing spinner and rainbow
keep their color but use the same stroke language and a matched flat
palette. Explicit fill/stroke attributes on each group override the
`.icon { fill: currentColor }` CSS, so no component changes needed.
Removes unused resize1.svg. Same benefit applies to the Electron
build since the renderer is shared.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Root cause: core:window:default does NOT include allow-start-dragging
(while internal-toggle-maximize is included), so every mousedown on a
data-tauri-drag-region element invoked plugin:window|start_dragging
and was silently denied by the ACL β€” the window never moved. Grant
core:window:allow-start-dragging in the default capability.

Also switch the action bar to data-tauri-drag-region="deep" (supported
by this Tauri version's drag script): the entire bar subtree drags,
clickable elements are excluded automatically, so Tooltip wrapper
spans and future children can't create dead zones. Poppers render
through a portal and are unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the cramped OptionsPanel popper with a fullscreen settings
page (ESC / back to close), reorganized around what the data model
already meant:

- Format conversion: one input->output mapping row per source format,
  now including HEIC and BMP which had no mapping UI at all (the old
  panel's heic handler even wrote to the jpg entry by mistake)
- Output options: encoding parameters keyed by OUTPUT format (JPEG
  quality, PNG colors, WebP/AVIF quality) β€” matching how task options
  are actually resolved (defaultOptions[exportExt])
- General: keep metadata, progressive/interlaced, update check, plus
  two new settings: UI language override (14 locales, stored in
  localStorage, applied on reload) and parallel task count (auto =
  cores-1, read live by the job runner)

New locale keys added to en/zh-CN; other languages fall back to
English. Pure UI reorganization β€” reducer/state shape unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Settings title now sits on the same row as the modal back button in
  a fixed header strip (which is also a drag region, so the window
  stays draggable while settings cover the action bar)
- Format conversion rows flow in a two-column grid; the compact
  task-card select style is overridden with a full-width, normal-size,
  left-aligned variant inside the grid
- macOS traffic lights moved from y=17 to y=19 to vertically center
  the 12px buttons in the 50px bar

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Rust backend has full feature parity, so the Node main process
goes away entirely: backend/, optimizers/, bootstrap, preload,
async-call, dev launcher, file-utils/env and their tests. Node.js
remains a build tool only (Vite bundles the renderer) β€” the shipped
app contains no Node runtime.

- package.json: 10 runtime deps (sharp, electron-log/store/updater,
  fs-extra, file-type, heic-decode, ...) and 22 dev deps (electron,
  electron-builder, webpack-era loaders, ...) removed β€” 638 packages
  gone from node_modules; electron scripts and builder config replaced
  by dev/build = tauri dev/build
- electron type imports replaced with local IUpdateInfo and ILogger;
  setProgressBar/store-get/store-set/about are now typed IPC channels
  (killing the @ts-ignore workarounds)
- fixed the two long-failing jest tests: job-runner now mocks the
  bridge (optimize moved off apis long ago), store.test expectations
  updated for the avif/heic/bmp formats β€” suite is 18/18 green
- eslint cleanup: --fix plus manual fixes for @ts-nocheck, unused
  members and naming β€” npm run lint passes with 0 errors for the
  first time
- README build section rewritten for the Tauri toolchain

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- ci.yml: on every push/PR β€” typecheck, eslint, jest and a Vite build
  (frontend job), plus cargo test and a debug build against
  webkit2gtk-4.1 (rust job, with rust-cache)
- release.yml: on v* tags β€” tauri-action builds and uploads bundles
  for macOS arm64 + x86_64, Windows and Linux to the GitHub release
- removes the electron-builder workflow

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sync the app version across `package.json`, `src-tauri/Cargo.toml`, and `src-tauri/tauri.conf.json`. Also add a `.DS_Store` ignore entry for macOS metadata files.
rav1e's asm feature assembles x86_64 SIMD with NASM, which GitHub
runners don't ship β€” the Linux/Windows/mac-x64 builds failed while
local arm64 builds (NEON path) never hit it. ilammy/setup-nasm covers
all three OSes; disabling the asm feature instead would make AVIF
encoding several times slower.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New bundle job builds release packages on every push (Windows NSIS/MSI,
macOS dmg, Linux AppImage/deb) and uploads them to the run's Artifacts
section, so a build can be tried on another machine without any local
toolchain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The primary action now sits top-right in the fixed header β€” always
visible instead of requiring a scroll to the bottom β€” leaving the
footer with just the version tag.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- The bundle matrix now builds both mac architectures via explicit
  --target triples (per-target output paths, per-entry rust-cache
  keys) alongside Windows and Linux
- The bare Windows exe rides in the artifact as the portable
  (no-install) build β€” Tauri embeds the frontend in the binary, so it
  runs anywhere with WebView2 present
- release.yml attaches the same portable exe to tagged releases as
  ImaginePlus_<version>_x64-portable.exe (tauri-action only uploads
  bundler outputs)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@xianfei xianfei merged commit 5c17613 into master Jul 2, 2026
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant