Releases: okstudio1/alpha-osk-releases
Release list
v1.1.0
Installing on Windows: about the SmartScreen prompt.
On first launch you may see a blue "Windows protected your PC" screen. This is normal for newer apps and does not mean anything is wrong. Alpha-OSK is digitally signed by OK Studio Inc. Click More info and you will see that publisher name, then click Run anyway to install. The prompt stops appearing on its own as more people install. (Microsoft removed the SmartScreen fast-pass for code-signed apps in 2024, so reputation now builds from download volume, not from the certificate.)
[1.1.0] (2026-05-19)
Headlines: a new "Data Backup" feature (export your model, lifetime stats, and imported vocabulary packs to a portable .zip; import on a new machine to restore everything in place), pill-click learning now passes through the same 3-sighting candidate gate as free-typed words (a single accidental click can no longer permanently change the model), a clickwrap MIT license page in the Windows installer, live download / install progress in the auto-updater popup and the relauncher splash, and a documentation overhaul (segmented onboarding README, screenshot gallery, and white-paper figures / Data Backup section / macOS section).
Added
- README onboarding rewrite. Plain-language tagline replaces the engine-acronym lead. The dark-theme keyboard screenshot moved to a hero position above the fold. New three-column "Pick your path" card routes visitors by audience (end users → Install + First launch, developers → Architecture +
CLAUDE.md, reviewers → white paper + Status). New "First launch" walkthrough (six numbered steps from installer to first word to settings tour) and a ten-question FAQ covering the actual common stumbles (Slack/Discord/IDE keystroke issues, move/resize, pause learning, theme, opacity, data location, telemetry, vocab packs, emoji). Install restructured into a Platform / File / Notes table. Screenshot gallery moved below First launch. - White-paper refresh. Five inline figures sourced from the live app (full keyboard, Appearance panel, Data & Privacy panel, analytics dashboard, Word Cloud). New §5.5 Data backup (export / import) covering archive contents, replace-not-merge import semantics, the rescue archive, and the allow-list extraction validation. Telemetry renumbered §5.5 → §5.6 and Auto-update threat model §5.6 → §5.7 with all inline cross-references updated. New §7.5 macOS (in progress). Stale test count
450+bumped to640+. Top-of-doc version line clarified as "Document revision" distinct from app version. 78 pre-existing em dashes stripped per the global writing-style preference (paired dashes to parentheticals, bullet term-definitions to colons, others to sentence breaks). assets/screenshots/new directory with six images sourced from the live app: dark theme keyboard, Amethyst theme keyboard, Appearance settings panel, Data & Privacy settings panel, Word Cloud tab, Dashboard tab.- Data export / import (back up your model, move it between machines). New "Data Backup" section in Settings → Data & Privacy with Export Data… and Import Data… buttons. Export bundles the prediction model (
ngram_model.json+ppm_model.json), lifetime analytics (analytics.json), and every imported vocabulary pack into a single.ziparchive with a manifest (schema version, app version, ISO-8601 UTC timestamp, file list, pack ids). Import reads the archive, shows a preview card (app version + export date + file/pack counts), and on confirmation replaces the user's current data, after first writing a timestamped rescue export of the existing state to<config_dir>/exports/rescue-<ts>.zipso the user can roll back by importing that file. The predictor and lifetime analytics live-reload from disk after import (no restart needed); enabled-pack state resets so the user re-enables packs on the new machine.telemetry.jsonis deliberately excluded from exports: copying theanon_idacross machines would link contributions, which the telemetry consent docs explicitly promise not to do, so a freshanon_idis generated on the new machine when telemetry is re-enabled. Settings (theme, layout, toggles, window size) are not exported either; they live in the Qt settings layer (Windows registry / Linux config) and are quick to reconfigure manually, while the irreplaceable bit is the model. Security hardening on import: allow-list extraction (a hand-edited archive can't smuggletelemetry.jsonor arbitrary files past the extractor), zip-slip rejection (..components, absolute paths, drive prefixes, backslashes), per-file (75 MB) / total uncompressed (500 MB) / archive-on-disk (200 MB) size caps that trip onfile_sizemetadata before any bytes hit disk, and forward-compatibility rejection when the manifest'sschema_versionexceeds what this build supports ("upgrade Alpha-OSK first" rather than half-applied state). Atomic-rename writes through.importingtempfiles so a partial copy can't corrupt the existing file. New modulesrc/data_export.py, new bridge slots (exportUserData,inspectUserExport,importUserData,getDefaultExportDir,getSuggestedExportName), newHybridPredictor.reload_from_disk()andTypingAnalytics.reload_from_disk(). 20 regression tests intests/test_data_export.pycover the round-trip, every security gate, and the telemetry-exclusion invariant. Full design in CLAUDE.md § Data Backup (Export / Import). - Pill-click learning now passes through the 3-sighting candidate gate. Clicking a prediction pill for a word that the engine generated but the user has never actually typed (a fuzzy-recogniser completion of a typo, a PPM character-level extrapolation, a vocab-pack word the user hadn't fired before) used to inject the word into
user_vocabpermanently with weight 5 on the very first click, so a single accidental click could permanently change the model. Symptom: words showed up in the Model Visualization dashboard after one keypress when the user expected they wouldn't be added until "really" learned. NewNgramPredictor.learn_from_pill_click(word)mirrors the gate already used by free-typing inlearn(): known words (already in the Google 10K base dict oruser_vocab) get the immediate +5 boost as before, but unknown words go through_candidate_countsand only promote intouser_vocabafter three clicks (landing with cumulative weight 3 × 5 = 15).HybridPredictor.learn_from_selectionroutes through this; trailing bigram / trigram reinforcement still fires immediately on each click since the context edge was validated by the click and a bigram pointing into a not-yet-promoted unigram surfaces the word only in that specific context, which is exactly the loop that drives "click more times to promote." Right-click → Show more and vocab-pack import deliberately bypass the gate (those are explicit user-boost actions, not implicit signals; gating them would breakunprefer's rollback math). Plus: candidates now carry a_candidate_last_seentimestamp, and_sweep_stale_candidates(called from_apply_decay) drops entries older than 30 days so an accidental sighting doesn't sit in the pool indefinitely waiting for the multiplicative decay to drain it. Timestamps persist across save/load; older save files without the field have entries backfilled on the next sweep rather than instantly expired. Coverage intests/test_ngram_predictor.py::TestLearnFromPillClickandTestStaleCandidateSweep, plustests/test_hybrid_predictor.py::test_learn_from_selection_gates_unknown_word/_promotes_after_threshold. - Clickwrap license page in the Windows installer. New
LICENSE.rtf(MIT) shipped underbuild/windows/and wired into the generated NSIS script asMUI_PAGE_LICENSEbetweenWELCOMEandDIRECTORYwithMUI_LICENSEPAGE_CHECKBOXenabled, so the Next button stays disabled until the user ticks "I have read and accept the terms of the license agreement." CustomisedMUI_LICENSEPAGE_TEXT_TOPclarifies the action ("You must accept them to install Alpha-OSK"). The repo-rootLICENSE(already MIT) is unchanged; the RTF is a formatted copy with bold headers, an accessibility-tool disclaimer, and a privacy / auto-update summary, since the NSIS license control renders RTF better than plain text and the recent NSIS high-contrast-mode fix targets it. Silent install (/S, used by the auto-updater) bypasses pages entirely so the EULA never blocks an upgrade. First-install gets the clickwrap, every subsequent auto-update flows through silently. - Streaming download progress for the auto-updater.
KeyboardBridge.updateDownloadProgress(bytes, total)is emitted from the install worker thread as the signed installer downloads. The popup that appears when the user clicks the title-bar ↓ icon now shows live MB / total MB / percentage and a determinateProgressBar; when the server omitsContent-Lengthit falls back to a labelled byte count + indeterminate bar. Emits are throttled at 256 KB to keep the queued-signal connection out of the hot path on a fast link (an 85 MB installer would otherwise fire ~1300 progress events). Closes the silent-download gap that used to read as "did the Install button do anything?". - Indeterminate progress bar on the post-install relauncher splash. The "Updating Alpha-OSK" window that bridges the gap between OSK-vanishes and OSK-relaunches now shows a moving marquee bar under the phase-aware status text. NSIS silent install suppresses its own UI, so we cannot show a real percentage during the install phase; the constant motion is the difference between "is this stuck?" and "still working" and is the same pattern every commercial installer uses. The bar settles full on the Done phase and empty on a failure phase so the eye can tell the work has stopped before the splash dwell expires. Splash widget grew 30 px in height to fit; layout, theming, and the existing ✕ dismiss behavi...
v1.0.18
[1.0.18] — 2026-05-16
Headlines: initial macOS support (the keyboard runs natively on macOS 11+ with key synthesis, AXUIElement password detection, focus-theft fixes for Qt 6), Sticky Shift no longer "keeps holding" after Shift+Tab and other special-key chords, scorecard exception list for two repo-posture findings deemed not load-bearing for a solo-dev accessibility tool, plus the OSV-Scanner CI gate, CycloneDX SBOM emission, and the audit-driven hardening pass that were sitting in Unreleased.
Changed
- OSV-Scanner CI job promoted from advisory to merge-gating.
.github/workflows/ci.ymlnow setsfail-on-vuln: true, so any CVE found inrequirements-dev.txtorbackend/cf-worker/package-lock.jsonfails CI and blocks the merge. The six known Wrangler-3.x findings (one moderateesbuild, five medium-to-highundici) were resolved by bumping the worker to Wrangler^4.0.0(4.92 at upgrade time), which ships cleanesbuild0.27.x andminiflare4.x; the worker source needed zero code changes (the upgrade is breaking for legacy classic Workers and[env]config patterns, neither of which we use — it's a textbook ESM-module / D1-binding worker). One transitive Python advisory (lxmlGHSA-vfmq-68hx-4jfw, fixed in 6.1.0) flowing throughcyclonedx-bomwas pinned away vialxml>=6.1.0inrequirements-dev.txt. SARIF upload to the Security tab remains off (upload-sarif: false) because the source repo is private and GitHub Advanced Security is not enabled; findings surface in the job's annotations / summary. Future advisories that genuinely cannot be fixed before the next push should be quarantined with anosv-scanner.tomlignore entry, not by flippingfail-on-vulnback globally. CLAUDE.md,docs/WINDOWS.md, anddocs/WHITEPAPER.mdupdated.
Added
- macOS support (Phase 1 + Phase 4). The keyboard now launches on macOS 11+, types into other apps, and respects Cocoa conventions for a non-activating system tool. Run from source with
python run.pyafter granting Accessibility in System Settings. New platform layer (src/platform/macos.py) drivesMacOSKeySynthesizervia QuartzCGEventCreateKeyboardEvent+CGEventPostToPidwith pid-targeted delivery. The pid target is the key fix for focus theft: Qt 6 dropped theQt.Tool→NSPanelmapping, so neitherNSApplicationActivationPolicyAccessorynorWindowDoesNotAcceptFocusprevents click-activation onQQuickWindow'sQNSWindow. Posting to a specific pid sidesteps frontmost state entirely. Cold-start seeding walks the parent process tree viaos.getppid()+ps -o ppid=until it lands on a pidNSRunningApplicationrecognises, so first-typed keystrokes don't vanish into the OSK itself during the gap before the user activates a real target.NSWorkspaceDidActivateApplicationNotificationobserver tracks subsequent app switches (primary signal); the bridge's 250 ms foreground-window poll also callsset_target_pidon macOS for defence-in-depth. App-level wiring: accessory activation policy removes the Dock icon and Cmd+Tab entry,_apply_macos_window_flagssetsNSFloatingWindowLevel+CanJoinAllSpaces | Transient | FullScreenAuxiliary+hidesOnDeactivate=NO,_icon_pathis now per-platform (.icnson macOS,.icoon Windows,.pngon Linux). Password-field auto-detection via_MacOSAXDetectorwalksNSWorkspace.frontmostApplication()→AXUIElementCreateApplication(pid)→kAXFocusedUIElementAttribute→AXSecureTextFieldsubrole/role, pid-keyed cache, same dual-trigger pattern as Windows/Linux (200 ms poll + per-keystroke synchronous check rate-limited to 50 ms). Routed via the frontmost app rather than the system-wide element becausekAXFocusedUIElementAttributeonAXUIElementCreateSystemWide()returnskAXErrorCannotComplete(-25204) on macOS 14/15. Verified across CocoaNSSecureTextField(System Settings, Keychain), WebKit<input type=password>(Safari), and Chromium password fields. Documented constraint: macOS Secure Event Input is enabled per-app byNSSecureTextField, so the system shell (System Settings, Keychain, FileVault, login window,sudoin Terminal) blocks third-party OSKs from typing into password fields; web<input type=password>in Safari/Chrome, many app login fields, and password-manager autofill all work or bypass the path entirely. Apple's built-in Accessibility Keyboard bypasses SEI via a privileged path not available to third-party apps. Build pipeline (build/macos/) mirrorsbuild/linux/with PyInstallerBUNDLE()producingAlpha-OSK.app(com.okstudio1.alpha-osk,LSMinimumSystemVersion 11.0,LSUIElement false,NSHighResolutionCapable true), lockfile + CycloneDX SBOM, optional--dmgviahdiutil; not yet exercised end-to-end.alpha-osk.icnsis a 10-size multi-resolution icon generated fromlogo-2048.pngviasips+iconutil(regen recipe indocs/MACOS.md). Stubbed for later phases: code signing & notarization (Phase 3), auto-update (Homebrew tap first, Sparkle later). pyobjc requirements gated viasys_platform == "darwin"so Linux/Windows resolves are unchanged.scripts/mac_keysend_diag.pyis a standalone Quartz key-send sanity check used to isolate Quartz from the Qt UI layer during focus-theft debugging. Full plan, design decisions, phase punch list, and per-category SEI breakdown indocs/MACOS.md; CLAUDE.md "Things to Watch Out For" entry covers theCGEventPostToPid+ Qt 6 /NSPanelhistory so the pattern isn't accidentally simplified away. - CycloneDX 1.6 SBOM emitted at release time + CI-time OSV-Scanner CVE check. Builds the structured supply-chain document that procurement reviews, federal contracts (Executive Order 14028), and the upcoming EU Cyber Resilience Act all expect.
build/{windows,linux}/build.py::emit_sbomrunspython -m cyclonedx_py environment --of JSON --sv 1.6 --output-reproducibleagainst the build venv on every build (always, even on--skip-build) and writesrelease/Alpha-OSK-Setup-{version}-sbom.cyclonedx.json(Windows) orrelease/Alpha-OSK-{version}-linux-sbom.cyclonedx.json(Linux) alongside the plaintext lockfile already shipped. About 100 KB, 80 components for the Python side.--output-reproduciblestrips time / random fields so two builds of the same env produce byte-identical SBOMs (diffs stay noise-free). Soft-fails (warning, no abort) ifcyclonedx-bomisn't in the venv; production builds pull it in via the newcyclonedx-bom>=7.0.0pin inrequirements-dev.txt. Worker side:npm run sbomcalls@cyclonedx/cyclonedx-npmto emitcf-worker-sbom.cyclonedx.json(~470 KB, 209 components — npm dep trees are deeper), and a newpredeployscript chains it before everywrangler deployso every worker deploy has a fresh SBOM. Worker SBOM is gitignored (regenerable from the committedpackage-lock.jsonany time). New CI jobosv-scanin.github/workflows/ci.ymlpinned togoogle/osv-scanner-action@9a498708959aeaef5ef730655706c5a1df1edbc2(v2.3.8) reads both lockfiles (requirements-dev.txt+backend/cf-worker/package-lock.json) and queries the OSV database on every push and PR (see the Changed entry above for the merge-gating posture and how the initial known noise was resolved). Release-checklist step 7 (docs/WINDOWS.md) now uploads the SBOM alongside the installer and lockfile viagh release create. Full rationale + maintenance notes indocs/WINDOWS.md§ Dependency Lockfile & SBOM and CLAUDE.md § Dependency Lockfile & SBOM. Closes gap #6 from WHITEPAPER §8.2. - Release-time Python dependency lockfile.
build/windows/build.py::freeze_lockfileandbuild/linux/build.py::freeze_lockfilerunpip freeze --allagainst the build venv on every build and write the result torelease/Alpha-OSK-Setup-{version}-requirements.lock.txt(Windows) orrelease/Alpha-OSK-{version}-linux-requirements.lock.txt(Linux), with a short header naming the version. Runs even on--skip-build(since bumping the version is what--skip-buildis for, and the lockfile name encodes the version). PyInstaller bundles whatever pip resolved at build time, so until now we literally could not answer "what version of urllib3 shipped in 1.0.16?" — the lockfile fixes that with one text file and zero new build dependencies. Not a CycloneDX/SPDX SBOM (no licenses, no purls, no signed statements); the proper SBOM upgrade path viacyclonedx-bom+osv-scanneris documented indocs/WINDOWS.md§ Dependency Lockfile for when it's actually needed (hospital procurement, federal supply-chain audit per Executive Order 14028). Release-checklist step 7 (docs/WINDOWS.md) now uploads the lockfile asset alongside the installer ingh release create. backend/cf-worker/package-lock.jsoncommitted. The cf-worker shipped without a lockfile, so local dev and CI could resolve different Wrangler / TypeScript /@cloudflare/workers-typesversions. Rannpm install --package-lock-onlyto generate it, addednode_modules/to.gitignoreso the lockfile stays the source of truth. Generating the lockfile incidentally surfaced six dev-only CVEs flowing through Wrangler 3.x (one moderate esbuild, five medium-to-high undici) — these affected only the localwrangler dev --localdev server, not the deployed worker on Cloudflare's edge runtime where those packages don't exist. Resolved in the same release by the Wrangler 4.x bump documented in the Changed entry above. Exactly what a lockfile is for: making transitive-dep CVEs visible.
Fixed
- Sticky Shift no longer "keeps holding" after pressing a special key. Reported as: clicking Shift then Tab fired the chord correctly but Shift visually stayed on and every subsequent click was also under Shift until the user tapped Shift again to toggle off.
pressSpecialKeyauto-released Ctrl, Alt, and Win at the end of its modifier-cleanup block but Shift was missing from that block, so_shift_activestayed True and `hold_mod...
v1.0.17
Rolls up everything that was sitting in Unreleased plus the auto-update relauncher fix that was prepared as 1.0.16 but never published. Headlines: opt-in telemetry pipeline scaffolded (off by default), single-section Analytics dashboard, prediction pills mirror typed capitals onto every candidate (not just strict-prefix matches), Backspace double-fire boundary widened from ~620 ms to ~800 ms, hyphen no longer eats the preceding word on prediction click, plus the auto-update relauncher fix that gives you back a keyboard after silent updates.
Fixed
- Clicking a prediction after a hyphen no longer eats the word before the hyphen. Reported as: typing "word1-word2", then clicking a suggestion for "word2", deleted both "word1" and the hyphen. Root cause: hyphen wasn't a word boundary for
_current_wordtracking, so after typing "word1-wo" the bridge thought the typed prefix was the whole 8-character"word1-wo". Clicking a suggestion for "world" failed the case-sensitive prefix match inpressPrediction("world".startswith("word1-wo") is False), fell through to thereplace_textclobber path, and BackSpaced 8 characters off the screen — wiping "word1-" along with the partial "wo". Hyphen now resets_current_wordthe same way comma / semicolon / colon do, but without the auto-trailing-space those add (the user types "word1-word2" with no space after the hyphen, unlike "Hello, world"). The character on screen is preserved (it was already sent before the boundary logic runs); only the prediction state changes. Same fix extended to/,\,(,[,{,<for the same bug class — paths, URLs, and bracketed expressions hit the same trap. Apostrophe is deliberately excluded so contractions like "don't" stay as one token. Three regression tests inTestContextTracking(hyphen reset, suffix-only path on the second word, slash spot-check). - Analytics dashboard "Top Words" bar chart now scales bars to relative frequency. Previous formula was
width = clamp(count * 3, 4, 60), which hit the 60 px cap the moment a word was used 20+ times. For top words like "and" / "to" / "the" with counts in the thousands, every bar saturated and the chart drew identical-length bars. Now scales proportionally to the #1 word's count: top word fills the full 80 px slot, the rest scale toslot_width * count / max_count. The opacity gradient (each rank ~15 % more transparent than the one above) still provides a secondary by-rank cue. - Prediction pills now mirror typed capitals onto fuzzy / autocorrect candidates, not only onto strict-prefix completions. Reported as "if I type a word with a capital letter, it's not capitalized... only sometimes." Root cause:
KeyboardBridge._display_casedmirrored typed uppercase positions onto each pill only when the pill's lowercase form strict-prefix-matched the typed letters. Spelling-correction candidates from the fuzzy recogniser routinely don't strict-match (typing "Hwl" for "Hel" surfaces "hello"; "hello".lower() doesn't start with "hwl"), so on every typo the cap was silently dropped and the pills came back lowercase. The mirror now runs unconditionally: typed uppercase positions are reflected onto every pill, prefix-match or not. "Hwl" → "Hello", "Heilo" → "Hello", "HEilo" → "HEllo". Two regression tests inTestCapsLockDisplayCasing. - Backspace double-fire boundary widened from ~620 ms to ~800 ms. Reported as "sometimes pushing backspace does two backspaces". The
KeyButton.qmlrepeat timer fired its first auto-repeat keystrokerepeatInterval(120 ms default) after the warm-up tick atrepeatDelay(500 ms default), so any press held between 500 ms and 620 ms produced exactly two keystrokes. Slow-motor users systematically tipped into that window. Added a third timer phase: after the warm-up tick, schedule the first auto-repeat atwarmUpGrace(300 ms default) instead ofrepeatInterval. Once auto-repeat is genuinely engaged the cadence stays at the configuredrepeatInterval, so bulk-delete speed is unchanged. Any press shorter thanrepeatDelay + warmUpGrace(default 800 ms) now produces exactly one keystroke. - Auto-update left the user with no keyboard. Reported as: "the new keyboard never opens" after a successful update. Root cause: the relaunch in
installer.nsh::customInstall(Exec '"$WINDIR\explorer.exe" "$INSTDIR\alpha-osk.exe"') is fire-and-forget across an integrity-level boundary. The elevated installer asks user-IL explorer.exe to spawn the new exe, and Windows can refuse that handoff silently on some configurations (anti-malware, Group Policy, AppLocker, or just the default IL rules around an elevated parent spawning user-mode children). With no error surface, the OSK simply never came back. New mechanism: a detached user-IL relauncher process spawned by the updater before the installer fires. Because the helper is launched while we're still running at user IL (before UAC elevation), it inherits a user-mode token and there is no IL handoff to fail. Flow: (1)download_and_installre-invokes ourselves with--update-relauncher+DETACHED_PROCESSflags, (2) the installer fires and taskkills the OSK, (3) the helper polls for our exit, waits for the installer's file copy, then launches the freshly-installedalpha-osk.exedirectly viasubprocess.Popenfrom the user session. The in-installerExec explorer.exetrick stays as a fallback, but the helper is now the primary mechanism. New module:src/_update_relauncher.py. New CLI dispatch insrc/keyboard_app.py::mainfor--update-relauncher(skips singleton lock + QApplication setup since the helper doesn't open a window). 14 unit tests intests/test_update_relauncher.pycovering process polling, mtime-newer-than-parent-death checks, launch failure handling, and the full happy-path flow.
Added
-
Opt-in telemetry pipeline scaffolded (off by default, no endpoint configured yet). New Settings → Privacy section with a "Share anonymous usage stats" toggle and a "Delete my contributed data" two-step button. When enabled, the client sends a weekly POST containing nine integer counters (the same lifetime numbers shown on the Analytics dashboard:
keystrokes,words,predictions,keystrokes_saved,minutes,sessions,prediction_offers, plus a UUID4anon_id,app_version, andosstring). Never sent: content, word frequencies, key frequencies, IP, hostname, or any per-session breakdown.anon_idis generated on first opt-in and cleared on opt-out so re-opt-in cycles cannot be linked. Privacy mode counters are already excluded at the analytics layer, so password-field activity never enters the totals. Backend lives inbackend/cf-worker/(Cloudflare Worker + D1) withPOST /v1/submit,GET /v1/aggregate,POST /v1/forget, plus a daily cron that prunes users inactive for >365 days. Endpoint URL (DEFAULT_ENDPOINTinsrc/telemetry.py) is currently empty, so even with the toggle on no data leaves the machine; will be set per-build once the worker is deployed and the schema is verified against real submissions. Full design indocs/TELEMETRY.md; user-facing data policy indocs/PRIVACY.md. 26 regression tests intests/test_telemetry.pycover consent gating, anon_id lifecycle, weekly cadence, payload shape + clamping, retry on 5xx/429/network error, and on-quit submission. -
Post-update confirmation toast. After an auto-update completes, the freshly-launched OSK flashes "✓ Updated to v1.0.17 from v{prior}" at the top of the window for 4 seconds. The relauncher writes
update_handoff.json(version, previous_version, completed_at) to$APPDATA/alpha-osk/after launching the new OSK. On startup,Main.qml::Component.onCompletedcallskeyboard.consumeUpdateHandoff()which reads + deletes the file (single-use breadcrumb) and returns the version pair. If non-empty, the newupdateAppliedToastPopup flashes. Five-minute freshness window. Anything older is treated as no handoff, since the user has obviously been using the new build for a while. Privacy: the breadcrumb only contains version strings, no user data. 5 bridge tests intests/test_keyboard_bridge.py::TestUpdateHandoffConsumption.
Changed
- Analytics dashboard collapsed into a single section, composite Quality Score removed. Previous layout layered four visually distinct subsections (green-bordered hero card showing "X keystrokes saved", three-pill row of words/sessions/hours, horizontal divider, Lifetime/Session toggle, then the 2x2 stat grid) which read as four disconnected views of the same data. Collapsed to one section: scope toggle + a 2x2 grid of Keystrokes Saved, Time Saved, Effort Saved, and Acceptance (= prediction picks ÷ prediction offers). The composite 0-100 Prediction Quality Score is gone — a single weighted number that didn't tell the user which lever to pull. Time Saved is now derived from the user's own keystroke pace (
keystrokes_saved × seconds_per_keystroke, fallback 0.5 s/key for new installs) rather than a constant, so the number reflects their actual experience. New persistedtop_pick_countcounter tracks rank-1 prediction picks (incremented insiderecord_prediction_selectedonly whenrank == 1); surfaced ingetAnalytics()astopPickRate/alltimeTopPickRateand consumed by the Model Visualization Dashboard's stat-card row in place of the old Quality value. Analytics section moved to the top of Settings, since lifetime savings is the most rewarding read on every open. The dashboard's rootItemnow sizes to its content (was hardcoded 460 px in the panel), so This Session view no longer leaves a tall empty band above the tile grid when the sparkline / top-words sections collapse to zero height. Subtext colour brightened from#666to#aaaand bumped from 9 px to 10 px for readability. 11 regression tests intests/test_analytics.pycover top-pick increment, time-saved math (incl. fallback pace), backwards-c...
v1.0.15
Smarter prediction learning + language-model-viz drill-down/live pulse, plus all the unreleased work since 1.0.14 (IDE-aware Compatibility Mode, selectable merge strategy, SymSpell-backed fuzzy correction, scancode chord forwarding for TeamViewer/RDP, no-auto-cap pills, configurable hold-to-repeat timing, and a pile of UX polish).
Added
- Click-to-drill-down on the Word Cloud and Word Flow tabs. Click a circle or node to open a side panel listing top successors (
word → next), top predecessors (prev → word), and trigram windows (X word Y/X Y word). Successor / predecessor entries are themselves clickable — drill from "asked" into "claude" and from "claude" into wherever next. - Live gold pulse on the active edge while typing. While the visualization is open, every keystroke pulses the matching node and edge in the cloud / flow tab (suppressed in privacy mode — never leaks password chars).
- Auto-compat now covers IDEs that intercept keystrokes, not just remote-desktop clients. VS Code + Monaco forks (Cursor, Windsurf, Codium, Code-OSS, Positron, Trae) and the JetBrains family (IDEA, PyCharm, WebStorm, PhpStorm, CLion, GoLand, Rider, RubyMine, DataGrip, DataSpell, Android Studio) now trigger Compatibility Mode automatically — fixes pill duplication when typing into the editor.
- Selectable prediction merge strategy in Settings → Suggestion Engine. Four formulae: Default (rank-based), Consensus boost (RRF), Confidence-weighted (linear interpolation), Multiplicative (log-linear). Default unchanged for existing users.
- SymSpell-backed fuzzy candidate generation. Two-edit corrections that the old path couldn't reach (e.g. "becouase" → "because") and non-adjacent substitutions ("rxample" → "example") now surface. ~40× faster lookups on the 10K-word dictionary.
- Configurable hold-to-repeat timing. Settings → Input → "Hold-to-Repeat Delay" / "Hold-to-Repeat Rate" — lets slow-motor users dial in a click cadence that won't accidentally fire a second Backspace.
- Auto-detection of remote-desktop sessions (TeamViewer, mstsc, AnyDesk, VNC, Parsec, Splashtop, RustDesk) — Compatibility Mode auto-engages when one is in the foreground.
- "Saved" toast when editing a prediction's casing — confirms the change stuck without quitting and relaunching.
Changed
- Targeted bigram reinforcement on prediction click. Clicking a pill no longer re-increments every bigram in the running buffer — only the new (prev, sel) edge gets +1.
- Backspace as negative signal. Typing a typo, pressing space, then immediately backspacing past the space now retracts that sighting from the n-gram model so typos don't accumulate toward the candidate gate. A word you've typed many times can't be unlearned in one keystroke — the decrement is per-sighting, not per-word.
- "Remote Desktop Mode" renamed to "Compatibility Mode" — covers IDEs too now. Existing users' toggle preference is preserved via an automatic settings migration.
- Prediction pills no longer auto-capitalise based on context. Only the "I" family auto-caps. Everything else mirrors the casing the user actually typed.
- Backspace auto-repeat slowed down (50 → 120 ms) for better controllability.
Fixed
- Ctrl chords (Ctrl+V/C/X/A/Z, Ctrl+click, …) now work over TeamViewer / RDP / VNC / AnyDesk. Root cause: scancode field was 0 in the synthetic SendInput events, and remote-desktop tools forward by scancode, not virtual-key.
- Backspace into a previously-completed word no longer produces "backspacbackspaces"-style duplicates.
- Backspace double-fire on slow clicks — the repeat-timer warm-up swallows the first auto-repeat-boundary keystroke.
- Modifier+punctuation chords (Ctrl+-, Ctrl+=, Ctrl+/, Ctrl+,, …) now actually reach the foreground app instead of falling through to Unicode injection.
- Auto-updater now relaunches the OSK after a successful silent update instead of leaving the user without a keyboard.
- Mid-word right-click capitals (eBay, macBook, JavaScript) are now learned when the user accepts a prediction.
Install
- First time: download
Alpha-OSK-Setup-1.0.15.exeand run. - Existing users: the in-app updater will pick this up on next startup if "Check for updates on startup" is enabled (Settings → Updates).
v1.0.14
Right-click on a key types its shifted variant, like the Windows on-screen keyboard.
Added
- Right-click → shifted variant. Right-click on any character key types the shifted glyph (
!from1,<from,,Afroma) without flipping the sticky shift state. Modifier and special keys are deliberate no-ops. Toggle in Settings → Input → "Right-Click for Shifted Character"; default ON since left-click behaviour is unaffected. - Design doc for the deferred long-press → accents feature.
docs/LONG_PRESS_ALTERNATES.mdcovers the data file format, popup architecture, and the reason it's paused (press-on-release timing change is hostile to slow-motor users until we have a way to scope the latency to keys with alternates).
Internal
KeyboardBridge.pressKeyLiteralslot — types a character verbatim, bypassing the shift / caps-lock case normalization thatpressKeyapplies. Right-click goes through this slot sincepressKeywas lowercasing the chosen'A'back to'a'.
Install
- First time: download
Alpha-OSK-Setup-1.0.14.exeand run. - Existing users: the in-app updater will pick this up on next startup if "Check for updates on startup" is enabled (Settings → Updates).
Alpha-OSK v1.0.13
Lifetime analytics, a stuck-key visual fix, the height-binding bug behind several recent layout complaints, and a Windows code-review sweep.
Highlights
- Lifetime analytics — every metric, not just totals. WPM, hit rate, savings %, backspace rate, top words, key frequencies, and the prediction quality score are now persisted across sessions. The dashboard got a Lifetime / Session toggle that drives every tile.
- Window stops resizing weirdly. v1.0.11's window-size persistence broke the height binding the moment it was restored on launch — once dead, height stayed wherever the saved value put it while content kept changing with width, causing both bottom-row clipping and empty bands above/below the keys at certain sizes. Height is no longer persisted; the binding stays alive forever and width is the only user-controlled dimension.
- Keys no longer get visually stuck after dragging off them. With
WS_EX_NOACTIVATE, Qt occasionally drops the release event when the cursor leaves onto another app's window. Press visuals now have four independent recovery paths. - Single-instance lock. Running Alpha-OSK twice surfaces the existing window instead of spawning a second one.
- UAC / Secure Desktop guidance in the help panel and
docs/WINDOWS.md— the why and the workaround if you want UAC consent prompts on the regular desktop where Alpha-OSK can reach them. - Smaller niceties: prediction pills scale with window size; PrtSc / ScrLk / Pause match the rest of the nav grid; installer welcome page is marketing-friendly;
KEYBDINPUT.dwExtraInfocorrected fromPOINTER(c_ulong)(UB) toULONG_PTR(integer).
Full notes in CHANGELOG.md.
Install
Download Alpha-OSK-Setup-1.0.13.exe below. The installer is EV code-signed; Windows SmartScreen should not flag it.
If you're already on v1.0.10 or later, the in-app updater will offer this release automatically. Earlier versions need to install this build manually once.
v1.0.12 (hotfix for 1.0.11)
Alpha-OSK 1.0.12 — Hotfix
If you installed 1.0.11, you need to install 1.0.12 by hand once because 1.0.11 doesn't launch.
Fixed
- QML duplicate-handler crash on launch. The window-size persistence work in 1.0.11 added top-level
onWidthChanged/onHeightChangedhandlers without noticing that handlers for those signals were already declared further down the file. Qt rejected the duplicates with "Property value set multiple times" and the QML file failed to load — the app started, logged the error, and exited. Merged the size-save call into the existing handlers.
The dev launcher's elevation-and-fork pattern hid the failure during smoke tests; only the frozen 1.0.11 installer hit it.
Full changelog: https://github.com/okstudio1/alpha-osk/blob/main/CHANGELOG.md
v1.0.11
Alpha-OSK 1.0.11
UX cleanup: standard taskbar minimize, persistent window size, plus the apostrophe-contractions feature that landed on main after 1.0.10.
Changed
- Minimize behaves like Chrome. Clicking the title-bar
−drops the OSK to the taskbar; click the taskbar entry to restore. Earlier builds suppressed the taskbar entry (viaQt.Tool+WS_EX_TOOLWINDOW) so minimize had tohide()the window — and the only path back was the easily-missed system-tray icon. Trade-off: the OSK now appears in Alt+Tab.WS_EX_NOACTIVATEstill prevents focus theft on click. - Window size persists across launches. Resize the OSK once, close, relaunch — comes back at the size you left it. Resize writes are debounced 300 ms so dragging the edge doesn't hammer the OS settings store.
Added
- Apostrophe-less contractions autocomplete and autocorrect. Typing
imsurfacesI'min the predictions and replaces toI'mon space; same fordont→don't,youre→you're,cant→can't,didnt→didn't, etc. Backed by 42 contractions indata/base_dictionary.txt(with realistic frequencies —i'mat 8000), 32 unambiguous bare-form entries indata/common_misspellings.txt, an apostrophe added to the fuzzy insertion alphabet, and a higher per-edit probability for apostrophe insertion specifically (0.50 vs 0.15 for generic letters) since missing apostrophes are by far the dominant insertion error in real OSK typing.
Full changelog: https://github.com/okstudio1/alpha-osk/blob/main/CHANGELOG.md
v1.0.10
Alpha-OSK 1.0.10
Auto-updater finally works end-to-end on Windows, plus a UI cleanup.
Required manual install
v1.0.5 / v1.0.6 / v1.0.7 / v1.0.8 / v1.0.9 users have to install v1.0.10 by hand once because their auto-updater couldn't elevate to admin to run the installer. Auto-update works for every release after that.
Fixed
- Auto-update install step now triggers UAC.
subprocess.Popen([installer, "/S"])was failing withWinError 740: The requested operation requires elevation— Windows refuses to launch a manifest-elevated process from a non-elevated parent without an explicitrunas. Replaced withShellExecuteW(None, "runas", installer, "/S", ...), which surfaces the UAC consent prompt; if you accept, the installer launches elevated and/Sruns it silently from there. Declining the UAC prompt now surfaces "Update cancelled at UAC prompt" instead of a generic failure.
Changed
- Update notification is a title-bar ↓ icon, not a full-width banner. The banner ate a row of OSK real estate for a passive notification — replaced with a small icon next to the play/privacy toggle that opens a popup with version info and Install / Later buttons. Icon turns red and shows the failure reason inline if a previous install attempt failed.
Full changelog: https://github.com/okstudio1/alpha-osk/blob/main/CHANGELOG.md
v1.0.9
Alpha-OSK 1.0.9
Fuzzy / autocorrect overhaul plus a Windows-terminal fix for prediction-pill replacement.
Fixed
- Prediction-pill replace works in Windows terminals — In cmd / PowerShell / Windows Terminal / mintty,
Shift+Leftmoves the cursor without selecting, so picking a prediction left the typed letters at the end of the inserted word (e.g.owen→ clickOwenproducedOwenowen). Detected via foreground window class; terminals now use a BackSpace+type fallback.
Changed
- Removed accessibility profiles. Six profiles (Precise / Normal / Mild–Severe Tremor / Limited Mobility) replaced by one tuned default. Profile UI gone. Picked Gboard-leaning constants:
spatial_uncertainty=1.4(covers diagonal neighbours),confidence_threshold=0.65(more willing to autocorrect),prediction_weight=0.6, beam-searchmin_prob=0.001.
Added
- Frequency-weighted fuzzy ranking — candidates ranked by
spatial_prob × log(freq + 1)so common words win over rare ones with the same spatial match. - Edit-distance candidates — fuzzy now catches transpositions (
teh→the), deletions (thee→the), and insertions (th→the), not just spatial substitutions. - Bigram prior on fuzzy candidates — context like
ofbooststheoverthy/thaeven when fuzzy returns them in the wrong order. - Space-time autocorrect — curated misspellings table at
data/common_misspellings.txt(~150 entries) plus fuzzy fallback. Runs on space, replaces typed letters atomically. Casing follows the typed word. Privacy mode and edit mode skip it.
Chores
- CLAUDE.md trimmed by ~33 % (release runbook + threat-model table moved to
docs/WINDOWS.mdanddocs/AUTO_UPDATE.md).
Full changelog: https://github.com/okstudio1/alpha-osk/blob/main/CHANGELOG.md