Skip to content

v0.7.0 — English + Polish UI, AMD encoding, dependency sweep

Choose a tag to compare

@Shironex Shironex released this 21 Aug 09:59
· 37 commits to main since this release

Localization and AMD hardware encoding, on top of a three-month dependency and security sweep. Every UI string now ships in English and Polish behind a language picker that lives in both Settings and the onboarding Welcome step. AMD GPUs finally get a hardware path — the encoder had no AMF branch at all, so an AMD-only machine on the default hwAccel:'nvenc' emitted h264_nvenc and failed with "unknown encoder"; AMF now has real args and the GPU probe is wired into the orchestrator so a detected encoder actually gets used. The probe also stopped taking ffmpeg's word for it: every detected encoder is confirmed with a 1-frame test-encode before it is offered. Alongside that, thirty-odd dependency commits took pnpm audit from 1 critical / 15 high / 20 moderate to zero, and carried Electron to 43, React to 19 and Astro to 7.

Added

  • UI localization (English + Polish) — every user-facing string across screens, chrome components and the 14-step onboarding is extracted into 14 namespaced en/pl locale files and routed through useTranslation. i18next initialises synchronously before React mounts (the init side-effect is imported ahead of App in the renderer entry, so a module-scope lookup can never race it). Decorative kanji/romaji eyebrows are left inline. A self-contained LanguagePicker appears in Settings and on the onboarding Welcome step.
  • uiLanguage setting — nullable, OS-detect-then-sticky: unset means "follow the OS", and the first explicit pick freezes the choice. useUiLanguage / useUiLanguageSync read a localStorage boot mirror for the first paint and treat electron-store as the durable source. The main-process queue-complete notification is localized off the persisted value, since it can't reach the renderer's i18next instance. Includes key-parity, plural and configured-instance tests, so a locale file that drifts out of sync fails CI.
  • AMF (AMD) H.264/HEVC encoding — new H264AmfSettings / HevcAmfSettings union members and h264_amf / hevc_amf branches in args.ts, written in AMF's own rate-control vocabulary (-quality {speed|balanced|quality} -rc cqp -qp_i/-qp_p/-qp_b, pixel format yuv420p, p010le for 10-bit HEVC) and verified against ffmpeg's amfenc source rather than analogized from NVENC. {codec:'av1',hwAccel:'amf'} is intentionally non-representable — ffmpeg has no av1_amf.
  • resolveHwAccel() and probe-driven encoder selection — a best-effort probeGpu dependency is wired into the orchestrator and soft-fails back to the requested settings when the probe is unavailable, mirroring the font-extractor pattern. This is what closes the AMD gap end to end: detection already worked, nothing consumed it.
  • Verified encoder detection — verifyEncoderWorks() runs a minimal 1-frame lavfi→null test-encode per detected encoder and keeps it only on a clean exit; non-zero exit, spawn error and timeout (SIGTERM→SIGKILL) all drop it. An advertised-but-broken hardware encoder is now caught at probe time instead of at the user's first real job. Results are cached in electron-store behind a version key with a 7-day TTL, so the test runs once rather than on every launch. The IPC contract gains a verified flag and a zod gpuProbeResultSchema.
  • Per-dialog last-used directory — UserSettings.lastDialogDirs maps a DialogDirKind (video / subtitle / save-file / output-folder) to the directory it was last opened at, so the media library, the subtitle folder and the output folder each keep separate memory. Folder picks remember the folder itself (reopening lands inside it); file picks remember dirname(). A cancelled dialog remembers nothing, a directory that no longer resolves is dropped at read time rather than handed to Electron dead, and an explicit defaultPath still wins over the remembered one.
  • Test coverage — desktop 321 passing / 2 skipped (was 263), web 51 (was 28). Includes dialog.test.ts (20 cases) for a channel that previously had none.

Changed

  • Electron 42.9.3 → 43.4.1. Five of the six entries in Electron's 43.0 breaking-change list resolve to zero call sites here (three are Linux-only and the build targets only win/mac; the NativeImage.toBitmap() sRGB change and the chrome.scripting fallback-frame change have no matching usage). The sixth — dialogs defaulting to the Downloads folder instead of the OS's remembered location — is what the per-dialog directory memory above was written to absorb, and it landed first, so the major itself carries no known UX regression.
  • React 18 → 19 across @moekoder/web and @moekoder/landing, bumped in lockstep so a single @types/react major exists in the store. No source changes were needed — the renderer was already on createRoot and every ref passes an initial argument.
  • Astro 6 → 7 on the landing app (Rust compiler, Vite 8), with @astrojs/react 5 → 6. Also tailwind-merge 2 → 3 (Tailwind v4 was already in place, so v2 was a latent class-group mismatch), sharp 0.34 → 0.35, concurrently 9 → 10, lint-staged 16 → 17, @types/yauzl 2 → 3, and electron-builder pinned to 26.15.7.
  • engines.node raised to >=22.22.1 — required by lint-staged 17's Node floor.
  • Icon set and OG card regenerated — the libvips 8.16 → 8.18.3 shift under sharp 0.35 moved a few pixels in icon-16.png / icon-32.png, and satori 0.33.0 made HarfBuzz shaping unconditional, which changes text measurement. Both artifacts are tracked, so both were re-rendered from the current toolchain.
  • GPU-probe verifications run sequentially. They were concurrent, which is faster in principle; see Fixed.

