Changelog

Every release, in the open.

What shipped, what changed, and any build we had to pull. Pulled straight from GitHub Releases — no curation.

  1. v1.0.0 — First stable release

    May 26, 2026

    First stable release 🎉

    v1.0.0 is a commitment to API + UX stability, not a re-implementation. The bundle is byte-identical in behaviour to v0.11.1 — same code path, same backend wire format, same prompts, same hotkey UX. The 1.0.0 number marks the boundary where the app is feature-complete + production-hardened.

    What 1.0.0 covers

    • Feature set is complete for every workflow the app was built for:

    - Inbound translate (selection → user's primary language) - Outbound translate (per-persona, per-tone) - Contextual rewrite (per-tone bindings + tone picker) - OCR capture (screen region → translate) - App Intents (Shortcuts.app + Spotlight + Siri) - Prompt Engineer mode (VN keywords → EN prompt for AI coding assistants)

    • Dual-mode translation backend:

    - Bring-your-own-API direct providers: Gemini, OpenAI-compatible, Ollama, DeepL, Google Translate, LibreTranslate - SaaS-hosted Contextual MT Cloud with quota / device cap / per-user encrypted cache

    • Distribution pipeline is hardened end-to-end: Apple Developer-ID signed, Apple-notarized, stapled, AppleDouble-clean ZIP + DMG, Sparkle EdDSA auto-update, GitHub Pages workflow deploy for the appcast.
    • 400 tests guard the public contract.

    What 1.0.0 means going forward

    | Bump | When | |---|---| | Patch (1.0.1, 1.0.2, …) | Bug fixes | | Minor (1.1.0, 1.2.0, …) | Backward-compatible feature additions | | Major (2.0.0) | Breaking changes to UserDefaults / Keychain layout / backend wire format |

    The on-disk layout (UserDefaults keys, Keychain accounts, file names, signing identifiers) and the backend wire format (direction field values, dual-emit body shape, snake_case + camelCase parity) are now considered stable surface area.

    No behaviour changes vs v0.11.1

    Just the version string + this entry. v0.11.0 → v0.11.1 → v1.0.0 upgrade is the cheapest in the app's history.

    Install

    • Recommended: download the DMG below — open, drag the app into Applications, launch.
    • Sparkle auto-updates from v0.10.3+ pull this in within an hour.

    Both DMG and ZIP are signed (Developer ID JP4XV8A9JF), notarized by Apple, and stapled. Apple Silicon only. macOS 14 or later.

    Thanks

    The 6 releases shipped today (v0.10.1 → v1.0.0) closed the long tail of issues that kept the app in alpha: SaaS backend wire format, AppleDouble Gatekeeper trap, TCC permission recovery, transparent-titlebar regression, Input Monitoring removal, SaaS rewrite + prompt expansion, pronoun preservation. The app is ready for daily use.

    View on GitHub →
  2. v0.11.1 — Rewrite pronoun preservation

    May 26, 2026

    Rewrite pronoun-preservation hotfix

    User-reported bug: a draft addressed with "em" was coming back as "anh/chị" (or vice versa) after a rewrite, inverting the entire social relationship between speaker and addressee. In Vietnamese pronouns ARE the relationship — switching them is a correctness failure, not a style choice.

    Root cause

    The pronoun-preservation rule was a single buried line in a bullet list AND two of the few-shot examples in the prompt itself silently switched "tôi" → "em", actively teaching the model the opposite of what the rule said. The Supabase server prompt didn't mention Vietnamese pronouns at all.

    Fixed — Mac app PromptBuilder.rewriteSystemPrompt

    • Pronoun-preservation rule promoted from a bullet to an ABSOLUTE rule — PRONOUNS: top-level section with explicit per-pronoun guarantees: "em" stays "em", "tôi" stays "tôi", "mình" stays "mình", every addressing pronoun (anh / chị / em / cháu / bạn / mình / quý khách / ông / bà / thầy / cô) preserved verbatim.
    • Example #2 fixed: input uses "tôi" → output now keeps "tôi" (was wrongly outputting "em").
    • Example #3 fixed: input uses "tôi" → output now keeps "tôi" (was wrongly outputting "em").
    • New example #4 demonstrates "em → anh" fidelity across a friendly tone shift.
    • Other-language formality markers ("tu"/"vous", "tú"/"usted", "你"/"您", "ты"/"вы") added so the fix isn't VN-only.

    Fixed — Supabase REWRITE_SYSTEM_PROMPT (already deployed)

    • Server-side prompt was about a third the size of the Mac-side prompt and had no pronoun rule at all. Now mirrors PromptBuilder verbatim so SaaS users get the same pronoun-preserving quality as direct-API users.

    Tests

    • rewriteSystemPromptHasFewShot updated to pin the ABSOLUTE pronoun-rule heading + per-pronoun guarantee strings so a future prompt refactor cannot quietly drop the protection. 400 tests pass.

    Risk-free upgrade

    UserDefaults + Keychain layout unchanged from v0.11.0.

    Install

    • Recommended: download the DMG below — open, drag the app into Applications, launch.
    • Sparkle auto-updates from v0.10.3+ work.

    Both DMG and ZIP are signed (Developer ID JP4XV8A9JF), notarized by Apple, and stapled. Apple Silicon only. macOS 14 or later.

    View on GitHub →
  3. v0.11.0 — Prompt Engineer mode

    Pre-releaseMay 26, 2026

    Prompt Engineer mode

    Type a minimal Vietnamese keyword sketch of a coding task. Press a hotkey. The app pastes back a complete, well-structured English prompt ready for Claude Code, Codex, ChatGPT, or Claude Desktop.

    Same hotkey machinery as translate/rewrite — different system prompt, different temperature, different intent. This release makes the use case documented at [contextmt.dev/recipes/prompt-expander](https://contextmt.dev/recipes/prompt-expander) first-class shipping infrastructure.

    Added

    • `TranslationDirection.expand` enum case + new `PromptBinding` data type (id, name, hotkey, targetLanguage default en, styleInstruction defaults to shared template when blank).
    • Settings → Prompt Engineer section parallel to Contextual rewrite. Per-binding rows: name, target language, optional custom expansion guidelines, hotkey recorder, delete. Gated on rewriteAvailable.
    • `TranslationWorkflow.expandAndSend(binding:)` — capture selection → translate with direction = .expand → always preview (output is N× longer than input) → paste on confirm.
    • `AppDelegate` registers settings.promptBindings on the same gate as rewrite bindings.
    • `PromptBuilder.expandSystemPrompt` mirrors the Supabase EXPAND_SYSTEM_PROMPT so direct-API providers (Gemini direct, OpenAI direct) get the same expansion quality without a backend round-trip.

    Server side (already deployed)

    • Supabase translate Edge Function accepts direction: "expand", swaps to EXPAND_SYSTEM_PROMPT, bumps temperature 0.3 → 0.6, and drops the Source language line from the user prompt. Cache key includes direction so expand / rewrite / translate of the same input get separate slots.

    Tests

    • +10 new testsPromptBindingTests (7: Codable, default fallback, custom precedence, style() pinning, persistence, conflict detection), ExpandDirectionWireFormatTests (2: raw value + JSON shape), BackendStreamingTests (1: end-to-end direction=expand reaches wire body).
    • 400 tests pass (was 390).

    Risk-free upgrade

    UserDefaults adds one new key (translator.promptBindings) defaulting to empty array. Users opt in by adding a binding in Settings → Prompt Engineer. No migration needed.

    Install

    • Recommended: download the DMG below — open, drag the app into Applications, launch.
    • Sparkle auto-updates from v0.10.3+ work.

    Both DMG and ZIP are signed (Developer ID JP4XV8A9JF), notarized by Apple, and stapled. Apple Silicon only. macOS 14 or later.

    How to use

    1. Settings → Prompt Engineer → Add binding. A default hotkey (⌥P) is suggested; change with the recorder if it collides. 2. Optionally open Custom expansion guidelines to override the default template (e.g. pin a specific tech stack: "I work in Swift 6 + SwiftUI on macOS 14+"). 3. From any chat input field (Claude Code, Codex web UI, ChatGPT, etc.), type a minimal sketch in Vietnamese — fix bug login keychain swift, prompt password lặp — then press ⌥P. The PreviewHUD shows the expanded prompt; confirm to paste.

    View on GitHub →
  4. v0.10.6 — Rewrite on SaaS backend

    Pre-releaseMay 26, 2026

    Rewrite works on SaaS backend

    User-reported bug: pressing a tone rewrite hotkey (Polite / Professional / Friendly etc.) did nothing when the app was configured to use Contextual MT Cloud or a self-hosted backend. The hotkey registration was silently skipped because the historical rewriteAvailable gate hardcoded "only direct-API providers can rewrite".

    Fixed — Mac app

    • SettingsStore.rewriteAvailable now returns true for both customBackend and firstPartyBackend translation sources. Direct API providers stay gated on each provider's individual supportsRewrite flag.

    Added — SaaS backend (already deployed)

    • The Supabase translate Edge Function accepts a new direction: "rewrite" field that switches Gemini to a same-language tone-rewrite system prompt and drops Source/Target language lines from the user prompt (otherwise the model would translate VN→VN instead of adjusting tone).
    • Backward-compatible: any other direction value (including missing or the older "inbound" / "outbound" raw values) lands on the regular translate path.
    • Cache key includes direction so a translate-of-X never returns a previously-cached rewrite-of-X.

    Internal

    • Refactored the v0.10.4 auto-open Settings tests to drive the decision synchronously via a new checkAutoOpenSettings() method on PermissionManager. Production code still schedules a 1.5 s grace task; tests just exercise the decision body deterministically (the original async version raced the cooperative scheduler in full-suite runs).
    • 390 tests pass.

    Risk-free upgrade

    UserDefaults + Keychain layout unchanged from v0.10.5.

    Install

    • Recommended: download the DMG below — open, drag the app into Applications, launch.
    • Sparkle auto-updates from v0.10.3 onwards work.

    Both DMG and ZIP are signed (Developer ID JP4XV8A9JF), notarized by Apple, and stapled. Apple Silicon only. macOS 14 or later.

    View on GitHub →
  5. v0.10.5 — Window chrome hotfix

    Pre-releaseMay 26, 2026

    Window chrome hotfix

    Three app windows (Onboarding, Settings, What's New) carried a transparent-titlebar / clear-background combination from an earlier macOS 26 "Liquid Glass" experiment. On real macOS, the title text rendered straight onto the desktop wallpaper with zero contrast — unreadable. User-visible symptom: top bar fully transparent, app title overlapping window controls and desktop content.

    Fixed

    • OnboardingWindowController, SettingsWindowController, WhatsNewWindowController — reverted the transparent-titlebar settings. Standard opaque titlebar restored on all three windows. The SwiftUI content's liquidGlassBackground modifier still applies its material to the content area on macOS 26; only the titlebar chrome changes.

    Tests

    • Bumped two PermissionManager test sleeps 2s → 3s to absorb scheduler jitter that produced intermittent zero-count failures on the auto-open-Settings contract added in v0.10.4. 390 tests pass.

    Risk-free upgrade

    UserDefaults + Keychain layout unchanged from v0.10.4.

    Install

    • Recommended: download the DMG below — open, drag the app into Applications, launch.
    • Sparkle auto-updates from v0.10.3 onwards work.

    Both DMG and ZIP are signed (Developer ID JP4XV8A9JF), notarized by Apple, and stapled. Apple Silicon only. macOS 14 or later.

    View on GitHub →
  6. v0.10.4 — Permission UX cleanup

    Pre-releaseMay 26, 2026

    Permission UX cleanup

    Drops Input Monitoring from the onboarding and Settings surface entirely — the app never actually needed it, and the Request button could not recover after the first denial (macOS silently suppresses the prompt forever once dismissed).

    The real fix: this app does not need Input Monitoring at all. Carbon RegisterEventHotKey (global hotkeys) and CGEvent posting (paste-at-caret) both run on Accessibility alone.

    Removed

    • Input Monitoring permission row from onboarding and Settings.
    • PermissionManager.inputMonitoringGranted + requestInputMonitoringIfNeeded deleted. If a future feature genuinely needs CGEvent tap, the probe + request closure pattern is easy to add back.

    Improved

    • Auto-open Settings fallback for Accessibility — when requestAccessibilityIfNeeded() fires and the prompt is silently suppressed (because the user previously denied), a 1.5-second grace task re-checks the live grant. If still not granted, System Settings opens to the Accessibility pane automatically. No more "I clicked Request and nothing happened" dead end.

    Build pipeline

    • scripts/package_dmg.sh now codesigns the DMG wrapper with Developer ID before notarization. v0.10.3's manually-built DMG had this; the script did not.

    Tests

    • PermissionManagerTests drops 2 Input Monitoring assertions and adds 2 new ones pinning the auto-open Settings contract: fires when grant misses the grace window; skipped when grant arrives in time. 390 tests pass (was 388, +2).

    Risk-free upgrade

    UserDefaults + Keychain layout unchanged from v0.10.3.

    Install

    • Recommended: download the DMG below — open, drag the app into Applications, launch.
    • Sparkle auto-updates from v0.10.3 onwards work too (pulls the ZIP).

    Both the DMG and the ZIP are signed (Developer ID JP4XV8A9JF), notarized by Apple, and stapled. Apple Silicon only. macOS 14 or later.

    View on GitHub →
  7. v0.10.3 — Distribution hotfix (Gatekeeper acceptance)

    Pre-releaseMay 26, 2026

    Distribution hotfix — Gatekeeper acceptance

    v0.10.0, v0.10.1, and v0.10.2 all shipped distribution ZIPs that, on the user's machine, failed Gatekeeper verification with "_sealed resource is missing or invalid_" — surfacing as "app chưa sign" when Sparkle tried to install an update.

    The bug: hidden AppleDouble metadata files (the ._* files) inside the embedded Sparkle.framework, introduced by SwiftPM's xcframework extraction on macOS. They were invisible to codesign --sign (which signs the real files), so the build chain succeeded — but post-extraction codesign --verify --deep --strict treated them as foreign content and rejected the bundle.

    v0.10.3 carries zero application code changes — it is purely the v0.10.2 codebase re-packaged with the build pipeline fixed.

    How to install

    Recommended: download the v0.10.3 ZIP from this page — Sparkle on a v0.10.0 install that already aborted a v0.10.1/v0.10.2 update is likely in an inconsistent state. Unzip, drag the .app into /Applications replacing the old copy, run it. Future Sparkle updates (v0.10.4 onwards) install cleanly through Sparkle.

    If Sparkle's Check for Updates does work — even better. The v0.10.3 bundle is correctly signed + notarized + stapled, and the distribution ZIP no longer contains the offending metadata.

    Build pipeline fixes

    • scripts/package_app.sh strips ._* files BEFORE and AFTER the codesign chain (defense in depth).
    • The script's final step runs codesign --verify --deep --strict as an invariant check — if a future toolchain combination re-introduces the problem, the build fails loudly.
    • The distribution ZIP is now created with ditto --norsrc --noextattr --noacl so that ditto does not re-pack metadata into the ZIP via xattr packaging.

    Verified end-to-end

    `` $ unzip Contextual-Mac-Translator-v0.10.3-macos-arm64.zip $ find "Contextual Mac Translator.app" -name "._*" | wc -l 0 $ codesign --verify --deep --strict --verbose=2 "Contextual Mac Translator.app" valid on disk; satisfies its Designated Requirement $ spctl --assess --type execute --verbose "Contextual Mac Translator.app" accepted; source=Notarized Developer ID $ xcrun stapler validate "Contextual Mac Translator.app" The validate action worked! ``

    Apple Silicon only. macOS 14 or later.

    View on GitHub →
  8. v0.10.2 — Permission UX & TCC stability

    Pre-releaseMay 26, 2026

    Permission UX & TCC stability

    A user-reported pain point: every Sparkle upgrade felt like macOS was clearing the app's Accessibility grant — translate hotkeys went silent until you manually re-granted, with no in-app surface telling you what happened.

    v0.10.2 fixes the user experience and hardens the signature so the underlying TCC issue is much less likely to recur in the first place.

    Added — Auto-recovery onboarding

    • Permission-loss detection on launchAppDelegate reads the persisted lastKnownAccessibilityGranted flag _before_ refreshing the live grant, so the true→false transition that signals a TCC reset is detectable. When detected, the onboarding window pops automatically in recovery mode with copy that acknowledges you already did this once.
    • `OnboardingMode.permissionRecovery` swaps the title + intro: _"Welcome back. macOS cleared the app's Accessibility grant after the recent update — translate hotkeys are silent until you re-grant. Click Request below, or Open Settings if the prompt does not appear."_
    • Hotkeys still register in recovery mode so any grants that survived (e.g. Input Monitoring) keep working — the onboarding pops on top so the issue cannot be missed.

    Hardened — Codesign stability

    • --identifier "app.lookerlab.translator" is now passed explicitly to the main-app codesign call instead of being inferred from CFBundleIdentifier. The value baked into the signature is byte-stable across releases. Verified post-build: v0.10.0, v0.10.1, and v0.10.2 all sign with byte-identical designated requirements, so TCC should persist the grant across upgrade.

    Internal — PermissionManager testability

    • Constructor accepts injected accessibilityProbe / inputMonitoringProbe / requestAccessibilityAction / requestInputMonitoringAction closures so unit tests exercise grant-sync wiring without touching the real TCC database.
    • refresh() writes the live grant back to SettingsStore.lastKnownAccessibilityGranted (guarded by didSet so the 1-second polling loop does not spam UserDefaults).
    • refreshLater() settle delay bumped 1s → 2s — gives the TCC database room after the user clicks "Allow".

    Tests

    • +11 new tests — 6 in PermissionManagerTests (grant-sync, init non-mutation, request action, nil-settings), 5 in LaunchRecoveryDecisionTreeTests (fresh / steady / revoked / never-granted / lost-then-regranted).
    • 388 tests pass (was 377).

    Risk-free upgrade

    UserDefaults adds one new key (translator.lastKnownAccessibilityGranted) defaulting to false on existing installs — so the recovery flow does not fire spuriously on the v0.10.1 → v0.10.2 upgrade itself. The first refresh writes the current live state forward; from v0.10.3 onwards the recovery signal is armed end-to-end.

    Install

    • Sparkle — existing installs receive this automatically via the appcast at https://hoangperry.github.io/hp-translator/appcast.xml.
    • Fresh install — download Contextual-Mac-Translator-v0.10.2-macos-arm64.zip below, unzip, drag the .app to /Applications. Signed (Developer ID JP4XV8A9JF) + notarized + stapled.

    Apple Silicon only. macOS 14 or later.

    View on GitHub →
  9. v0.10.1 — SaaS backend hotfix

    Pre-releaseMay 26, 2026

    SaaS backend hotfix

    v0.10.0 shipped right as the production SaaS Edge Function at app.contextmt.dev went live, and the two wire formats turned out to be incompatible. Translates failed immediately with HTTP 400: target_language is required; once that cleared, a second failure surfaced — the streaming flow expected SSE on /translate/stream, but Supabase returns one-shot JSON, so the stream loop never saw a data:-prefixed frame and threw _"The backend response did not include a translation."_

    Both fixes are backward-compatible with the legacy self-hosted FastAPI server.

    Fixed

    • Dual wire formatBackendRequestBody now emits every routing field under both camelCase (legacy self-hosted FastAPI in translator-server/server.py) and snake_case (SaaS Supabase Edge Function). One payload, both backends. Pinned by a new bodyEmitsDualNamingConventions contract test so a future refactor cannot silently re-break one backend.
    • Streaming non-SSE fallbackBackendProvider.streamSSE sniffs Content-Type; if it's not text/event-stream (Supabase returns application/json), the body is drained and decoded as a one-shot TranslationResult then emitted as a single .done(...) update. Self-hosted SSE keeps its real streaming UX. Pinned by nonSSEJSONFallback test.

    Tests

    • 377 tests pass (was 376; +1 fallback test).
    • Removed a stale "0.10.0" entry from WhatsNewHighlightsTests.unknownVersionReturnsNil — v0.10.0 added its own highlight set so the value moved out of the catch-all bucket.

    Risk-free upgrade

    UserDefaults + Keychain layout byte-identical to v0.10.0. Existing settings, hotkey bindings, register card, and glossary entries all round-trip clean.

    Install

    • Sparkle — existing v0.10.0 installs will receive this update automatically via the appcast at https://hoangperry.github.io/hp-translator/appcast.xml.
    • Fresh install — download Contextual-Mac-Translator-v0.10.1-macos-arm64.zip below, unzip, drag the .app to /Applications. Signed (Developer ID JP4XV8A9JF) + notarized + stapled.

    Apple Silicon only. macOS 14 or later.

    View on GitHub →
  10. v0.10.0 — Cultural Precision & Privacy (VN Register + Privacy badge + Glossary v2)

    May 25, 2026

    Cultural Precision & Privacy

    The first feature-loaded minor since v0.9.0. Three new sub-features anchored on "Apple Intelligence can't do per-locale Vietnamese precision" — pin your social register, see the privacy class of every translation, and replace the free-text glossary blob with typed entries the LLM follows exactly. Risk-free upgrade — 375/79 tests stay GREEN, every v0.9.x setting + binding + hotkey + glossary blob round-trips byte-identical, Sparkle OTA from v0.6.1+ users auto-updates with no manual action.

    Added — VN Social Register Card (anchor)

    • `RegisterCard` with 3 axes — Dialect (Bắc/Nam), Kinship (anh/chị/em/cháu/bạn), Formality (formal/neutral/casual) — plus optional roleHint ("e.g. TikTok Shop seller addressing customer").
    • Settings → Contextual Rewrite → Vietnamese register card — 3 Picker dropdowns, roleHint TextField, Reset button, live composed-prompt preview so you see exactly what the LLM receives.
    • Composition is prepend, not replace — your per-binding tone instructions flow through unchanged below the [Tone] tag.
    • nil = disabled (default) → v0.9.x behaviour byte-identical for users who never open the panel.

    Added — Local-LLM Privacy mode

    • `ProviderPrivacyClass` enum (.local / .cloud / .hosted) — every provider classifies explicitly. Ollama = .local, BackendProvider = .hosted, everything else (Gemini direct + CLI, Codex CLI, DeepL, Google Translate, LibreTranslate, OpenAI-compatible) = .cloud.
    • PreviewHUD Privacy badge — every translation surfaces the provider's class (🛡 Local / ☁ Cloud / 🏢 Hosted) with .help() tooltip showing the full provider name. Stamped eagerly into TranslationStyle at workflow construction so SwiftUI render never reaches back into providerFactory().
    • Settings → Privacy section — active-provider ribbon with Vietnamese headline ("🛡 Local only — không gửi dữ liệu khách ra nước ngoài" when Ollama is active), collapsed Ollama onboarding card with download link + two curated ollama pull commands (each with a Copy button) + Browse-more-models link + one-click "Test Ollama connection" button (2s timeout, exact-name model match via /api/tags JSON parse, inline green/red status).

    Added — Glossary v2-Lite (typed entries)

    • `GlossaryEntry` Codable with 3 kinds — .dontTranslate(term:) for brand names / code identifiers, .alias(from:to:) for casing & spelling normalisations, .alwaysTranslate(term:to:) for jargon.
    • Forward-compatible partial recovery — unknown KindTag from a future v0.10.x is dropped at the element level (not the whole list) via GlossaryEntry.decodeArray(from:). A user who briefly opens v0.10.1, adds one .scoped entry, then downgrades, keeps every other v0.10.0 entry intact.
    • `GlossaryComposer` pure function composes typed entries + legacy free-text blob. dontTranslate grouped under one bullet; alias + alwaysTranslate render one-per-line directional pairs. 50-entry render cap.
    • Settings → Glossary editor — Add-menu for the 3 kinds, type pill tinted per kind, 1-or-2 TextFields per row, delete button, drag-to-reorder via .onMove. Legacy free-text TextEditor moved to a collapsed DisclosureGroup below; rules apply structured-first.

    What's-New on first launch

    A small window pops once for v0.10.0 upgrades with 3 highlight cards (Register Card / Privacy badge / Glossary v2). Doesn't steal focus — click "Got it" to dismiss.

    Compatibility

    • Every v0.9.x persisted state round-trips clean: RewriteBinding, HotkeyConfig, TranslationStyle, SaaSConfig, legacy glossary string blob — all unchanged in storage.
    • TranslationStyle gains 3 default-safe fields (registerCard: nil, privacyClass: nil, providerDisplayName: "").
    • macOS 14 minimum preserved.

    Install

    • Sparkle OTA: v0.6.1+ users auto-update from inside the app — no action needed.
    • Fresh install: download the DMG below → drag to /Applications.

    Verify

    • App: 375 Swift / 79 suites GREEN (+56 from v0.9.2's 319).
    • Build: signed (Developer ID), notarized + stapled (DMG + Sparkle zip), EdDSA-signed appcast.

    Deliver-phase code review

    Independent code-review agent before tag: 0 CRITICAL / 3 HIGH / 4 MED / 2 LOW. All 3 HIGH + 3 MED fixed inline (commit 285f519):

    • H1SettingsPrivacySection was allocating TranslationProviderFactory on every SwiftUI render. Cached in @State, refreshed only on provider-change.
    • H2 — Force-unwrapped URL literals in body position. Lifted to static let constants.
    • H3 — Glossary array decode was all-or-nothing; one unknown KindTag from a future v0.10.x would nuke the user's whole list. Switched to per-element partial-recovery decode.
    • M1 — Concurrent taps of "Test Ollama connection" could race on testResult. Added isTesting guard.
    • M2RegisterCard.prompted was appending raw prefix instead of trimmed prefix, opening a double-blank gap when callers passed trailing whitespace.
    • M3 — Ollama /api/tags model match was substring (bodyText.contains(...)) which gave false positives. Switched to JSON-parsed exact-name match.

    Deferred to v0.10.1: M4 (stamp() helper duplication between TranslationWorkflow + CaptureOrchestrator), L1 (roleHint character-vs-byte cap clarification), L2 (LibreTranslate self-hosted shows ☁ Cloud despite localhost endpoint).

    Roadmap (next)

    • v0.10.1 patch backlog: M4 + L1 + L2 above, per-binding register-card override toggle (carried from v0.10.0 deferred list).
    • v0.11.0 candidates: voice input via WhisperKit (Moonshine-Medium is production-ready for VN by 2026), Glossary v2 scoped entries (per-language + per-app).

    EOF

    View on GitHub →
  11. v0.9.2 — Second refactor patch (SaaSConfig extract + protocol alignment)

    May 24, 2026

    Second refactor patch

    No new user-facing features. Clears the remaining HIGH + MED + LOW backlog items from the v0.9.0 deliver-phase review that v0.9.1 deferred. Risk-free upgrade — 319/69 tests stay GREEN with the same test bodies; UserDefaults + Keychain layout is byte-identical to v0.9.1 so every setting, hotkey, binding, and SaaS session round-trips clean.

    Changed (internal)

    • `SettingsStore` god-object trimmed (W1, HIGH) — extracted the Supabase project URL + anon key + the 4 SaaS factory helpers (authConfig / makeSessionStore / translateEndpoint / deviceIdentity) into a new Auth/SaaSConfig.swift file. SettingsStore drops from 593 → 538 LOC and from owning 6 Supabase concepts down to 1 (just a saaSConfig reference). Future SaaS-only changes now live in one file.
    • `ScreenCaptureService` protocol shape aligned to `OCREngine` (MED-4) — dropped the unnecessary @MainActor + AnyObject requirements from the protocol. Production impl is still @MainActor where it needs to be; stubs in tests can now be plain structs. Cleans up the asymmetry the v0.9.0 review flagged.

    Fixed

    • Picker + capture hotkey recorder sheets share one helper now (LOW-2) — centralised the synthetic Binding<HotkeyConfig> pattern into optionalHotkeyBinding(_:fallback:) on SettingsView. Pre-allocated HotkeyConfig.defaultPicker / .defaultCapture constants replace the per-render allocations.

    Deferred to v0.9.3+

    • TranslationWorkflowClipboardRestoreTest — needs HUDPresenting + Pasteboarding protocol extractions (own refactor commit).
    • Hardcoded Supabase anon-key + URL move to build-time .xcconfig (now that SaaSConfig is its own file, this becomes a one-line init-default change + a CI secret wiring step).

    Install

    • Sparkle OTA: v0.6.1+ users auto-update from inside the app — no action needed.
    • Fresh install: download the DMG below → drag to /Applications.

    Verify

    • App: 319 Swift / 69 suites GREEN (unchanged from v0.9.1).
    • Build: signed (Developer ID), notarized + stapled (DMG + Sparkle zip), EdDSA-signed appcast.
    View on GitHub →
  12. v0.9.1 — Refactor + reliability patch

    May 24, 2026

    Refactor + reliability patch

    No new features. Internal cleanup for the two pieces of structural debt the v0.9.0 deliver-phase review flagged, plus 13 high-leverage tests covering paths that silently failed before. Upgrade is risk-free — every v0.9.0 setting, hotkey, binding, and toggle round-trips clean; all 306 existing tests stay GREEN with zero modifications.

    Changed (internal)

    • `TranslationWorkflow.swift` split: 876 → 630 LOC, back under the 800-line guideline before any new workflow path lands.

    - New `CaptureOrchestrator.swift` — owns the v0.9.0 OCR-translate flow end-to-end. - New `RewriteService.swift` — owns the rewrite primitives (single-shot + refusal-retry + multi-variant + the 3 App Intents headless methods + the PickerEntry → TranslationStyle mapper). Previously duplicated in multiple sites; now one truth.

    • Behaviour byte-identical. Public API of TranslationWorkflow unchanged.

    Fixed

    • No more silent Japanese fallback in the status-bar "Send Keigo" / "Send Casual" menu items. A Vietnamese-primary user with no formal/casual outbound binding used to get a hardcoded .japaneseBusiness / .japaneseCasual translation. v0.9.1 surfaces an actionable error toast pointing to Settings → Translation bindings.

    Added (tests)

    • `BackendStreamingURLTests` (9 tests) — pin every branch of BackendProvider.streamingURL(for:). The 4-case path rewriter used to silently fall back to the configured endpoint on edge cases (/api/translate, trailing slash, non-default port); tests now lock the behaviour. Required marking streamingURL nonisolated static (honest — pure URL logic, no actor state).
    • `WhatsNewHighlightsTests` (4 tests) — pin the per-version highlight lookup that v0.9.0 introduced. Verifies v0.10.0 / v0.9.2 / older patches all fall into the nil catch-all branch correctly, so a future build can't silently mis-show stale highlights.

    Deferred to v0.9.2

    • TranslationWorkflowClipboardRestoreTest — needs HUDPresenting + Pasteboarding protocol extraction (own refactor commit).
    • SettingsStore god-object split (extract SaaSConfig).
    • OCREngine protocol isolation alignment.

    Install

    • Sparkle OTA: v0.6.1+ users auto-update from inside the app — no action needed.
    • Fresh install: download the DMG below → drag to /Applications.

    Verify

    • App: 319 Swift / 69 suites GREEN (+13 from v0.9.0's 306).
    • Build: signed (Developer ID), notarized + stapled (DMG + Sparkle zip), EdDSA-signed appcast.
    View on GitHub →
  13. v0.9.0 — Input Surface Expansion (Shortcuts.app + OCR-from-screen)

    May 24, 2026

    Input Surface Expansion

    Two new ways to invoke the translator: from any app via Shortcuts.app / Spotlight / Siri, and from any pixel on screen via an OCR capture hotkey. Both are opt-in. The single-key inbound/outbound flow + the v0.8 tone picker + the v0.8.5 multi-variant rewrite all stay byte-identical.

    What's new

    #### Shortcuts.app, Spotlight & Siri (App Intents)

    Three new actions discoverable in Shortcuts.app under "Contextual Mac Translator":

    • Translate Text — translate any text. Target language optional (falls back to your primary language). Returns a string usable in subsequent Shortcut steps.
    • Rewrite with Tone — picks from the 7 preset tones (Polite, Professional, Friendly, Firm-but-polite, De-escalate, Concise, Custom). Use it to chain the translator into Quick Actions, Raycast, Alfred, calendar workflows, anything that speaks AppIntents.
    • Rewrite with Instruction — free-text instruction ("warmer reply", "shorter under 2 sentences"). Mirrors the in-app picker's freetext row from v0.8.3.

    Spotlight + Siri trigger phrases prefixed with "Contextual" so they don't collide with Apple Translate.

    #### OCR-from-screen translate

    • Bind a hotkey in Settings → Capture, drag the system crosshair (same UX as Cmd+Shift+4), and the app reads the text on-device via Vision, auto-detects source language via NaturalLanguage, and translates it into your primary language.
    • Recognises Vietnamese / English / Simplified Chinese / Japanese / Korean — picked for the VN-power-user audience (1688 / Aliwangwang / Messenger / Discord flows where browser translation breaks down).
    • Result opens in a copy-mode PreviewHUD: the Send button becomes "Copy". No auto-paste, no keystroke simulation. You read the translation; you click Copy if you want it on your clipboard.
    • Privacy: OCR runs entirely on-device. Only the recognised text travels to your active translation provider — same as every other translate flow. Screen pixels never leave the device.

    #### What's-New window

    Pops once on first launch of a new minor/major release with a short summary of what's worth knowing. Self-dismissible. Doesn't steal focus (you can keep typing while it shows).

    How to set up

    1. Shortcuts: open Shortcuts.app → New Shortcut → search for "Translate Text" / "Rewrite with Tone" / "Rewrite with Instruction" under Contextual Mac Translator. 2. OCR: Settings → Capture → tick "OCR capture hotkey" → record a hotkey (default suggestion ⌘⌥G). The first invocation triggers macOS's Screen Recording permission prompt; the Settings panel has a one-click link to System Settings if you missed it.

    Compatibility

    • All v0.8.5 persisted state round-trips clean (RewriteBindings / hotkeys / glossary / expressive-tones / multi-variant — every Codable contract unchanged).
    • Sparkle OTA from v0.6.1+ auto-updates to v0.9.0 with no user action.
    • macOS 14 minimum preserved. App Intents (macOS 13+), Vision text recognition (10.15+), ScreenCaptureKit (12.3+), NaturalLanguage (10.14+) all backward-compatible to the floor.

    Install

    • Sparkle OTA: v0.6.1+ users auto-update from inside the app.
    • Fresh install: download the DMG below → drag to /Applications.

    Verify

    • App: 306 Swift / 67 suites GREEN (+36 from 270 in v0.8.5).
    • Build: signed (Developer ID), notarized + stapled (DMG + Sparkle zip), EdDSA-signed appcast.
    • Code-review pass: 0 CRITICAL / 3 HIGH (all fixed before tag) / 4 MEDIUM (2 fixed, 2 carried forward as test-coverage backlog).

    Roadmap (next)

    • v0.9.1 patch backlog: per-binding multi-variant override, captureAndTranslate orchestrator test, screencapture timeout-path test, MED-4 OCREngine isolation alignment.
    • v0.10.0 candidates from discovery: Glossary v2 typed entries, VN Register Card UI, Local-LLM "Privacy mode" pivot.

    Acknowledgements

    Discovery + Define + Develop + Deliver phases run via the OCTO Double-Diamond workflow. Planning artefacts at docs/v0.9.0/{discovery,define}.md.

    View on GitHub →
  14. v0.8.5 — Multi-variant rewrite (3 drafts in one round-trip)

    May 23, 2026

    Multi-variant rewrite — 3 drafts in one round-trip

    Opt-in setting that asks the LLM for three different rewrites in a single network call, then lets you page through them in the preview HUD before sending.

    What's new

    • Generate 3 drafts per rewrite toggle under Settings → Contextual rewrite. Off by default — existing users stay on the cheaper single-draft path.
    • Multi-variant preview HUD — pager chip ("2 / 3"), ← / → buttons to flip between drafts, ⌘1–3 for quick-select, edit any draft with Tab (edits persist per-variant as you page back and forth).
    • One network call — the prompt asks for N drafts separated by a ---VARIANT--- sentinel, so cost is ~1.5–2× tokens but only one round-trip latency.
    • Graceful fallback — if the model ignores the multi-variant prompt or all drafts get filtered as refusals, the existing single-draft retry chain kicks in so you still get a result.

    How to use

    1. Settings → Contextual rewrite → tick "Generate 3 drafts per rewrite". 2. Invoke a rewrite the same way you always have — binding hotkey (⌥R / ⌥E / …) or the tone picker (⌘⌥⏎). 3. The preview HUD shows draft 1/3. Use ← / →, ⌘1–3, or the chevron buttons to switch. 4. Tab to edit the current draft. Return to send the active one. Esc to cancel.

    Compatibility

    • Default OFF — no existing users see a behaviour change without opting in.
    • Single-draft mode (toggle OFF) takes the same code path as v0.8.4 and earlier.
    • Works with every LLM provider (Gemini, Ollama, OpenAI-compatible).

    Install

    • Sparkle OTA: v0.6.1+ users auto-update from inside the app.
    • Fresh install: download the DMG below → drag to /Applications.

    Verify

    • App: 270 Swift / 56 suites GREEN (+13 net: multi-variant view-model, variant splitter, style helpers).
    • Build: signed (Developer ID), notarized + stapled (DMG + Sparkle zip), EdDSA-signed appcast.

    Roadmap (next)

    • Polish on response parsing for the long tail of models that ignore the sentinel in creative ways.
    • Optional per-binding multi-variant override (today the toggle is global).
    View on GitHub →
  15. v0.8.4 — Pre-warmed picker + per-binding 'In picker' + a11y

    May 23, 2026

    Pre-warmed picker + per-binding "In picker" + VoiceOver polish

    Three small upgrades that make the tone picker feel native.

    What's new

    • Pre-warmed panel — the picker NSPanel is constructed at app launch instead of on first hotkey press, so first open is near-instant. The ~30–60 ms setup cost moves off the hotkey path.
    • "In picker" per binding — every saved rewrite binding in Settings now has an opt-in checkbox to also surface it as a row in the popup. Pick saved instructions (with your custom override prompts) without remembering hotkeys.
    • VoiceOver labels — every picker row announces its name + ⌘ shortcut hint + selected state; the container itself announces "Tone picker — Type to filter, arrow keys to navigate, Return to apply, Escape to cancel".

    How to use

    • In picker checkbox: Settings → Contextual rewrite → expand any binding row → tick "In picker" (default ON). Open the picker with your tone-picker hotkey; saved bindings appear below built-in tones.
    • Search: type to filter; bindings match by their display name + tone label.
    • Quick-select: ⌘1–9 commits the Nth row directly, including bindings.

    Compatibility

    • Bindings persisted by v0.8.3 and earlier decode with showInPicker = true via decodeIfPresent — zero migration cost, round-trip safe.
    • Users who want a binding hidden from the picker can untick it in Settings.

    Install

    • Sparkle OTA: v0.6.1+ users auto-update from inside the app.
    • Fresh install: download the DMG below → drag to /Applications.

    Verify

    • App: 257 Swift / 54 suites GREEN (pre-existing flaky SupabaseAuth.sendCode-no-config test ignored — passes 10/10 in isolation, fails ~1× per full suite since v0.5.x).
    • Build: signed (Developer ID), notarized + stapled (DMG + Sparkle zip), EdDSA-signed appcast.

    Roadmap

    • v0.8.5 — Multi-variant rewrite (generate 3 drafts in one call, pick between them via Preview HUD).
    View on GitHub →
  16. v0.8.3 — Free-text custom instruction in tone picker

    May 23, 2026

    Free-text rewrite instructions in the tone picker — Apple Writing Tools "Describe your change" style, kept inside the same one-hotkey flow.

    How to use

    Press your picker hotkey → start typing anything that's not a preset name → a "Use: ..." row appears at the top → Return / ⌘1 / click to commit. The app rewrites with your typed instruction.

    Examples:

    • "make it sound less defensive"
    • "warmer reply to an angry client"
    • "shorter, under 2 sentences"
    • "as if I'm writing to my boss"

    Added

    • `PickerEntry` sum type.preset(RewriteTone) for built-in tones, .freetext(String) for user-supplied instructions.
    • Freetext row appears at the top of the picker whenever the query is non-empty, with a pencil icon and italic label.
    • Field placeholder updated: "Filter or describe…" makes the dual purpose discoverable.

    Notes

    • Freetext rewrites stay strict — they don't auto-flip the expressive-content flag. Users who want Chửi thề BLOCK_NONE routing should pick that preset explicitly.
    • Tests: 252 Swift / 53 suites GREEN (+2 net from picker test rework)
    • Apple notarization: DMG + zip both Accepted and stapled

    Auto-update

    On v0.6.1+ this arrives automatically via Sparkle.

    View on GitHub →
  17. v0.8.2 — New tone: Chửi thề (casual-raw, opt-in)

    May 23, 2026

    New rewrite tone: "Chửi thề" — opt-in casual-with-edge register for Vietnamese chats with close friends. Uses profanity markers like vl / vcl / đm as natural intensifiers (the "as hell" / "af" of Vietnamese internet writing), preferring abbreviated forms over spelled-out vulgar phrases.

    For chats with friends, not customers. Every rewrite still shows in preview before sending.

    How to enable

    1. Settings → Contextual rewrite → "Enable expressive tones (Chửi thề)" — flip on 2. A confirmation dialog appears explaining the tone's intent — hit Continue 3. "Chửi thề" now appears in the tone picker + binding dropdown

    Safety + design notes

    • Identity-safe. The prompt has hard carve-outs: never add slurs, never attack identity (race, gender, origin, appearance), never add personal insults that aren't already in your input.
    • Abbreviation-first. The prompt nudges toward vl / vcl / đm over spelled-out vulgar phrases — matches authentic Vietnamese internet chat register.
    • Provider routing. With Gemini, the app sets safetySettings = BLOCK_NONE for the 4 adjustable categories (HARASSMENT / HATE_SPEECH / SEXUALLY_EXPLICIT / DANGEROUS_CONTENT) — Google explicitly documents this for creative-writing use cases. Non-adjustable filters (CSAM, election integrity) remain enforced.
    • Other providers may refuse. OpenAI / Ollama / etc. don't get the BLOCK_NONE relaxation (their content policy varies). The existing refusal-detection retry kicks in; if the model still refuses, the workflow falls back cleanly without overwriting your draft.
    • Opt-in by default. The toggle is OFF on fresh installs. Updating from v0.8.1 keeps it OFF — nothing surprising.

    Auto-update

    On v0.6.1+ this arrives automatically via Sparkle.

    Compatibility

    • macOS 14 Sonoma minimum
    • Liquid Glass effects on macOS 26 Tahoe

    Tests

    • 250 Swift tests / 53 suites (+8 new: casualRaw flag, available() filter, expressive style flag, Gemini permissive safety shape, Settings persistence, Vietnamese instruction guidance)
    • Apple notarization: DMG + zip both Accepted and stapled
    View on GitHub →
  18. v0.8.1 — tone picker code-review polish

    May 23, 2026

    Code-review polish on top of v0.8.0 — no behaviour change, pure cleanup.

    Changed

    • Single source of truth for the picker panel size. TonePickerController derives both the NSPanel.contentRect at construction AND the cursor-anchored origin from one private let panelSize constant; the two can no longer drift.
    • Removed unused `clampSelectionAfterFilter()` helper (and its test). The view already resets the selection to 0 on every filter change (the Spotlight/Raycast UX) — the clamp helper would have been a different UX and was dead code.

    Auto-update

    On v0.6.1+ this arrives automatically via Sparkle.

    Compatibility

    • macOS 14 Sonoma minimum
    • Liquid Glass effects on macOS 26 Tahoe

    Tests

    • 242 Swift tests / 52 suites GREEN (−1 from the removed clamp test)
    • Apple notarization: DMG + zip both Accepted and stapled
    View on GitHub →
  19. v0.8.0 — Tone picker hotkey: one popup, every tone

    May 23, 2026

    New feature: Tone picker hotkey. Bind ONE global hotkey and a popup picker lists every rewrite tone. Pick with arrows + Return, type-to-filter, or ⌘ 1-7. The app rewrites your current input line in that tone, shows the preview HUD, and pastes when you confirm.

    Coexists with v0.7's per-binding model — power users keep their muscle-memory hotkeys for top tones, the picker scales to the long tail.

    How to use

    1. Settings → Contextual rewrite → "Tone picker hotkey" → Set hotkey (e.g. ⌘⌥⏎) 2. Type a message in any app 3. Press the picker hotkey — popup appears at your cursor 4. Pick a tone (arrows + Return / ⌘ 1-7 / type to filter) 5. Review the rewrite in the preview HUD, edit if needed, hit Send

    | Picker key | Action | |------------|--------| | ↑ / ↓ | Move selection | | Return | Confirm tone | | Esc | Cancel | | ⌘ 1-7 | Quick-select that row | | Click outside / 5 s focus loss / 20 s dwell | Auto-dismiss | | Re-press picker hotkey | Toggle picker closed |

    Safety + privacy

    • Refuses password fields. AX role check blocks AXSecureTextField before any keyboard simulation runs — your password drafts never reach an LLM.
    • Eager clipboard restore. The captured line is restored to the clipboard *before* the picker dwells, so a clipboard manager doesn't snapshot your draft.
    • Right-Arrow collapse. Immediately after Cmd+C the selection is collapsed — a cancelled picker leaves zero side effects in your input field.
    • Always preview. Tone-changed messages always go through the preview HUD before paste — never auto-send.

    Provider requirements

    Needs an LLM provider: Gemini, Ollama, or an OpenAI-compatible API. DeepL and Google Translate cannot rewrite — Settings warns when the active provider can't, and the picker hotkey simply doesn't register.

    Auto-update

    On v0.6.1+ this arrives automatically via Sparkle — the app prompts with "Install and Relaunch."

    Compatibility

    • macOS 14 Sonoma minimum
    • Liquid Glass effects on macOS 26 Tahoe

    Tests

    • 243 Swift tests / 52 suites GREEN
    • Apple notarization: DMG + zip both Accepted and stapled
    View on GitHub →
  20. v0.7.1 — Contextual rewrite polish (clipboard race + hotkey gate)

    May 23, 2026

    Polish on top of v0.7.0. Addresses the post-ship code-review findings — no new features.

    Fixed

    • Clipboard race on post-paste focus loss. Both translateAndSend and rewriteAndSend now use the delayed restoreClipboard(snapshot) (700&nbsp;ms) when the focus-guard fails *after* the paste, instead of the synchronous restore. A slow target app could otherwise read the restored snapshot before it had finished consuming the just-pasted text.
    • Rewrite hotkey gated by provider availability. applyHotKeys now skips registering rewrite hotkeys when the active provider can't rewrite (DeepL / Google Translate / LibreTranslate). Switching back to an LLM provider re-registers automatically. No more "press hotkey, get an error every time" while a non-LLM provider is selected.

    Auto-update

    If you are on v0.6.1+, this arrives automatically via Sparkle.

    Compatibility

    • macOS 14 Sonoma minimum
    • Liquid Glass effects on macOS 26 Tahoe

    Tests

    • 220 Swift tests / 49 suites GREEN
    • Apple notarization: DMG + zip both Accepted and stapled
    View on GitHub →
  21. v0.7.0 — Contextual rewrite: same-language tone shift on a hotkey

    May 23, 2026

    New feature: Contextual rewrite. Bind a hotkey to a tone and the app rewrites the current input line in that tone — same language, intent preserved. Built for the moment you typed something blunt or angry and need to soften it before sending. Always shows a preview before sending — never auto-paste.

    How to use

    1. Settings → Contextual rewrite → Add binding 2. Pick a tone — Polite, Professional, De-escalate, Friendly, Firm-but-polite, Concise, or Custom (your own free-text instruction) 3. Click Change… to record a hotkey (e.g. ⌥R) 4. Type a message that needs softening, press the hotkey 5. Review the rewritten version in the preview HUD, edit if needed, hit Send

    Example:

    | You typed | Tone | App rewrites | |-----------|------|--------------| | Chị ngu quá chị không hiểu gì hết | Polite | Chị ơi, chỗ này hơi khó hiểu một chút, để em giải thích lại rõ hơn cho mình nhé. | | Trả tiền đi không tôi không giao hàng nữa đâu | Firm but polite | Anh/chị vui lòng hoàn tất thanh toán giúp em ạ, để bên em sắp xếp giao hàng đúng hẹn nhé. | | Lỗi của shipper chứ đâu phải lỗi của tôi mà bắt đền | De-escalate | Em rất xin lỗi về sự cố vừa rồi ạ. Trường hợp này phát sinh từ khâu vận chuyển, em sẽ hỗ trợ kiểm tra và xử lý sớm cho mình. |

    Vietnamese-aware

    The prompt preserves your existing anh/chị/em pronoun pairing exactly and adds politeness particles (ạ / dạ / nhé) as the tone requires.

    Provider requirements

    Rewrite needs an LLM provider — Gemini, Ollama, or any OpenAI-compatible API. DeepL and Google Translate cannot rewrite (translation-only, no instruction following) — Settings shows a warning when the active provider can't.

    Safety

    • Always preview. A tone-changed message must be reviewed before it goes out.
    • Refusal handling. If the model declines on a profane draft, the app retries once with a stronger anti-refusal prompt; if it still refuses, your original text is kept and you get a clear error.
    • Original-text guard. Empty custom binding? Wrong provider? Network error? The app fails fast with an actionable message and never overwrites your input with garbage.

    Auto-update

    If you are on v0.6.1+, this arrives automatically via Sparkle — the app offers v0.7.0 with an "Install and Relaunch" prompt.

    Compatibility

    • macOS 14 Sonoma minimum
    • Liquid Glass effects on macOS 26 Tahoe

    Tests

    • 220 Swift tests / 50 suites GREEN
    • Apple notarization: DMG + zip both Accepted and stapled
    View on GitHub →
  22. v0.6.3 — real Liquid Glass for app windows

    May 22, 2026

    Corrects the Liquid Glass adoption. v0.6.1/v0.6.2 made the onboarding and Settings windows translucent with .ultraThinMaterial — but that is the *legacy* vibrancy material (the pre-Tahoe fallback), not Liquid Glass. On macOS 26 those windows were rendering the old material.

    Fixed

    • Onboarding + Settings windows now use real Liquid Glass — swapped the unconditional .ultraThinMaterial for a helper that resolves to .glassEffect() on macOS 26 Tahoe and falls back to .regularMaterial only on macOS 14/15.
    • No more glass-on-glass — the onboarding permission rows were glass cards sitting on what is now a glass window. Glass cannot sample other glass, so the rows are now plain content cards.

    Note

    The app has always been built against the macOS 26.1 SDK, so standard controls, the window chrome, sheets and menus already adopt Liquid Glass automatically. This release only corrects the two custom window backings.

    Auto-update

    If you are on v0.6.1+, this arrives automatically via Sparkle — the app offers v0.6.3 with an "Install and Relaunch" prompt.

    Compatibility

    • macOS 14 Sonoma minimum
    • Liquid Glass effects on macOS 26 Tahoe

    Tests

    • 190 Swift tests / 45 suites GREEN
    • Apple notarization: DMG + zip both Accepted and stapled
    View on GitHub →
  23. v0.6.2 — Liquid Glass Settings + OTA verification

    May 21, 2026

    Completes the Liquid Glass pass and is the first build to verify the Sparkle auto-update path end-to-end (v0.6.1 → v0.6.2).

    Changed

    • Settings window adopts Liquid Glass — translucent window with the grouped Form's opaque scroll backing hidden over an ultraThinMaterial layer, matching the onboarding window. The macOS 26 System Settings look.

    Auto-update

    If you are on v0.6.1, this update arrives automatically — the app checks the appcast in the background and will offer v0.6.2 with an "Install and Relaunch" prompt. No manual download needed.

    Install (first time)

    Download the DMG below, drag into Applications, launch. Signed with Developer ID + Apple-notarized (DMG and update zip both stapled).

    Compatibility

    • macOS 14 Sonoma minimum
    • Liquid Glass effects on macOS 26 Tahoe

    Tests

    • 190 Swift tests / 45 suites GREEN
    • Apple notarization: DMG + zip both Accepted and stapled
    View on GitHub →
  24. v0.6.1 — crash hotfix + draggable popups

    May 21, 2026

    Hotfix for v0.6.0, which crashed on launch. If you are on v0.6.0, download the DMG below manually — the crash happens before Sparkle starts, so auto-update can't reach you. Every release after this updates silently.

    Fixed

    • Crash on launch (dyld: Library not loaded: @rpath/Sparkle.framework) — swift build only bakes rpaths for the .build/ layout. Once the binary moved into Contents/MacOS/ and Sparkle into Contents/Frameworks/, dyld had no rpath that resolved there. The packaging script now adds @executable_path/../Frameworks to the binary before signing.

    Changed

    • HUD + preview popups are draggable and resizable — drag from any non-control area to reposition, drag any edge to resize. The HUD keeps its position and size while a translation streams in.
    • Onboarding window adopts Liquid Glass — translucent window with an ultraThinMaterial backing; the Accessibility / Input Monitoring permission rows are glass cards on macOS 26 Tahoe.

    Install

    • Download the DMG below, drag into Applications, launch.
    • Signed with Developer ID + Apple-notarized (DMG and update zip both stapled).

    Compatibility

    • macOS 14 Sonoma minimum
    • Liquid Glass effects on macOS 26 Tahoe

    Tests

    • 190 Swift tests / 45 suites GREEN
    • Apple notarization: DMG Accepted, zip Accepted (9649ce1f-…)
    View on GitHub →
  25. v0.5.1 — Liquid Glass HUD + dismiss controls

    May 20, 2026

    Liquid Glass adoption + explicit dismiss controls for the result HUD. No more popups that feel stuck on screen.

    What's new

    • Liquid Glass background — On macOS 26 Tahoe the HUD and Preview panels use .glassEffect(in:). Older systems get the existing .regularMaterial look as a graceful fallback.
    • X close button on the loading / result / error HUD — tap to dismiss instantly.
    • Click anywhere outside the panel to dismiss — global mouse-down monitor watches for clicks in other apps and hides the HUD.
    • Stable display preserved — the auto-hide timer (8s result, 6s error) is still the safety net. No focus-loss auto-dismiss that could surprise you mid-read.

    Why

    > "tôi nhớ AppKit của Mac mới nhất là Liquid design mà sao mở app lên không có. Với lại popup nên có nút tắt để user tắt sớm, hoặc bấm ra ngoài để tắt. Nhưng đảm bảo nó phải hiển thị ổn định."

    Done meow~

    Install

    1. Download the DMG below 2. Open it, drag Contextual Mac Translator.app into Applications 3. Launch from Spotlight / Launchpad — signed + notarized by Apple, no right-click required

    Compatibility

    • macOS 14 Sonoma minimum (set in Package.swift)
    • Liquid Glass effect kicks in on macOS 26 Tahoe; older systems get the .regularMaterial fallback automatically

    Tests

    • 190 Swift tests / 45 suites GREEN
    • Apple notarization: Accepted (submission c6e5f957-…)
    View on GitHub →
  26. v0.5.0 — SwiftUI App + MenuBarExtra + Swift 6 mode

    May 20, 2026

    Architecture refactor: the app moves off the legacy NSApplication.shared + manual AppDelegate wiring and onto SwiftUI's modern @main App + MenuBarExtra. Compiles under Swift 6 language mode with strict concurrency checking.

    Changed

    • MenuBarExtra refactor: main.swiftContextualMacTranslatorApp.swift. @main App struct with declarative MenuBarExtra scene replaces the ~50-line imperative NSStatusItem + NSMenu setup. AppDelegate lives on as @NSApplicationDelegateAdaptor, still owning the workflow, hotkey manager, HUDs, and the Settings + Onboarding windows.
    • Swift 6 language mode enabled on both executable and test targets. The codebase was already correctly annotated with @MainActor, Sendable value types, and @Observable reference types, so the upgrade was a zero-diff for source files.
    • Dead code removed: AppDelegate.buildStatusItem and the statusItem property gone.

    Install

    Download the DMG, double-click to open, drag Contextual Mac Translator to Applications. Signed with Apple Developer ID and notarized — no Gatekeeper warning.

    Notes

    Tests: 190 Swift / 45 suites green. Apple Silicon, macOS 14+.

    Full changelog: https://github.com/hoangperry/hp-translator/compare/v0.4.1...v0.5.0

    View on GitHub →
  27. v0.4.1 — Modern macOS UI: app icon + @Observable + symbol effects

    May 20, 2026

    UI modernization pass plus the long-missing app icon. Foundational bump to macOS 14 to unlock the modern SwiftUI surface.

    Added

    • App icon: hanko-stamp 訳 squircle (cream character on hanko-red background) matching the brand mark used across the marketing site. Finder, Dock, Spotlight, and Command-Tab all show the icon now.
    • Symbol effects: HUD result icon bounces on new translation, error icon pulses, permission checkmarks bounce on grant; all status icons use hierarchical SF Symbol rendering.

    Changed

    • macOS minimum bumped to 14 (was 13). Unlocks \@Observable\, \.symbolEffect\, modern \onChange\ two-parameter form, and the rest of the macOS-14 SwiftUI surface.
    • State management → \`@Observable\`: all four observable classes (SettingsStore, PermissionManager, PreviewHUDViewModel, SupabaseAuthViewModel) use the Observation macro instead of ObservableObject + @Published. AppDelegate binding observation switched from Combine \.sink\ to \withObservationTracking\.
    • Onboarding polish: bigger Continue button (\.borderedProminent\ + \.controlSize(.large)\), hierarchical info icons.
    • Preview HUD: edit pencil hierarchical + \.controlSize(.large)\; deprecated \onChange(of:perform:)\ updated to the macOS-14 closure form.

    Install

    Download the DMG, double-click to open, and drag Contextual Mac Translator to Applications. The build is signed with Apple Developer ID and notarized by Apple — no Gatekeeper warning, no right-click step.

    Notes

    Tests: 190 Swift / 45 suites green. Apple Silicon, macOS 14+.

    Full changelog: https://github.com/hoangperry/hp-translator/compare/v0.4.0...v0.4.1

    View on GitHub →
  28. v0.4.0 — Cloud sign-in + device registration

    May 19, 2026

    First step onto the SaaS path: the app can now sign in to Contextual MT Cloud. Self-hosted and custom-backend modes are unchanged.

    Added

    • Contextual MT Cloud sign-in (opt-in): Settings → Translation Source → "Contextual MT backend" now offers a "Contextual MT Cloud · email sign-in" mode alongside the self-hosted token mode. Sign-in is a Supabase-backed email OTP (6-digit code, no password). _(M2.1-a)_
    • Device registration: each Mac generates a device identity and sends X-Device-* headers with cloud requests so the backend can enforce the per-plan device limit. _(M2.1-c)_
    • Keigo-specialized style instruction: Japanese business outbound uses a keigo-tuned LLM instruction for more reliable 敬語 output.
    • IT glossary starter pack: an EN/VI → JP IT-term preset for bridge engineers.

    Changed

    • Settings rebuilt as a grouped \Form\ for the native macOS System Settings appearance.
    • HUD and preview-panel borders now use the adaptive \.separator\ color (fixes an invisible border in light mode).

    Install

    Download the DMG, double-click to open, and drag Contextual Mac Translator to Applications. The build is signed with Apple Developer ID and notarized by Apple — no Gatekeeper warning, no right-click step. Bundle-ID migration and Sparkle auto-update remain planned for a later release.

    Notes

    Tests: 190 Swift / 45 suites green. Pre-release (alpha), Apple Silicon, macOS 13+.

    Full changelog: https://github.com/hoangperry/hp-translator/compare/v0.3.1...v0.4.0

    View on GitHub →
  29. v0.3.1 — Hotkey recorder + custom style + server DeepL/LibreTranslate mirror

    Pre-releaseMay 11, 2026

    What's new in v0.3.1

    Closes the v0.3 follow-up backlog.

    Customizable hotkey recorder

    • Click "Change…" next to any hotkey in Settings → press your new combo → save.
    • Built on NSEvent.keyDown with proper Cocoa-flag-to-Carbon-modifier-mask mapping.
    • Esc cancels mid-recording.
    • Validates: at least one modifier (⌘/⌥/⌃/⇧) + a regular key.

    Hotkey conflict detection

    • Recorder warns inline when the pressed combo is already bound to another translation target (or to the inbound hotkey).
    • Save button disables until the conflict is resolved.

    Custom LLM style instruction per binding

    • Expandable "Custom style instruction" field on each outbound binding.
    • Overrides the auto-derived (language, register) style — useful when you want very specific tone control for a single target.
    • Empty = uses the default register-aware instruction (back-compat).

    Server-side mirror (translator-server)

    • DeepLProvider — backend mode now supports DeepL Free/Pro with the same formality=more/less mapping as the app.
    • LibreTranslateProvider — backend can self-host or proxy to a LibreTranslate instance.
    • Aliases libre / libre-translate / libre_translate all dispatch to libretranslate.
    • 16 new pytest cases. Total server tests: 68 → 84 GREEN.

    Tests

    • 150 Swift / 35 suites + 84 pytest / 17 classes = 234 GREEN.

    Docs

    • CHANGELOG.md added with version history v0.1.0 → v0.3.1.
    • docs/ENV.md + docs/api-contract.md document the new providers + aliases.

    Upgrade

    • Existing v0.3.0 installs migrate transparently; the new customStyleInstruction field on OutboundBinding defaults to empty (= use derived style).
    • Apple Silicon only (arm64). Ad-hoc signed.

    Install

    1. Download Contextual-Mac-Translator-v0.3.1-macos-arm64.dmg. 2. Open DMG → drag app to /Applications. 3. Launch from /Applications, grant Accessibility. 4. Open Settings → Languages, change hotkeys via "Change…", customize style per target.

    Full changelog

    See [CHANGELOG.md](https://github.com/hoangperry/hp-translator/blob/main/CHANGELOG.md).

    View on GitHub →
  30. v0.3.0 — Multi-language + DeepL + LibreTranslate + Customizable Hotkeys

    Pre-releaseMay 10, 2026

    What's new in v0.3.0

    Multi-language support

    • Pick any language pair. Replaced the hardcoded VI ↔ JP setup with TranslationStyle (direction + BCP47 target language + register). 30 curated languages in Settings dropdown (vi/ja/en/ko/zh/fr/de/es/...).
    • Persona refactor: formal register maps to keigo (JP), jondaemal (KR), vouvoiement (FR), polite English, etc. — the LLM picks per-language conventions from one shared Register enum.
    • My language picker in Settings: every inbound translation targets it; outbound uses it as source.

    Customizable hotkey bindings

    • Unlimited outbound targets. Each (target language + register) pair gets its own configurable hotkey.
    • Default v0.2 layout preserved: ⌘⏎ = Japanese formal, ⌥⏎ = Japanese casual, ⌥D = inbound to Vietnamese.
    • Add/remove targets in Settings → Languages → Outbound translations. Hotkeys re-register automatically when you save.

    2 new free providers

    • DeepL Free — top NMT quality for European + East Asian languages, free tier 500K chars/month. formality=more/less mapped to formal/casual register.
    • LibreTranslate — open-source MT, self-host via Docker for unlimited private use, or use the community instance.

    App now ships 9 direct providers total: Gemini · Ollama · DeepL · LibreTranslate · Google Translate · OpenAI-compatible · Gemini CLI · Codex CLI · Mock.

    v0.2.1 bug fixes bundled

    • Fixed double-HUD when preview opens (loading HUD now dismisses).
    • Fixed Cmd-V not working in Settings text fields (added Edit menu).
    • Fixed Codex/Gemini CLI not found from /Applications (extended PATH with /opt/homebrew/bin).

    Tests

    • 150 Swift tests / 35 suites + 68 pytest = 218 GREEN (+28 from v0.2.0).
    • New coverage for DeepL, LibreTranslate, multi-binding persistence, hotkey display.

    Upgrade notes

    • Existing v0.2 users: settings auto-migrate to vi/ja default with the same hotkeys. Open Settings → Languages to add more outbound targets or change your primary language.
    • Apple Silicon only (arm64). Ad-hoc signed; first launch may need right-click → Open.

    Install

    1. Download Contextual-Mac-Translator-v0.3.0-macos-arm64.dmg. 2. Open DMG → drag app to /Applications. 3. Launch from /Applications, grant Accessibility. 4. Open Settings, configure languages + provider, test hotkey.

    View on GitHub →
  31. v0.2.0 — Multi-provider + Streaming SSE

    Pre-releaseMay 9, 2026

    What's new in v0.2.0

    App-side multi-provider support (Phase 3)

    • New Translation Source picker: Direct API / Custom backend / 1st-party backend
    • 7 direct providers — call APIs without a backend hop:

    - Gemini (Google AI Studio) - Ollama (local, privacy-first) - Google Translate Basic - OpenAI-compatible (LM Studio / OpenRouter / vLLM / OpenAI) - Gemini CLI (experimental) - Codex CLI (experimental) - Mock (echo, for smoke tests)

    • Per-provider credential slots in macOS Keychain
    • TranslationProviderFactory dispatches based on Settings; switch sources without re-entering keys

    Streaming SSE (Phase 5)

    • New /translate/stream endpoint emits SSE chunks
    • Inbound flow shows progressive HUD as tokens arrive
    • Mid-stream errors surface as RFC 7807 frames

    v2 client adoption (Phase 2.5)

    • Idempotency-Key on every request → server cache prevents double-paste
    • RFC 7807 problem-details parsing → richer error messages
    • 429 + Retry-After UX → \"slow down, retry in 3s\" instead of generic error

    Production polish (Phase 7)

    • X-Forwarded-For aware rate limiting (opt-in via env)
    • DEPLOY guide with monitoring, troubleshooting, SSE nginx config

    Tests

    • 122 Swift / 29 suites + 68 pytest / 14 classes = 190 GREEN

    Install (alpha)

    1. Download \Contextual-Mac-Translator-v0.2.0-macos-arm64.dmg\ 2. Open DMG → drag app to /Applications 3. Launch from /Applications 4. Grant Accessibility (System Settings → Privacy & Security) 5. Open Settings, pick a Source, paste credentials, test hotkey

    Notes

    • Apple Silicon only (arm64)
    • Ad-hoc signed; first launch may need right-click → Open
    • Bundle ID still placeholder (\app.lookerlab.translator\); Developer ID signing is on the M2 roadmap
    View on GitHub →
  32. v0.1.5 alpha DMG

    May 8, 2026

    Alpha macOS DMG for local testing. This build adds an onboarding fallback for macOS TCC lag: if Accessibility is already enabled in System Settings but the app status has not refreshed, use Continue Anyway, then quit/reopen if hotkeys do not respond. DMG includes Contextual Mac Translator.app and an Applications symlink. SHA256: acdb48f21ee9bc94e3eb66a624dcad900513b23106a732757b62a45a3c7d7575

    View on GitHub →
  33. v0.1.4 alpha DMG

    Pre-releaseMay 8, 2026

    Alpha macOS DMG for local testing. This build improves the permissions onboarding screen: Accessibility is clearly marked required, Input Monitoring is optional/recommended, Continue only requires Accessibility, and the screen includes guidance for manually adding /Applications/Contextual Mac Translator.app when macOS does not list it automatically. SHA256: 0c67407aa6ea8926fc02b1adc1738ba638c0235871e6b871c50512c480ba53fc

    View on GitHub →
  34. v0.1.3 alpha DMG

    Pre-releaseMay 8, 2026

    Alpha macOS DMG for local testing. Open the DMG and drag Contextual Mac Translator.app into Applications. The app bundle is ad-hoc signed, but the DMG/app are not Developer ID signed or notarized, so macOS may require right-click Open on first launch. SHA256: 9b191ce9bb1b67b0133f74f4ea08282b790a1047bd9ad0f896494e8ed7a4d4f8

    View on GitHub →
  35. v0.1.2 alpha installer

    Pre-releaseMay 8, 2026

    Alpha macOS installer package for local testing. The package installs Contextual Mac Translator.app into /Applications. The app bundle is ad-hoc signed, but the installer package itself is not Developer ID signed/notarized, so macOS may require right-click Open or command-line install. SHA256: 824310dd726e26ab58a8201f2adfe598a97b9824550fdaa9631a910bf8467237

    View on GitHub →
  36. v0.1.1 alpha

    Pre-releaseMay 8, 2026

    Alpha macOS app build for local testing. This build ad-hoc signs the full .app bundle so macOS TCC can identify it as app.lookerlab.translator / Contextual Mac Translator. Still unsigned by Developer ID and not notarized, so macOS may require right-click Open on first launch. Move the app to /Applications before approving Accessibility/Input Monitoring. SHA256: db27c866b44c3324a79318b3eae494529e6a9747a3610b623e3a08ee42555ae0

    View on GitHub →
  37. v0.1.0 alpha

    Pre-releaseMay 8, 2026

    Alpha macOS app build for local testing. This bundle is unsigned/not notarized, so macOS may require right-click Open on first launch. SHA256: 9852561bc7f6717eb95af2c5aab7fad40219b767504e4ff89cfc0637620cdf15

    View on GitHub →