Skip to content

Releases: okstudio1/alpha-osk-releases

v1.1.0

Choose a tag to compare

@owenpkent owenpkent released this 19 May 19:42

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 to 640+. 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 .zip archive 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>.zip so 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.json is deliberately excluded from exports: copying the anon_id across machines would link contributions, which the telemetry consent docs explicitly promise not to do, so a fresh anon_id is 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 smuggle telemetry.json or 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 on file_size metadata before any bytes hit disk, and forward-compatibility rejection when the manifest's schema_version exceeds what this build supports ("upgrade Alpha-OSK first" rather than half-applied state). Atomic-rename writes through .importing tempfiles so a partial copy can't corrupt the existing file. New module src/data_export.py, new bridge slots (exportUserData, inspectUserExport, importUserData, getDefaultExportDir, getSuggestedExportName), new HybridPredictor.reload_from_disk() and TypingAnalytics.reload_from_disk(). 20 regression tests in tests/test_data_export.py cover 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_vocab permanently 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. New NgramPredictor.learn_from_pill_click(word) mirrors the gate already used by free-typing in learn(): known words (already in the Google 10K base dict or user_vocab) get the immediate +5 boost as before, but unknown words go through _candidate_counts and only promote into user_vocab after three clicks (landing with cumulative weight 3 × 5 = 15). HybridPredictor.learn_from_selection routes 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 break unprefer's rollback math). Plus: candidates now carry a _candidate_last_seen timestamp, 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 in tests/test_ngram_predictor.py::TestLearnFromPillClick and TestStaleCandidateSweep, plus tests/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 under build/windows/ and wired into the generated NSIS script as MUI_PAGE_LICENSE between WELCOME and DIRECTORY with MUI_LICENSEPAGE_CHECKBOX enabled, so the Next button stays disabled until the user ticks "I have read and accept the terms of the license agreement." Customised MUI_LICENSEPAGE_TEXT_TOP clarifies the action ("You must accept them to install Alpha-OSK"). The repo-root LICENSE (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 determinate ProgressBar; when the server omits Content-Length it 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...
Read more

v1.0.18

Choose a tag to compare

@owenpkent owenpkent released this 16 May 21:20

