QUILL for All v0.9.0-beta.2 Beta (macOS)
Pre-release
Pre-release
Bug-fix and polish release, driven by beta 1 user reports. Rolls up on top of 0.9.0 Beta 1 (below).
- QuillRichEdit: a native Rich Edit wrapper, real RTF, and a candidate braille fix (experimental) — feedback wanted from braille display owners. A new experimental editor surface wraps the same native Rich Edit control QUILL already ships as its default editor, adding native RTF load/save, in-place bold/italic/underline/font/alignment formatting, and — the part we most need your help with — a candidate fix for two long-standing braille reports: the "cell-two" line-start offset and missing selection dots (7-8) on some displays. Turn it on from Preferences > Experimental: tick Enable experimental features, tick Enable experimental editor surfaces, set Editor surface to QuillRichEdit, tick QuillRichEdit: emulate a system edit control (braille test), then restart QUILL. If you use a braille display, please try it and tell us what you see (cell 1 or 2, selection dots or not, still reads correctly) via Help > Report a Bug — we genuinely don't yet know if this fixes it on real hardware. See the user guide's "QuillRichEdit (experimental)" section for the full walkthrough.
- Wikipedia added to the non-AI Look Up dictionary/thesaurus (#897). Look Up now includes a short encyclopedia summary alongside definitions and synonyms when you're online, with a link back to the source article. Same consent gate and offline fallback as the existing Free Dictionary/Datamuse lookups — nothing changes if you keep Look Up offline-only.
- Clip Library: a rolling clip history beneath Copy Tray's curated 12 slots (#895). Edit > Keep Selection in Clip Library remembers a selection (up to 200, de-duplicated, favorites protected from eviction); Edit > Open Clip Library... lets you search, favorite, remove, copy to the clipboard, or promote an entry into a specific Copy Tray slot. Copy Tray itself is unchanged — this is a second, complementary tier, not a replacement. An optional Preferences > Editing > "Automatically keep everything you copy in the Clip Library" setting (off by default) captures every copy made inside QUILL automatically, no separate Keep action needed.
- Work Personas: named, launchable bundles of a feature profile, working folder, favorite files, and keymap (#896). Tools > Work Personas... creates named bundles ("School," "Novel," "Freelance Client X") that switch your feature profile, change your working folder, reopen your favorite files, and apply a keymap profile in one action — Apply Now in the dialog, or
quill --persona NAMEfrom the command line, or a generated launch shortcut so a persona is reachable without QUILL already running. - Send as Email / Copy as Email Body (#900). File > Send as Email opens your mail client with the current selection (or the whole document) as the body; File > Copy as Email Body puts the same rendered content on the clipboard instead, for mail clients that truncate or reject a long mailto: body. A new setting, Preferences > Editing > "Kept and sent content format" (plain text/Markdown/HTML), controls how both this and the Clip Library render content.
- Accessible AutoOutline: heading auto-numbering (#894). Format > Update Outline Numbering numbers every heading by nesting level — numeric (1, 1.1, 1.1.1) or legal (I, A, 1), set in Preferences > Editing > "AutoOutline numbering style" — as literal text in the heading itself, so it reads aloud, survives copy/paste, and exports cleanly with no extra machinery. Format > Remove Outline Numbering reverts it; re-running after adding, removing, or reordering headings replaces the numbers instead of stacking them.
- Mandatory alt text at insertion, and inline "alt text MISSING" announcements (#899). Insert > Image... is QUILL's first dedicated image-insertion flow: it requires real alt text or an explicit "this image is decorative" choice before it lets you insert, so a document can no longer accrue an un-alt-texted image through this path. Tools > Describe Image at Cursor announces what a screen reader hears for any image already in the document — typed, pasted, or imported — either its alt text, or, just as loudly, "alt text MISSING." GLOW's after-the-fact audit and repair are unchanged; this adds the proactive half.
- Print Studio: an accessible print preview, plus odd/even/reverse/skip-first-page (#891). File > Print Studio... reports a spoken/textual preview — "3 pages, Letter, default margins" — the screen-reader equivalent of a visual print preview, then lets you choose all/odd/even pages, reverse the print order, or skip the first page (for pre-printed letterhead) before handing off to the same Print dialog File > Print already uses. Along the way, printing became properly paginated — it previously drew as much of the document as fit on one page and silently dropped the rest.
- A keyboard-first Header and Footer Builder (#892). File > Header and Footer... lets you build a header and footer from named presets ("Title left, page number right," "Filename and date," "Roman numerals for front matter") or your own combination of a small token set — title, filename, date, page number — placed left, center, or right, with an optional different first page and a choice of numeric or Roman page numbering. Saved per document, and drawn on every printed page (both File > Print and Print Studio).
- Fixed Import → PDF/OCR failing on a clean install with "no PDF extraction engine available" (#909), and made it a one-click download instead of forcing it onto every install. The free-first import pipeline (the MarkItDown converter and the PDF text extractors) was advertised in-app as built-in, but the packages were only in an optional extra the shipping build never installed — so a fresh install had no PDF/Office text extractor at all. Help > Download Optional Components > "PDF and Office text extraction" (~30 MB) now fetches it in one click, on any install, the moment you need it — rather than bundling it whether or not you ever open a PDF or Office document. A PDF with no selectable text still says "this looks like a scanned PDF — use OCR" instead of the same message as a missing engine.
- Fixed "Report a Bug" showing "no token" for users who upgraded (or never signed in). The Windows release build never provided the bundled issues-only token (only macOS did), so it shipped empty and in-app bug reporting failed for anyone who hadn't configured their own GitHub token. The Windows build now supplies the token, and every build — Windows and macOS, release or a throwaway local build — hard-fails if the token is missing, with no opt-out, so a tokenless bug reporter can never ship again; a build-time check also asserts the token that actually lands in the bundle is non-empty (the exact "upgraded and got No token" symptom, locked out). When no token is present at runtime the app now offers the online bug form up front — with a spoken, actionable message — instead of dead-ending at submit. Check for Updates now also self-heals a tokenless build (#21): if the running build is found missing the bundled bug-report token, it offers the latest release even at the same version, with a dialog that says the reinstall restores the bug-report token. A silent background check only records a notification (it never auto-reinstalls the running version); Skip this version silences it, and it stops the moment the token is back — the runtime complement to the build-time "every build ships the token" hardening.
- Fixed a crash opening the Quillins Manager when PyNaCl is not installed (#919). PyNaCl is a dev/CI-only dependency for Quillin Hub artifact signing, never a shipping one, but viewing a Quillin's details (which checks whether it's publisher-signed) imported it unconditionally and crashed with
ModuleNotFoundErroron every real build. The signature check now degrades gracefully — reporting "PyNaCl is not installed" — instead of crashing. - Quillin signature verification now actually works for end users (#919 follow-up). PyNaCl had been a dev/CI-only dependency (the
[signing]/[dev]extras), so every real shipping build shipped without it — the Quillins Manager always showed "PyNaCl is not installed" and could never tell a publisher-signed Quillin from a tampered one. PyNaCl is now bundled as a runtime dependency (the[ui]extra, mirrored inrequirements.txtand the macOS py2app includes), so the "Signature: verified" / "unsigned" lines in a Quillin's details are real on every install. The graceful missing-nacl path stays as defense in depth. - "Casual Writer" is now a true "just write" profile (#890). It used to leave AI, GLOW review, remote files, document analysis, watch folders, notebooks, and the developer console switched on (any feature it didn't explicitly name defaulted to on), which is not what "casual" implies. Those power surfaces are now off by default — while read aloud, voice commands, dictation, OCR, and braille stay available, because a simpler profile must never mean a less accessible one. Turn any feature back on from Preferences > Profiles and Features.
- GitHub Items: a read-only, screen-reader-first repository browser (#924). File > Open from Remote > GitHub Items... opens a list-over-detail viewer for a repository's issues, pull requests, branches, commits, tags, releases, and workflow runs — modeled on the open-source GHManage viewer, on QUILL's existing PyGithub transport and consent/token gates. One View switcher covers all six; the Issues & PRs view is the combined inbox (merged from two API calls) with Show / State / Sort filters. A Quick/Full list mode (GHManage parity) spells each cell as
field: valuein Full mode so a screen reader reads a self-describing line per row. Selecting an issue/PR loads its comment thread and Alt+N/Alt+P jump between comments ("Comment N of M"). Enter opens a row in the browser (and drills a branch row into that branch's commits); Ctrl+R refreshes, Ctrl+O opens in browser, Ctrl+G goes to an issue/PR by number, View More loads the next page. Read-only in v1 (no close/reopen/comment); disabled in Safe Mode; works anonymously for public repos or with your stored token for private ones. The repository field is prefilled from the current document's GitHub origin. - QUILL no longer treats an OS shutdown or logoff as a crash (#920). When Windows logs off, restarts, or shuts down while QUILL is open, the session-end event (
EVT_QUERY_END_SESSION/EVT_END_SESSION) now marks a clean exit instead of leaving a crash marker, so the next launch no longer offers crash recovery for a session QUILL didn't actually crash on. - Fixed a crash registering Windows shell-integration entries on the Python 3.13 runtime (#921). The
OpenWithProgidsentry writes an emptyREG_NONEvalue with the progid as the value name; Python 3.13'swinregis strict and rejects astrfor a binary registry kind, where older Pythons silently accepted it (so the bug only surfaced on the 3.13 embedded runtime). Registry values are now coerced to the type each kind requires — bytes for binary kinds, str for string kinds, int forREG_DWORD/REG_QWORD. - Mastodon posts can set their language, and the character counter respects each instance's limit (#922). The compose dialog adds a language picker (Default/instance, or a specific language) so a post written in one language is filed under that language's preset instead of the account default. The live counter now reads the instance's own per-post character limit (for example an instance that allows 9999) via a one-time cached unauthenticated
/api/v2/instancelookup (with a/api/v1/instancefallback for non-Mastodon forks like GoToSocial/Pleroma/Akkoma that don't implement v2), falling back to 500 on any failure — never blocking a post. - macOS users can launch QUILL from source by double-clicking in Finder (#923). A new
run-from-source.commandwrapper forwards torun-from-source.sh— double-clicking a.shin Finder opens it in a text editor, but macOS runs.commandfiles in Terminal, so the wrapper makes source-launch reachable without opening a terminal. Header documents the first-run Gatekeeper quarantine prompt and the workaround. - Fixed the docs-regenerate CI job failing on pull requests from forks (#925). The job checked out
github.head_ref, which does not exist in this repo for a fork PR; it now checks out the fork's repository at the PR head SHA (and falls back to the repo's own ref on a push), so regenerated epub/html renders build for contributor PRs. - Voice preview (Voice Browser, and Test in Download Optional Components) now plays a "generating, please wait" cue for slow voice synthesis, never overlaps a prior preview with a new one, and its button toggles to Stop while a preview is generating or playing.
- Fixed earcons being completely silent on macOS.
SoundPlayeronly ever triedsound_lib(a licensed engine excluded from every build) andwinsound(Windows-only) — macOS had no audio backend at all, so the bundled Ink sound pack (correctly included in the app bundle) never played. Added an AppKitNSSoundbackend. - Fixed the AI Setup Wizard getting stuck showing OpenAI (or another SDK-based engine) as "active" with no way to configure it, after an install was interrupted. A crashed or interrupted background install could leave a package partially written to disk; the availability check only confirmed the module could be located, not that it could actually import, so a broken install looked permanently "installed." It now verifies real importability, so an interrupted install correctly shows as not-installed and Set Up works again.
- Fixed several related macOS gaps in one pass: opening a file from Finder, the Dock, or
open -a Quill filelanded in a blank document (QUILL never handled macOS's file-open Apple Event); anyCmd+-based keybinding silently produced no shortcut at all on any platform; and Ctrl+Tab/Ctrl+Shift+Tab for switching documents landed on toolbar buttons instead of switching, because macOS reserves Cmd+Tab (whatCtrl+Tabmaps to there) for its own App Switcher. Document switching on macOS now defaults to Cmd+Shift+]/[ instead. - A full-codebase macOS platform review landed ~24 fixes. Read Aloud was silent on macOS for every WAV-based engine (Piper, Kokoro, ElevenLabs, SAPI5, DECtalk) — the live playback path gated on Windows'
winsoundand silently deleted each synthesized WAV; it now plays viaafplayon macOS. The earcon volume slider was a no-op on macOS (NSSoundhad no volume control wired); it now appliesNSSound.setVolume_()per sound.total_ram_gb()returned a flat 8.0 GB on macOS, so every Mac got wrong Whisper model recommendations; it now queriessysctl hw.memsize. DECtalk was offered as an installable component on macOS with no working backend (its only backend isDECtalk.dll); it's now Windows-only, and the "Dictation (offline speech)" description no longer claims SAPI 5 works on macOS (it doesn't). Three macOS keymap collisions —edit.replace(Ctrl+H → Cmd+H = Hide),edit.pop_mark(Ctrl+M → Cmd+M = Minimize),edit.select_chunk(Ctrl+Space → Cmd+Space = Spotlight) — now have darwin alternates (Cmd+Alt+F,Cmd+Alt+M,Cmd+Alt+Space); Find Next/Previous now default to the macOS-standardCmd+G/Cmd+Shift+G. The duplicate "About Quill" Help-menu entry is hidden on macOS (the Application menu already shows it), and the redundantCmd+F4close accelerator is Windows-only. VoiceOver announcements now cap at 4096 chars (so a runaway status string isn't an unreadable wall of text) and carry an interrupt flag (routine status no longer talks over what you're hearing), and the AppKit announce path marshals off-main calls onto the main thread. Document saves and autosave snapshots are now atomic (temp + fsync + rename) so a crash mid-write can't corrupt your real document or make a truncated snapshot the recovery source. The[macos]packaging extra no longer pulls macOS-only build tools (py2app, pinnedsetuptools) on Windows/Linux. The macOS dictation "microphone unavailable" message no longer says "Windows microphone permissions" verbatim. The macOS release CI now runs the Python test suite (pytest -m "not slow"), so theskipif(sys.platform != "darwin")tests finally execute. New dependency tests guard the macOS packaging markers, andtest_high_contrast.pynow actually tests true/false behavior instead of justisinstance(result, bool). - Fixed "Failed to get data from the clipboard (OpenClipboard Failed)" errors during cut/paste. Reading the clipboard made exactly one attempt, so a screen reader or clipboard-history manager briefly holding it produced this error immediately. Clipboard reads now retry briefly (up to ~200ms) before giving up.
- macOS low-hanging-fruit pass (5 fixes from the platform review): tray minimize/restore status messages now say "menu bar" on macOS instead of "system tray" (the feature renders as a menu-bar status item there); the Settings default-folder hint shows a
/Users/...example on macOS instead of a WindowsC:\Users\...path; the AI Hub Engines tab's install-complete callback now guards all four post-install widget calls against a destroyed panel (an earlier fix guarded only the button re-enable, so closing the Hub mid-install could still crash on a later widget); bundled-tool relative paths use forward slashes so the bundled binary is actually found on macOS (a backslash is a literal filename character there, not a separator); and the zero-caller Windows HTML/RTF email-clipboard module is flagged as dead code with a note that a revival needs a macOSNSPasteboardcounterpart (it degrades to plain text on macOS today). - macOS review batch 2 (5 more fixes): MathCAT is no longer offered as a download on macOS (#46) — like DECtalk, its only backend is a Windows
.dll(libmathcat_c.dll) that can never load on a Mac, so it's now gated Windows-only alongside DECtalk in the optional-components catalog. Short keychain secrets no longer leak into logs (#60/#73): the macOSsecurityCLI takes the secret as-w <secret>in separate argv, and a short/non-hex secret slipped past the per-arg redaction that guards the diagnostics log; the value following-wis now redacted explicitly before logging. Three fixes are code-complete and unit-tested here but only show their real effect on a Mac, so they await tester results (reopenable via Help > Report a Bug): Work Persona launchers now write a Finder-launchable.commandshell script (shebang +exec+ chmod 0o755) on macOS instead of a useless Windows.bat(#38); the Simple File Open dialog's "toggle hidden files" chord is nowCmd+Shift+.on macOS instead ofCtrl+H(which is the system Hide-window shortcut) (#51); and eSpeak synthesis pipes very long input (over 8,000 chars) via--stdininstead of passing it as a trailing argv element that can overflow the OS command-line length (Windows ~32,767) and truncate/abort a very long Read Aloud span with no clear error (#64/#77). - macOS review batch 3a (6 fixes, all code-complete and unit-tested here): the braille pack download is now gated Windows-only and points macOS users to Homebrew liblouis (
brew install liblouis), which QUILL detects automatically — the pinned pack ships the Windowslou_translate.exebinary and could never run on a Mac (#47). External-engine resolution now finds a Homebrew/nvm-installed Node on a Finder-launched macOS app, which gets a minimal PATH and never sources the user's shell profile, so a real on-disk install was invisible to the engine allowlist (#48); the lookup checks/usr/local/bin,/opt/homebrew/bin,/opt/local/bin, and the highest version under~/.nvm/versions/node. LibreOffice import now works on a standard macOS install: the three soffice call sites (XLS, legacy Office, and Pages) resolve/Applications/LibreOffice.app/Contents/MacOS/sofficevia a newlibreoffice_executable()helper instead of the bare"soffice"argv that is never on PATH on a Mac, with a Homebrew/installer hint when it's missing (#41). Portable mode no longer silently drops credentials on macOS: with no DPAPI-equivalent single-folder store off Windows, a portable Mac build now routes load/save/delete to the login Keychain (the best available store) and logs a warning that Keychain storage is system-level, not portable, instead of losing the key with no signal (#35). The two drift-prone platform-dispatch mechanisms are consolidated:quill.platform.dispatchnow delegates to the module-level-gated root helpers (high_contrast,sr_detect) instead of carrying its own copy of the platform branch, so a future macOS-routing change applied in one place is picked up everywhere (#7). And the macOSshell_integrationmodule — Info.plist document types, the best-effortdutiassociation path, and its no-op/off-darwin branches — now has direct test coverage (#50). - macOS review batch 3b (7 fixes, all code-complete and unit-tested here; #2 native macOS TTS deferred to batch 3c): PDF import now distinguishes four real failure modes instead of collapsing them all into "looks scanned — use OCR": encrypted (password-protected, suggests
qpdf --decrypt), damaged (a real parse failure, suggestsqpdf --checkand notes OCR won't help a corrupt file), scanned/image-only (genuinely points at OCR), and unavailable (no extractor installed, points at Help > Download Optional Components), each with its own engine tag; a new_is_encrypted_pdfprobes the empty user password so a permissions-only lock isn't misreported as encrypted (#58). Keymap packs no longer apply verbatim on macOS:build_keymap_for_packroutes darwin pack overrides through_apply_darwin_pack_overrides, which folds Ctrl->Cmd for comparison only (wx maps ACCEL_CTRL to Cmd at runtime, so a stored "Ctrl+G" and a default darwin "Cmd+G" are the same runtime shortcut even though they compare as different strings), drops overrides whose runtime chord is macOS-system-reserved (Cmd+H/M/Q/W/Space/Tab/Grave, F9-F12, the Alt+letter deadkeys), and drops overrides that collide with another command's runtime chord — so no pack silently steals a system shortcut or a sibling binding; storage is unchanged, only the comparison folds (#4). The screen-capture off-Windows message now names the macOS Screen Recording permission (System Settings > Privacy & Security > Screen Recording) and the built-in Cmd+Shift+3/4/5 shortcuts instead of the bare "only available on Windows" (#5).install_shell_integrationnow returns aShellIntegrationStatus(hosted in the platform-neutralquill/platform/shell_integration.py) so the caller reports why nothing happened whendutiis missing — a clear "install it withbrew install duti; the app bundle's Info.plist associations still apply" message — instead of a false success (#8). Three are code-complete and unit-tested here but only show their real effect on real macOS hardware, so they're closed pending tester validation (reopenable via Help > Report a Bug):quill/platform/macos/high_contrast.pykeeps the Increase Contrast detection (is_high_contrast_enabled) re-exported through the neutralquill/platform/high_contrast.py; theis_dark_mode_enabled/is_reduce_motion_enabled/macos_appearance()helpers were dead code with no UI consumer (Dark Mode is read via wx'sSystemSettings.GetAppearance().IsDark()instead) and were removed (#6); the STT self-test (verify_component) now synthesizes its test clip via the built-in macOSsaycommand on darwin (and SAPI 5 on Windows) so the speak->transcribe confidence loop runs on a Mac without a SAPI dependency (#29); and pausing Read Aloud mid-sentence now keeps the cursor at the sentence start (re-reads the partial sentence on resume) instead of advancing tospan.endas if the whole sentence had been spoken, for both the live eSpeak and WAV runners (#65/#78). Also #40 closed as premise-false: theCtrl+Alt+Shift+chords andAlt+Shift+Dare actively routed (to the AI/compare command class andview.toggle_dark_mode), not dead. - macOS review batch 3c (6 fixes, all code-complete and unit-tested here; the full Read Aloud macOS engine #21/#75 is wired and awaiting Mac validation): Keychain secrets no longer reach the command line on macOS (#1/#16/#43):
quill/platform/macos/keychain.pynow talks to Keychain through the native Security framework (pyobjc) first —SecItemAdd/SecItemCopyMatching/SecItemUpdate/SecItemDelete— passing the secret only in the Keychain item'skSecValueDatafield where it never becomes a process argument, instead of thesecurityCLI that takes the value as-w <secret>argv (visible to other processes and the diagnostics log); the leaky CLI fallback still exists for machines without pyobjc but now warns the first time it's used that the secret will touch the command line, and the no-argv-leak guarantee is pinned by cross-platform branching unit tests (SecItemAdd used, nosubprocess.run, secret inkSecValueData, duplicate->update, both pyobjc return-tuple conventions normalized).install_shell_integrationnow refreshes LaunchServices after registering associations (#74): it force-registers the app bundle withlsregister -fso the new default takes effect immediately instead of leaving the LaunchServices database stale until a reboot, and_app_pathnow walks up from the running executable to find the enclosing.app(recognizing a bundle from a Finder/Dock launch, not only when launched from inside one), with arefresh_launch_services()helper gated off-darwin/missing-bundle/missing-tool. The Window menu is now registered with the system as the macOS Window menu (#76):menu_bar.SetWindowMenu(window_menu)on darwin (guarded like the existingSetHelpMenuhint) tells AppKit to move it to its conventional slot just left of Help and merge in the standard Minimize (Cmd+M)/Zoom/Bring-All-to-Front items and the live window list alongside QUILL's own Next/Previous/Close-Other/Send-to-Tray entries. macOS announcements now self-voice when VoiceOver is off (#2):quill/platform/macos/tts.pyis a new native TTS backend (NSSpeechSynthesizer.availableVoices()/attributesForVoice_()catalog +speak_announcement/stop_announcement), and the darwin announce branch inprism_bridge.pynow routes to VoiceOver when a screen reader is running but to the native synthesizer when it isn't — mirroring the Windows SAPI self-voice fallback so a low-vision Mac user without VoiceOver hears "Saved"/"Ln 12, Col 7"/the QUILL-key chord instead of silence; the routing is pinned by cross-platform branching tests (both darwin branches exercised deterministically) plus darwin-gated live tests that assertavailable()/list_voices()on macOS CI. Code-complete, unit-tested, awaiting Mac-hardware validation (#21/#75): "macOS (system voice)" is now wired as a first-class Read Aloud engine in Speech Hub —macos_say_available()/synthesize_with_macos()/list_macos_voices()inread_aloud.py(thesayCLI backend; >8000-char input via-ftempfile like the eSpeak--stdinguard; pyobjc voice catalog withsay -v ?fallback), themacosengine in_valid_engines/start()/dispatch/_run_macos_live, the settingsread_aloud_macos_voice/read_aloud_macos_ratefields + spec choice, the Speech Hub engine option/voice picker/preview/persist,batch_export+document_speech+project_profile+batch_speech_runnerintegration, andvoice_browser_dialograte handling; off-darwin it is inert (raises/guarded) and pinned by 10 new unit tests. - macOS review batch 3d — Windows-only UI strings sweep + module-ownership retarget (#37, #2/#10). ~20 user-facing strings that named a Windows-only mechanism rendered verbatim on macOS; they now adapt to the platform via a new wx-free, gettext-wrapped
quill/core/platform_nouns.py(credential_store_name()returns "macOS Keychain" on darwin vs "Windows Credential Manager" on Windows, used in the AI Hub Auphonic note, the Audio Studio SFTP publish note, the GitHub sign-in and forget-AI-key messages, and the trust-consent storage clause;primary_command_chord_label()returns "Cmd+Alt" on darwin vs "Ctrl+Alt" for help/status text that examples a chord). "Windows dictation" status messages are now "System dictation" everywhere; the setup wizard and settings data-location label say "In my user profile" instead of "In my Windows user profile"; the egress-audit note names both vaults. On Windows the helpers return the original strings so behaviour and the accessibility source-contract tests are unchanged. The.potwas regenerated and the three invalidated Italian entries cleared to English fallback (the.morecompiled). Separately (#2/#10), the five remainingquill.platform.windows.sr_announce/announce_engineimport sites inmain_frame.py,main_frame_intellisense.py,main_frame_format_codes.py, and the accessibility tests now import from the platform-neutralquill.platform.sr_announce/quill.platform.announce_engineshims (thetts_init_faileddeferred import inmain_frame.pystays direct so the existing monkeypatch test keeps working) — pure import-path change, no behavioural change, finishing the module-ownership cleanup so macOS routing lives behind a neutral facade. - macOS review completion pass — six smaller fixes. Extend-selection now follows native Mac semantics (Option selects by word, Cmd by line/document); the OK/Cancel button rows in dialogs use the native Mac order; pasting HTML as Markdown tries the
public.htmlclipboard flavor on darwin; the New Sticky Note hotkey emits a clear status message on macOS; the close handler no longer calls Hide()+Veto() when there's no tray icon to hide into; and the diagnostics bundle collects~/Library/Logs/DiagnosticReportscrash logs on macOS so a Mac crash is captured the way a Windows one is. - Help wanted from macOS users — three things we'd like you to confirm on real hardware (none of these block the release; they're confirmation we can only get from a real Mac, reopenable via Help > Report a Bug). (1) Sound and Read Aloud audibility. The macOS sound backend (AppKit
NSSoundfor earcons, and theafplayRead Aloud path inread_aloud.py) is unit-tested with fakes only on our Windows dev box; we'd like to hear whether earcons and Read Aloud are actually audible on your Mac, and whether the 16-entry live-sound retention is enough under real earcon firing rates without AppKit tearing a sound down mid-playback. (2) File-open from Finder and the document-switching chords. Opening a file from Finder, the Dock, oropen -a Quill file(an Apple Event, notargv) is standard wx API, but the cold-launch race — where a file-open event can arrive beforeMainFramefinishes constructing — and the default document-switching chord (Cmd+Shift+]/[, chosen to match Safari/Xcode's tab-cycling convention), plus the newerCmd+Alt+F/Cmd+Alt+M/Cmd+Alt+Spacedarwin alternates, are worth a real Mac user's eyes before we call them final; tell us if any collides with something on your setup. (3) A latent, not-yet-reproduced intellisense crash risk. The earlier #917/#918 fix made_IntellisensePopup.is_visible()tolerate a deleted C/C++Frame, but a sibling path (_show_intellisense_popup) still callspopup.update(...)/popup.show(...)after that check — if a future keystroke reaches it with the same dead-frame condition, those calls are unguarded and could raise the same class ofRuntimeErrorsomewhere new. No crash report evidences this path is actually reached (the two filed crashes were both in the now-guardedis_visible()path), so this is a documented risk, not a confirmed bug — please report it if you see a similar crash with a different traceback. - Fixed 4 crash reports from user feedback: the Spell Check Language chooser crashing immediately on open (a missing argument); word-prediction crashing after a crash-recovery restart (a stale reference to an already-destroyed popup window); and the AI Hub's Engines tab crashing if closed while a background install was still in progress.
- Added API-key setup for OpenAI Agents SDK and Claude Agent SDK. Unlike GitHub Copilot (which has its own guided sign-in), these two engines previously had no in-app way to add a key at all once installed — Set Up in the AI Hub's Engines tab now opens a small dialog to paste, save, or remove the key, applied immediately without a restart.
- The AI Hub now shows each Ollama model's real capabilities, and auto-probes a running Ollama. Ollama models are discovered via
/api/showand tagged with their actual abilities (Vision, Tools) instead of a hardcoded list, so a model's badges reflect what it can really do. Selecting the Ollama provider also auto-probes/api/tagsand announces whether Ollama is running and how many models are available, so you know immediately whether local AI is reachable. - Fixed Convert Non-ASCII to HTML Entities freezing QUILL (and your screen reader) on a large document (#906). The per-character transform loop ran synchronously on the UI thread; on a 1MB+ file it could take the better part of a minute, during which QUILL (and NVDA riding along with it) went Not Responding. It now runs on a background thread like every other potentially-slow operation.
- Fixed Convert Non-ASCII to HTML Entities leaving a pointless UTF-8 BOM behind (#905). Converting a UTF-8 document with a byte-order mark left the file tagged as
utf-8-sig, so saving still wrote the three-byte BOM even though every non-ASCII character was now an HTML entity and the file was otherwise pure ASCII. The BOM is now dropped when the conversion leaves the whole document ASCII (a partial-selection conversion that leaves non-ASCII text elsewhere correctly keeps it). - First-run "Personalise QUILL" now offers to restart when you change where your data lives. Choosing to store data next to the app (or any other new location) on first run applied it only on the next launch — the same safe, restart-deferred behavior Preferences already uses, but silently, so the choice looked ignored until you happened to relaunch QUILL. It now offers the same Restart Now/Later prompt Preferences gives.
- The status bar's Message cell no longer repeats what another cell already says. A user reported it sometimes showing the same text as the Page cell. Message is meant to always show something — but not the same something twice — so it now hides itself whenever its text exactly matches another currently-visible cell's.
- Fixed offline speech-model downloads crashing with
'NoneType' object has no attribute 'write'. The bundledquill.exeis a windowedpythonw.exewith no console attached, sosys.stderrisNonethere; the whisper.cpp/Faster Whisper download progress bar (tqdm) defaulted to writing its own bar tosys.stderrand crashed the moment any progress arrived. The progress-reporting subclass now givestqdma real, harmless sink instead. - Fixed Piper voice Test reporting "Piper model file was not found" for a voice that was just downloaded. The preview code was passing the bare catalog voice id (e.g.
"en_GB-alan-medium") directly as a file path instead of resolving it to the actual downloaded.onnxfile. - The braille pack's Test result now actually shows the installed LibLouis version. It previously tried
import louisto read the version — but QUILL never imports liblouis in-process (BR-020; it's a subprocess worker), so that import always failed silently and the version always read "unknown." The version now comes from the pack's ownmanifest.json. - Downloading something from Download Optional Components now reselects the row you were on, instead of resetting the list to the top — including the spell-check dictionary rows, which previously dropped you into the editor instead of returning to the hub at all.
- Vosk no longer has its own row in Download Optional Components. It's a third engine choice (alongside Whisper.cpp and Faster Whisper) inside the same guided offline-speech picker, so there is exactly one place to set up offline dictation instead of two.
- Audio export, playback, and chapters are now one place. FFmpeg (compressed-audio export), the mpv playback engine, and MP3 chapter markers are a single "Audio: export, playback & chapters" row in Download Optional Components instead of three scattered entries — with each piece still fetched only when its feature is first used, so nothing large downloads until it is needed. (FFmpeg previously had its own row.)
- Testing an offline speech engine with no model installed yet now reopens the guided engine+model picker, not the full Speech Settings dialog — getting the missing piece stays a one-button action instead of a bigger detour.
- Added "Set as Default" for the offline speech model (a button and a right-click context menu in Manage Speech Models) and made it explicit for Read Aloud voices too (previously only implicit via the dialog's OK button). The guided offline-speech picker now also sets what it just installed as the default automatically.
- Setting up offline dictation is now a guided, one-step-at-a-time journey. The Download Optional Components row (renamed from "Offline speech engine (whisper.cpp)" to "Dictation (offline speech)") and the Manage Speech Models panel now lead with a banner that always says which step you are on and the single next action — 1. pick and install an engine, 2. download a model (the best fit for your computer is pre-selected, so it is one click), 3. Test dictation and set it as your default. A new Test dictation button runs the self-test inline for the engine you selected (Whisper.cpp, Faster Whisper, or Vosk — the test now covers all three, not just Whisper.cpp and Vosk), and a successful test also makes that engine your default, so "try it" and "use it" are one action. Each engine option in the guided picker announces its trade-off on focus ("light and fast, works on any computer" / "most accurate" / "tiny, for old or low-memory machines"), so a screen reader hears the difference without opening the docs.
- Manage Speech Models now remembers the engine you chose. It used to reopen on the built-in Whisper engine even after you switched to Faster Whisper, because the saved engine was only honoured when it was already fully ready. It now opens on your saved engine — showing its "needs a model" state when setup is unfinished — instead of silently snapping back.
- Offline-speech Test results are now written to the log, so a failure is captured in a diagnostics bundle instead of living only in the on-screen message — making a "the test didn't work" report far easier to pin down.
- Node.js moved to the bottom of Download Optional Components, as the least-used extra.
- The Speech Settings dialog now splits Speech and Dictation into Offline/Online tabs (Speech (Offline), Speech (Online), Dictation (Offline), Dictation (Online)) instead of mixing local engines and API-key cloud providers in one flat list per tab.
- Fixed
speech_provider's settings validation silently rejecting"vosk"as an unknown value (a latent bug exposed by Vosk's guided-picker consolidation above). - Every document now shows a Page indicator (#872). A beta tester asked whether QUILL could show real page numbers. It can, for PDFs -- QUILL now preserves each PDF's real page boundaries at import, so the status bar's new Page cell (on by default, right next to your line/column position) reports an exact "3 of 12," and
Ctrl+Shift+G(Go To Page) jumps exactly. For plain text, Markdown, and Word documents -- where a "page" has no real meaning until you print or export -- the same cell shows a clearly-labeled estimate based on word count instead: "~3 of ~12 (estimated)." The tilde and the word "estimated" always appear together, so an estimate is never mistaken for a fact; tune the words-per-page assumption in Preferences > Navigation and QUILL Key. Braille documents are unaffected -- they keep their own, richer page system. - Crash reports no longer leak your Windows username (#886). A portable copy running from inside your home directory (e.g.
C:\Users\yourname\...) had its local crash-report file path embedded verbatim in the metadata block of a submitted bug report, bypassing the redaction that already scrubbed the same path everywhere else in the report. That path is now redacted the same way before it's included. - Fixed a crash opening the Outline Navigator (#885).
Ctrl+Shift+O(and the EPUB/Quick Nav variants that share the same dialog) could throw awxAssertionErrorand fail to open, because the tree control's hidden root was being expanded, which wx disallows. The invalid call is removed; the outline shows normally. - Fixed AI Hub focus jumping away from the Provider list (#883). Choosing a provider with the arrow keys (without opening the dropdown) could bounce focus over to the Model field as its suggestions refreshed. Focus now stays on the Provider list when that happens.
- **Fixed the QUILL key not responding to Ctrl+Shift+
on macOS.** wx reports the Cmd key (not the physical Control key) throughControlDown()on macOS, so a literal Ctrl+Shift+press was invisible to the QUILL key's shortcut handling. It's now recognized via the physical-Control detection macOS exposes separately. (Cmd+Shift+` is macOS's own reserved "cycle windows" shortcut and is not affected by this fix — use the physical Control key, or reassign System Settings > Keyboard Shortcuts > Mission Control if you'd rather keep Cmd.) - Fixed a crash opening non-UTF-8 text files (#867). Opening a
.txt/.mdfile saved in Windows-1252/Latin-1 (a curly quote, en-dash, or other high-byte character without a UTF-8 BOM) used to throw an unhandledUnicodeDecodeErrorstraight out of the open flow. Reading now falls back from UTF-8 to cp1252 to latin-1 (the last of which can never fail), and the status bar announces "Opened using {encoding} text encoding (not UTF-8)" when a fallback fires so you know the source wasn't UTF-8. - Removed an unbounded UI freeze source in the speech probing path (#866).
has_nvidia_gpu()callednvidia-smiwith no timeout, once per Speech Hub open, directly on the UI thread — a stalled driver/service could block QUILL indefinitely (matching a crash-recovery report of a 100+ second UI freeze). The probe now runs through QUILL's timeout-enforcing subprocess wrapper and is cached for the process lifetime. - Made Whisper model downloads self-diagnosing. whisper.cpp models now download through
huggingface_hub(the same library Faster Whisper already uses) instead of a hand-rolled request, so a future stale pin surfaces a clear "this QUILL build's model reference is out of date, please update QUILL" instead of a generic "download failed." - Offline transcription no longer mistakes the
openai-whisperCLI for whisper.cpp (#931). If you had theopenai-whisperPyPI package installed, QUILL matched its barewhisper/whisper.execommand and ran it with whisper.cpp flags, so transcription failed with a confusing error. The detector now requires the real whisper.cpp binary name (whisper-cli/main), so a genuine whisper.cpp install is used and the OpenAI CLI is left alone. - Kokoro voices now report readiness accurately, and point at the fix. Read Aloud used to mark Kokoro "available" once its model files were downloaded, even when the
kokoro_onnxpip package hadn't installed — so selecting the voice failed at synthesis time with a confusing two-option error. The readiness check now requires both, and the failure message leads with the actual fix: Tools > Speech > Install Kokoro ONNX. - Speech Hub, Manage Speech Models, and Manage Voices now open with real content focused, not the OK/Cancel button.
- The Download Optional Components hub is far friendlier. Help > Download Optional Components now leads with what people reach for first (Pandoc, then the braille pack), shows a plain-language description and a size for every row (spell-check dictionaries included), and — for anything installed — adds a Test button that proves it works and a Remove button. Test speaks a sample through a voice so you actually hear it, transcribes a spoken phrase through the offline speech engine and shows what it heard, or reports a tool's version; Remove deletes QUILL's downloaded copy and turns its features back off (removing the voice engine you were using switches Read Aloud back to the system voice). Piper voices and the Node.js runtime are now listed too, the window opens instantly (the list loads in the background instead of stalling on version probes), everything installs into your portable data folder in portable mode, and if a download or test fails QUILL offers to send a bug report with the details.
- The installer no longer prompts for optional components. Installing 0.9.0 Beta 1 presented a custom-install page asking you to choose Pandoc, Node.js, Piper, and Braille-pack components — but those are all fetched on demand now, so in the shipped build several of those checkboxes installed nothing. The installer ships only QUILL's core (no component or setup-type selection page at all); Pandoc, Piper (neural Read Aloud voices), Node.js, the braille pack, and the offline speech engines all download at point of use — Piper via Manage Voices, the braille pack and Pandoc via Help > Download Optional Components — completing the PRD 10.2.x footprint unbundle that had already dropped whisper.cpp, Kokoro, DECtalk, and eSpeak-NG.
- Built-in keymap profiles now stay platform-aware, and Preferences is reachable from the standard macOS app menu. The shipped keymap profiles no longer override the platform-specific defaults for quit, back/forward location, and document switching, and the Preferences command now uses the stock macOS app-menu id so it appears in the expected place on macOS.
- The advertised AI, compare, and dark-mode shortcuts now fire as intended. Proofread, translate, compare-navigation, and dark-mode commands were present in the menus but not wired through the shared accelerator path, so their documented chords could appear in the UI without actually running. They now honor the bindings shown in the UI and the keymap editor.
- You can now reassign the QUILL key itself. A new Change QUILL Key... button in the Keymap Editor (Preferences > Keyboard > Keymap Editor) lets you pick a new prefix key for every QUILL-key chord: it validates the choice, warns about conflicts with other bindings and OS-reserved chords, and rewrites your stored chords to follow the new prefix, so
QUILL Key + Sand friends all move together. The Keymap Editor's search box also lists every QUILL-key-chord command when you typequill, so you can see and rebind the whole chord set in one place. Cross-platform — the same on Windows and macOS. - Tabbed dialogs now open with the first control focused, not the tab strip. The AI Hub (and the About and Quillin-preferences dialogs) opened with focus parked on the notebook tab control, so a screen reader announced a tab while users heard the first field's label and assumed they could arrow into it — landing on nothing. Initial-focus routing in the shared dialog contract (
focus_primary_control) is now notebook-aware: it drills into the selected tab and lands on that page's first real control, and the per-dialog overrides that fought it were removed. Any tabbed dialog that goes through the contract now behaves this way — including the About Whisperer dialog and the Help status dialogs, swept in the same pass. - Fixed Piper (and every other Read Aloud engine) mis-reading Markdown syntax aloud. Live Read Aloud and Export to Speech Audio used to hand raw
#/**/[text](url)characters straight to the synthesizer; Piper's espeak-ng phonemizer in particular mis-tokenized it badly enough to sound like a different language. Both paths now share the same Markdown sanitizer batch export and document-to-speech already used. - macOS: Pandoc installed via the pandoc.org installer (not Homebrew) is now detected. A Finder-launched QUILL doesn't inherit
/usr/local/binfrom the shell profile the way a terminal does, so a genuinely-installed Pandoc could still be reported missing. QUILL now also checks the well-known install locations (/usr/local/bin,/opt/homebrew/bin,/opt/local/bin) when PATH lookup comes up empty. - Fixed "Writing && Language" and other doubled ampersands in menu titles (#876). Several Tools-menu submenu headers ("Writing & Language," "Reading & Dictation," "HTML & Encoding," "Sort & Filter," "Customize & Support") used wx's
&&escape for a literal ampersand, which some screen readers read back as two ampersands instead of one. Those labels now spell "and" instead of relying on the escape sequence. - Added Danish to the Braille Translation menu (#877). Danish grade 1 and grade 2 tables ship with liblouis and were on disk, but QUILL's curated language list never included them, so Danish never appeared under Tools > Braille > Translation > More Languages.
- Italian now actually appears under Change Display Language. Elena Brescacin's complete Italian translation shipped in Beta 1, but the build never compiled
quill.mo, so it was never selectable in the language picker. The compiled.monow ships with every build, and a regression test guards every tracked.poso a language can't silently vanish again. - The Italian translation no longer conflates two different "vaults" (#913). The OS credential vault and QUILL's document vault had landed on the same Italian word; they're now distinct — the credential store is "archivio delle credenziali" and the document vault is "raccolta" — and stale fuzzy markers are cleared, with "encoding" corrected to "codifica".
- Fixed a crash opening Profiles and Features (#878, #879, #880, #907). Built-in profile names are translatable and use a lazy-loaded string internally; the dialog's list box requires a fully resolved string and raised
TypeError: Item at index 0 has type '_LazyString'...the moment the list was built, whether opened from the Tools menu or the command palette. - Fixed the status bar re-announcing "Status Bar" on every arrow keystroke. Pressing F6 into the status bar and then arrowing between cells made JAWS say "Status Bar" before every single cell. The region name is now spoken once, when you first land on the bar; arrowing between cells announces just that cell's label and value (e.g. "Position, Ln 12, Col 7") — the quieter, less repetitive behavior the status bar was always meant to have.
- Stable error codes for support triage, now across the whole codebase (#873). A
CodedErrorbase (quill/core/error_codes.py) prefixes a stable, greppable code — e.g.[QUILL-SPEECH-WHISPER-DL-404]— onto an error's message, and the code rides along in crash-report metadata automatically, so a pasted error message alone tells support which failure branch fired. Every custom exception class acrossquill/core,quill/io, andquill/stability(45 in all) now carries one, and a new CI gate (quill/tools/error_code_audit.py, GATE-EC) fails the build if a future exception class is added without a unique, correctly-shaped code. - Kokoro voices now actually speak on a clean install (#881). After downloading Kokoro, synthesis failed with a misleading "needs one more component" message. Root cause (from a submitted diagnostics log): the packaged build pruned
babel, butkokoro-onnximports it transitively at runtime (kokoro_onnx → phonemizer → segments → csvw → babel.numbers), so its onnx path couldn't import and silently fell back to the torch route. Fixed by not pruning babel; it now ships (or, per the unbundle below, arrives with the on-demand install). - Fixed Piper voice preview crashing after downloading a voice (#882). Preview worked before a voice was downloaded, then failed afterward with
'Settings' object has no attribute 'read_aloud_piper_executable'— the field was read at the preview call site and listed in the settings-export allowlist but was never declared on theSettingsdataclass. Mirrorsread_aloud_dectalk_executable/read_aloud_espeak_executable, which were declared correctly. - Smaller installer — Kokoro is downloaded on demand. The kokoro-onnx package (with onnxruntime, phonemizer, and babel) was bundled in the installer even though Kokoro's models already downloaded on demand. It is now installed on first use via
install_kokoro_onnx()into an engine-pack, trimming the installer by roughly 23 MB (and ~600 MB unpacked). Kokoro's first use downloads it, consistent with the rest of the footprint unbundle. - DECtalk now speaks instead of opening a Python file. In the packaged app, DECtalk drove its out-of-process worker as
[sys.executable, dectalk_say.py], butsys.executableis thequill.exelauncher there — so QUILL opened the worker script as a document instead of running it. The worker now runs through the embeddedpythonw.exe/python.exebeside the launcher. - Testing a not-yet-ready component now guides you, not a bug report. In the Download Optional Components hub, testing whisper.cpp with no model (or a voice engine with no voice) reported an error and offered to file a bug report for a completely normal not-yet-downloaded state. It now routes you to finish setup (the guided offline-speech picker for a missing model, Manage Voices for a missing voice) and only offers a bug report on a genuine failure — which also removed the failure dialog that could strand keyboard focus behind the hub.