Fixed

  • Concurrent probe verification dropped working encoders — running the test-encodes through Promise.allSettled let them race on GPU-context initialisation and hit driver concurrent-session limits (NVENC / AMF / QSV), so a perfectly good encoder could be marked unavailable. They now run one at a time; a 1-frame test is under 100 ms, so total probe latency stays sub-second.
  • Polish word order in container labels — "{c.name} {containerSuffix}" rendered "MP4 kontener". The two fragments are now a single containerLabel key with a {{name}} placeholder, so each locale places the noun itself ("{{name}} container" / "kontener {{name}}").
  • Install-stage label was force-lowercased — .toLowerCase() on a translated string applies locale-independent casing rules (wrong for languages like Turkish) and overrides the translator's intent. The stage label renders as authored.
  • Queue retry button's accessible name — the retry aria-label pulled from the common namespace while its sibling buttons used queue-namespace keys; it now uses queue.retryLabel.
  • Inconsistent Windows install-path casing in onboarding copy — the engine-gate destination and dl.install.sub disagreed (moekoder vs MoeKoder); both now match APP_NAME, which is the actual %LOCALAPPDATA% folder name.
  • Landing OG card spacing under satori 0.33.0 — HarfBuzz shaping stopped applying letter-spacing to collapsible whitespace, collapsing the word gaps in the eyebrow and meta rows into letter gaps, and re-measured the 萌コーダー run 18px narrower, sliding the decorative rule left into the kana. Whitespace runs are now NBSP (which is shaped as a real glyph and still takes letter-spacing) and the width delta is compensated with padding. The card differs from the 0.26.0 baseline by 5 pixels out of 756,000, all antialiasing.

Security

pnpm audit on the full graph went from 1 critical / 15 high / 20 moderate / 1 low to 0 vulnerabilities, with no ignoreCves suppressions left in place.

  • electron-updater 6.8.3 → 6.8.9 — the highest-priority fix in the sweep, since it runs on end-user machines. Pulls builder-util-runtime 9.7.0, closing GHSA-p2f4-r6v6-j797 (a cross-origin redirect leaks Authorization / PRIVATE-TOKEN headers) plus a js-yaml quadratic-DoS advisory.
  • Electron 42.5.0 → 42.8.1 ahead of the 43 bump closed CVE-2026-70606 (protocol-handler session-isolation bypass). No custom protocol handlers exist here, so the surface wasn't reachable either way.
  • pnpm.overrides floors raised and bounded — fast-uri, shell-quote, form-data, brace-expansion, undici, tmp, svgo, yaml, js-yaml, nanoid, vite, @babel/core, devalue, ip-address. Several floors were unbounded above and pnpm had resolved them past what their consumers declare (nanoid to an ESM-only v6 behind postcss's ^3.3.17, @babel/core to v8 behind @vitejs/plugin-react's ^7.29.0); each is now capped at the reviewed major.
  • A stale electron-builder-squirrel-windows@26.8.1 chain was collapsed — it dragged in its own old app-builder-lib with vulnerable tar, js-yaml, fast-uri and builder-util-runtime, duplicating packages the top-level resolution had already fixed. Because it and app-builder-lib pin each other as exact mutual peers, pnpm kept reusing the stale snapshot; adding it as an explicit devDependency gave the resolver a real edge and cleared the critical tar advisory along with several highs.
  • Dev-path fixes — vite 8.0.13 → 8.1.5 (CVE-2026-53571, server.fs.deny bypass on Windows alternate paths), esbuild 0.28.0 → 0.28.1 (GHSA-g7r4-m6w7-qqqr, arbitrary file read via the dev server), wait-on 9.0.10 → 9.1.0 (clears all 8 axios advisories), and astro 7.0.4 → 7.1.3 (3 moderates, cascading svgo and postcss fixes).
  • Full audit trail: docs/deps/2026-08-06-audit-standard-2026-08.md, docs/deps/2026-08-20-audit-standard.md, docs/migrate/2026-08-20-electron-43.md, docs/migrate/2026-08-20-satori-0.33.0.md.

Known Limitations

  • The AMF encoder path has not been validated on real AMD hardware. There is no AMD GPU in CI, so args.test.ts asserts arg shape only — the flags were derived from ffmpeg's amfenc source, not from a successful encode. If you have an AMD card, a 1-frame encode confirming ffmpeg accepts these flags is the single most useful bug report you can file against this release.
  • Multiple subtitle tracks in a single mux is still out of scope — one external .ass per mux. It was pencilled in for v0.7 and did not make it.
  • Extraction is still copy-or-transcode between text formats only; image subtitles (PGS / VOBSUB) can't be exported.
  • Same as v0.5.x and v0.6.0: binaries are unsigned, the auto-updater is Windows-only, and there's no Linux build yet. Embedded-font extraction still runs per-job (no <userData>/fonts/<sourceHash>/ cache).