[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.yml now sets fail-on-vuln: true, so any CVE found in requirements-dev.txt or backend/cf-worker/package-lock.json fails CI and blocks the merge. The six known Wrangler-3.x findings (one moderate esbuild, five medium-to-high undici) were resolved by bumping the worker to Wrangler ^4.0.0 (4.92 at upgrade time), which ships clean esbuild 0.27.x and miniflare 4.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 (lxml GHSA-vfmq-68hx-4jfw, fixed in 6.1.0) flowing through cyclonedx-bom was pinned away via lxml>=6.1.0 in requirements-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 an osv-scanner.toml ignore entry, not by flipping fail-on-vuln back globally. CLAUDE.md, docs/WINDOWS.md, and docs/WHITEPAPER.md updated.

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.py after granting Accessibility in System Settings. New platform layer (src/platform/macos.py) drives MacOSKeySynthesizer via Quartz CGEventCreateKeyboardEvent + CGEventPostToPid with pid-targeted delivery. The pid target is the key fix for focus theft: Qt 6 dropped the Qt.ToolNSPanel mapping, so neither NSApplicationActivationPolicyAccessory nor WindowDoesNotAcceptFocus prevents click-activation on QQuickWindow's QNSWindow. Posting to a specific pid sidesteps frontmost state entirely. Cold-start seeding walks the parent process tree via os.getppid() + ps -o ppid= until it lands on a pid NSRunningApplication recognises, so first-typed keystrokes don't vanish into the OSK itself during the gap before the user activates a real target. NSWorkspaceDidActivateApplicationNotification observer tracks subsequent app switches (primary signal); the bridge's 250 ms foreground-window poll also calls set_target_pid on macOS for defence-in-depth. App-level wiring: accessory activation policy removes the Dock icon and Cmd+Tab entry, _apply_macos_window_flags sets NSFloatingWindowLevel + CanJoinAllSpaces | Transient | FullScreenAuxiliary + hidesOnDeactivate=NO, _icon_path is now per-platform (.icns on macOS, .ico on Windows, .png on Linux). Password-field auto-detection via _MacOSAXDetector walks NSWorkspace.frontmostApplication()AXUIElementCreateApplication(pid)kAXFocusedUIElementAttributeAXSecureTextField subrole/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 because kAXFocusedUIElementAttribute on AXUIElementCreateSystemWide() returns kAXErrorCannotComplete (-25204) on macOS 14/15. Verified across Cocoa NSSecureTextField (System Settings, Keychain), WebKit <input type=password> (Safari), and Chromium password fields. Documented constraint: macOS Secure Event Input is enabled per-app by NSSecureTextField, so the system shell (System Settings, Keychain, FileVault, login window, sudo in 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/) mirrors build/linux/ with PyInstaller BUNDLE() producing Alpha-OSK.app (com.okstudio1.alpha-osk, LSMinimumSystemVersion 11.0, LSUIElement false, NSHighResolutionCapable true), lockfile + CycloneDX SBOM, optional --dmg via hdiutil; not yet exercised end-to-end. alpha-osk.icns is a 10-size multi-resolution icon generated from logo-2048.png via sips + iconutil (regen recipe in docs/MACOS.md). Stubbed for later phases: code signing & notarization (Phase 3), auto-update (Homebrew tap first, Sparkle later). pyobjc requirements gated via sys_platform == "darwin" so Linux/Windows resolves are unchanged. scripts/mac_keysend_diag.py is 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 in docs/MACOS.md; CLAUDE.md "Things to Watch Out For" entry covers the CGEventPostToPid + Qt 6 / NSPanel history 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_sbom runs python -m cyclonedx_py environment --of JSON --sv 1.6 --output-reproducible against the build venv on every build (always, even on --skip-build) and writes release/Alpha-OSK-Setup-{version}-sbom.cyclonedx.json (Windows) or release/Alpha-OSK-{version}-linux-sbom.cyclonedx.json (Linux) alongside the plaintext lockfile already shipped. About 100 KB, 80 components for the Python side. --output-reproducible strips time / random fields so two builds of the same env produce byte-identical SBOMs (diffs stay noise-free). Soft-fails (warning, no abort) if cyclonedx-bom isn't in the venv; production builds pull it in via the new cyclonedx-bom>=7.0.0 pin in requirements-dev.txt. Worker side: npm run sbom calls @cyclonedx/cyclonedx-npm to emit cf-worker-sbom.cyclonedx.json (~470 KB, 209 components — npm dep trees are deeper), and a new predeploy script chains it before every wrangler deploy so every worker deploy has a fresh SBOM. Worker SBOM is gitignored (regenerable from the committed package-lock.json any time). New CI job osv-scan in .github/workflows/ci.yml pinned to google/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 via gh release create. Full rationale + maintenance notes in docs/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_lockfile and build/linux/build.py::freeze_lockfile run pip freeze --all against the build venv on every build and write the result to release/Alpha-OSK-Setup-{version}-requirements.lock.txt (Windows) or release/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-build is 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 via cyclonedx-bom + osv-scanner is documented in docs/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 in gh release create.
  • backend/cf-worker/package-lock.json committed. The cf-worker shipped without a lockfile, so local dev and CI could resolve different Wrangler / TypeScript / @cloudflare/workers-types versions. Ran npm install --package-lock-only to generate it, added node_modules/ to .gitignore so 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 local wrangler dev --local dev 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. pressSpecialKey auto-released Ctrl, Alt, and Win at the end of its modifier-cleanup block but Shift was missing from that block, so _shift_active stayed True and `hold_mod...
Read more

v1.0.17

Choose a tag to compare

@owenpkent owenpkent released this 09 May 00:46

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_word tracking, 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 in pressPrediction ("world".startswith("word1-wo") is False), fell through to the replace_text clobber path, and BackSpaced 8 characters off the screen — wiping "word1-" along with the partial "wo". Hyphen now resets _current_word the 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 in TestContextTracking (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 to slot_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_cased mirrored 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 in TestCapsLockDisplayCasing.
  • Backspace double-fire boundary widened from ~620 ms to ~800 ms. Reported as "sometimes pushing backspace does two backspaces". The KeyButton.qml repeat timer fired its first auto-repeat keystroke repeatInterval (120 ms default) after the warm-up tick at repeatDelay (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 at warmUpGrace (300 ms default) instead of repeatInterval. Once auto-repeat is genuinely engaged the cadence stays at the configured repeatInterval, so bulk-delete speed is unchanged. Any press shorter than repeatDelay + 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_install re-invokes ourselves with --update-relauncher + DETACHED_PROCESS flags, (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-installed alpha-osk.exe directly via subprocess.Popen from the user session. The in-installer Exec explorer.exe trick stays as a fallback, but the helper is now the primary mechanism. New module: src/_update_relauncher.py. New CLI dispatch in src/keyboard_app.py::main for --update-relauncher (skips singleton lock + QApplication setup since the helper doesn't open a window). 14 unit tests in tests/test_update_relauncher.py covering 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 UUID4 anon_id, app_version, and os string). Never sent: content, word frequencies, key frequencies, IP, hostname, or any per-session breakdown. anon_id is 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 in backend/cf-worker/ (Cloudflare Worker + D1) with POST /v1/submit, GET /v1/aggregate, POST /v1/forget, plus a daily cron that prunes users inactive for >365 days. Endpoint URL (DEFAULT_ENDPOINT in src/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 in docs/TELEMETRY.md; user-facing data policy in docs/PRIVACY.md. 26 regression tests in tests/test_telemetry.py cover 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.onCompleted calls keyboard.consumeUpdateHandoff() which reads + deletes the file (single-use breadcrumb) and returns the version pair. If non-empty, the new updateAppliedToast Popup 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 in tests/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 persisted top_pick_count counter tracks rank-1 prediction picks (incremented inside record_prediction_selected only when rank == 1); surfaced in getAnalytics() as topPickRate / alltimeTopPickRate and 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 root Item now 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 #666 to #aaa and bumped from 9 px to 10 px for readability. 11 regression tests in tests/test_analytics.py cover top-pick increment, time-saved math (incl. fallback pace), backwards-c...
Read more

v1.0.15

Choose a tag to compare

@owenpkent owenpkent released this 07 May 00:42

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.exe and 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

Choose a tag to compare

@owenpkent owenpkent released this 29 Apr 00:35

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 (! from 1, < from ,, A from a) 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.md covers 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.pressKeyLiteral slot — types a character verbatim, bypassing the shift / caps-lock case normalization that pressKey applies. Right-click goes through this slot since pressKey was lowercasing the chosen 'A' back to 'a'.

Install

  • First time: download Alpha-OSK-Setup-1.0.14.exe and 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

Choose a tag to compare

@owenpkent owenpkent released this 26 Apr 19:41

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.dwExtraInfo corrected from POINTER(c_ulong) (UB) to ULONG_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)

Choose a tag to compare

@owenpkent owenpkent released this 25 Apr 23:41

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 / onHeightChanged handlers 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

Choose a tag to compare

@owenpkent owenpkent released this 25 Apr 23:27

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 (via Qt.Tool + WS_EX_TOOLWINDOW) so minimize had to hide() 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_NOACTIVATE still 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 im surfaces I'm in the predictions and replaces to I'm on space; same for dontdon't, youreyou're, cantcan't, didntdidn't, etc. Backed by 42 contractions in data/base_dictionary.txt (with realistic frequencies — i'm at 8000), 32 unambiguous bare-form entries in data/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

Choose a tag to compare

@owenpkent owenpkent released this 25 Apr 21:38

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 with WinError 740: The requested operation requires elevation — Windows refuses to launch a manifest-elevated process from a non-elevated parent without an explicit runas. Replaced with ShellExecuteW(None, "runas", installer, "/S", ...), which surfaces the UAC consent prompt; if you accept, the installer launches elevated and /S runs 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

Choose a tag to compare

@owenpkent owenpkent released this 25 Apr 21:21

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+Left moves the cursor without selecting, so picking a prediction left the typed letters at the end of the inserted word (e.g. owen → click Owen produced Owenowen). 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-search min_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 (tehthe), deletions (theethe), and insertions (ththe), not just spatial substitutions.
  • Bigram prior on fuzzy candidates — context like of boosts the over thy / tha even 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.md and docs/AUTO_UPDATE.md).

Full changelog: https://github.com/okstudio1/alpha-osk/blob/main/CHANGELOG.md