Releases: owenpkent/alpha-osk-releases
Release list
v1.4.1
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.)
A small follow-up to 1.4.0, entirely repairs and wording on the setup wizard. Nothing in the keyboard itself changed.
Changed
The usage-statistics page now shows you the message it would send, says what it is for, and arrives with the box ticked. It listed the ten numbers in prose and left the box empty. Two things were missing from that. It never said what the numbers are for, so it read as collection for its own sake; they are the only measure of whether the predictions really save clicks outside a benchmark, which settings people can actually use, and whether a release helped or hurt. And a description of a payload is something you have to take on trust, so the page now prints the payload itself, field by field with example values, in a fixed-pitch block. The field names are the real ones and the version is the version you are installing, so it cannot quietly drift away from what the app sends.
The box being ticked is a deliberate reversal, and the case against it is real: for a user in the EU, a pre-ticked box is not valid consent, and that rule reaches this app even though it is a desktop program with no browser and no cookies in it. It was ticked anyway, on the grounds that the page states the purpose, shows the exact payload, and declines in one click, and that this is still a more conservative position than the editors and developer tools that switch telemetry on with no box at all. Unticking still declines, the switch under Settings → Data & Privacy still governs it afterwards, and deleting everything you have shared is still one button. A silent install, which is what the auto-updater runs, still never shows the page and still never changes what you already chose.
Fixed
-
The shortcut checkboxes no longer forget what you chose. Unticking "Create Desktop shortcut", clicking Back and then Next put the tick back, and the shortcut was created anyway. The page filled both boxes in from the default every time it was shown rather than from your answer, and the wizard rebuilds that page on each visit, so the default won.
-
The "Alpha-OSK is currently running" prompt no longer hides behind other windows. Installing over a running copy asks permission to close it first, and that question was being raised before the setup wizard had appeared on screen: it belonged to no window, so Windows was free to leave it behind whatever you were last looking at, with nothing on screen to say the install was waiting on an answer. It is now asked when you click Install, over the wizard, and it stays on top.
It is also asked later than it used to be. Before, the keyboard was closed before you had picked an install location or agreed to anything, so cancelling further on left you with no on-screen keyboard and nothing installed. Nothing is closed now until you have committed to the install, and answering Cancel stops without touching either the running keyboard or the files on disk.
-
Updating no longer throws away words learned since the last save. Installing a new version closed the running keyboard by killing it outright, which gave it no chance to write anything down: every automatic update lost whatever the model had picked up since it last saved, and could leave Shift or Ctrl stuck held down across the whole desktop afterwards, because the keyboard releases those on its way out too. It is now asked to close first, the way clicking the X does, and only forced if it has not gone within a few seconds. In practice it closes in a fraction of a second, so updates are slightly quicker than before as well.
One caveat on the way in, the same shape as the settings fix in 1.4.0: the update that brings you this still runs the closing sequence already on your machine, which is the old one, so words learned since the last save are lost one final time as you take it. Every update after this one closes the keyboard properly.
Full changelog: https://github.com/owenpkent/alpha-osk/blob/main/CHANGELOG.md
The requirements.lock.txt and sbom.cyclonedx.json assets are the exact dependency set this installer was built from.
v1.4.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.)
Added
A research study you can take part in, run from the keyboard itself. Settings → Data & Privacy → Research Study walks you through the consent form, a few background questions, then blocks of copy typing with predictions on and with predictions off, a rating after each block, and finally your own numbers. Nothing leaves the machine until you have read those numbers and chosen to send them, and stopping part way through leaves nothing behind.
Why it exists: every keystroke-savings figure this project has published comes from a benchmark that types a fixed corpus in simulation. That is an upper bound on what prediction can save, not a measurement of what it does save for a person. The protocol and consent form live in docs/research/ and were published before anyone enrolled, deliberately, since there is no institutional review board behind this and that version history is the only thing standing in for a preregistration.
The anonymous usage counters now have somewhere to go, and the installer asks whether you want to share them. The opt-in telemetry toggle has existed for several releases with no endpoint behind it, which meant it could be switched on and nothing was ever sent. The receiving end is now deployed, so the setting does what it says. The setup wizard explains the ten numbers (how many keystrokes, how many words, how many suggestions were offered and accepted, and so on: totals only, never anything you typed, no IP address and no hostname).
The checkbox is unchecked and stays that way unless you tick it, because a pre-ticked consent box is not consent. Turning it off deletes the random identifier, so nothing you sent before can be linked to anything you send after, and the first submission lands about a week after you opt in rather than immediately.
Fixed
- Your settings survive an update. Installing a new version reset the theme, layout, panels, opacity and window size to defaults every time, because the previous version's uninstaller deleted the registry key every setting lives in. One caveat on the way in: the update that brings you this fix still runs the old uninstaller already on your machine, so your settings reset one last time as you take it. Every update after that keeps them.
- Two letters no longer leave the suggestion bar empty. If either of the first two letters of a word landed on the wrong key, nothing at all was offered until the third letter arrived. 298 of the 676 possible two-letter combinations were in that state. A pair of letters that really does begin a word is answered exactly as before, so nothing that already worked has changed.
- Compact View shows the function keys above the digits, not between them.
- Settings, Help and the Dashboard open somewhere you can reach, on the keyboard's own screen rather than always the primary monitor, and clear of the keyboard.
- The Key Colours schemes colour the suggestion pills' outline, not their middle.
- The installer's pages carry their own headings, rather than inheriting "Choose Install Location" from the page before.
Full changelog: https://github.com/owenpkent/alpha-osk/blob/main/CHANGELOG.md
The requirements.lock.txt and sbom.cyclonedx.json assets are the exact dependency set this installer was built from.
v1.3.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.)
The largest release so far. Voice input, a symbol and emoji picker, colour-coded keys, twelve more function keys that can be programmed, and a prediction engine that finishes a word through a mis-clicked key. Swipe typing is removed.
Added
- Dictation. A microphone at the left end of the suggestion bar: click it, speak, click again. The live transcript renders in the bar itself, and each phrase is typed into whatever app has focus as soon as the recogniser finalises it. Off by default and inert without a key; transcription is Deepgram, using your own. It stops itself when a password field is focused, cancelling the run outright rather than letting it finish. It costs no new dependencies: capture and transport are already inside the Qt that ships with the app.
- Symbols & Emoji. A picker holding 372 glyphs across twelve categories, from curly quotes and arrows to seven pages of emoji. Tapping one types it. Glyphs you have used recently come back on their own page.
- Key Colours. Settings -> Appearance -> Key Colours colours a key by what it does rather than by where it sits. Six schemes, with Monochrome the new default. Every colour is derived from the theme above it, so each scheme looks different on each theme, and every fill is held to 4.5:1 against its legend on all nine themes.
- Twelve more function keys, F13 to F24, and any function key can be programmed. Almost nothing binds F13 to F24, so one can be handed to a game, to OBS or to a macro tool without colliding with that program's own F5. Any function key can instead fire a shortcut, type a stored phrase, or keep its keystroke and only change what the keycap says. A new Function Keys page in Settings lists all twenty-four, because right-clicking a key cannot be the only way in for a pointer with no right button.
- Suggestions complete a word through a mis-click. Mid-word, the fuzzy source could not finish a word once a click had slipped (measured at 0% recovery). It is now a beam over the dictionary's live prefixes that allows a slipped, missed, doubled or swapped click, so
hwllostill offershello. Keystroke savings with one uncorrected mis-click per word went from 34.6% to 47.9%. - The keyboard arrives already knowing which words follow which, seeded from a published forum language model rather than a short hand-written list. Your own half is unchanged: what you type is still learned separately and still outweighs the seeds.
- Where inside the key you clicked now counts, and the keyboard learns your pointer's bias from your own presses.
- Somewhere to find the log. Settings -> Data & Privacy -> Diagnostics shows where the diagnostic log lives, with Open Log Folder and Copy Path beside it.
Changed
- The keyboard's edges are straight. Every row on the full-size layouts now totals exactly the same width. They did not before, so the left and right edges stepped in and out five times and the whole thing read as lumpy.
- The keyboard grid, the navigation cluster and the numpad share one height, instead of being three heights centred against one another with the arrow cluster floating clear of the bottom rail.
- The space bar is over half as wide again, 6.0 key units to 9.5, growing around its own centre so every click that used to land on it still does. The room came from a symbol page whose glyphs are all in the new picker anyway.
- The function rows fill the width of the keyboard, making every function key about a quarter wider to aim at.
Fixed
- A click that lands between two keys types the key you were aiming at. The gap between keycaps was dead space, and a click there did nothing at all, with nothing on screen to say why. Every key now owns half the gap around it.
- The white notch in a corner of the keyboard on Windows is gone. It came and went with whatever was behind the window, which is why it read as intermittent.
- Acronyms like PR, QML or SDK can be learned at last. The filter that keeps keyboard slips out of your vocabulary could not tell one from an acronym. A word typed with capitals on two or more letters is now taken as deliberate.
- Your own phrases surface after a couple of typings instead of never. The word-pair tables were re-seeded on top of themselves at every launch, so a phrase typed after a common word could not compete.
- The Dictation settings page no longer runs off the right edge, and the arrow keys are separated from the block above them by a gutter, the way a physical keyboard has one.
Security
Six fixes, including: the auto-updater can no longer be handed a different installer than the one it verified; the diagnostic log no longer contains anything you typed; the Deepgram API key can no longer reach a log or a crash report; and a crafted vocabulary pack can no longer make the suggestion bar fetch a URL.
Removed
- Swipe typing is gone (#39). A sustained precise drag is the one gesture this keyboard's users can least rely on, and its overlay took every press inside its bounds, which had made unrelated keys dead taps three separate times.
Full detail in CHANGELOG.md.
Install: download Alpha-OSK-Setup-1.3.0.exe. EV code-signed by OK Studio Inc. The lockfile and CycloneDX SBOM for this build are attached.
v1.2.2
Follow-up to 1.2.1. The Hold a Letter to Repeat It setting that 1.2.1 added now actually works everywhere it claimed to, and the taskbar-button half of the 1.2.1 window fix is properly flushed.
Fixed
-
Holding a letter repeats it with Swipe Typing on. The setting was inert there, and silently: the swipe overlay takes every press, and because a press on a letter might be the start of a swipe it deferred the keystroke to release, which is the one path that never arms a key's repeat timer. Holding a letter for five seconds gave one letter and no clue why. A stationary character press is now watched, and becomes a hold if the pointer has not moved by the time the key's arm delay elapses. Any movement that leaves the key or promotes the gesture to a swipe cancels the wait, so swiping is untouched and a release before the delay is still an ordinary tap producing exactly one character.
-
Holding a digit on the Numpad repeats it. The panel had no repeat wiring at all, so "hold a letter or a digit" was half true: a digit on the Number Row repeated while the same digit on the Numpad did not, and the Numpad's arrows and PgUp/PgDn were dead while the identical keys in the Navigation panel repeated. Home and End deliberately still do not repeat, matching the Navigation panel, because the caret cannot move past the start or end of a line.
-
Repeat really does wait 800 ms now, at every repeat-delay setting. The guarantee was arithmetic rather than a floor, and the repeat delay is a user setting that goes down to 300 ms, so anyone who had shortened it to make Backspace responsive was getting a second letter after 600 ms and then one every 60 ms. That is the exact failure the safeguard exists to prevent. Letters and digits now carry a hard floor. Backspace, Delete and the arrows deliberately do not, since shortening the delay is how you make Backspace snappier.
-
The taskbar button's style word is flushed.
WS_MINIMIZEBOX/WS_SYSMENUwere written after the singleSetWindowPos(SWP_FRAMECHANGED)call rather than before it. Both are frame styles, and a frame-style change is not picked up until a followingSWP_FRAMECHANGED.
Internal
Window-flag documentation across five files was describing the opposite of what the code does after 1.2.1, including a troubleshooting entry that listed the intended Alt+Tab entry as a defect to fix by re-adding the style 1.2.1 deliberately removed. Four tests that could not fail were replaced with ones that can.
Full detail in CHANGELOG.md.
Install: download Alpha-OSK-Setup-1.2.2.exe. EV code-signed by OK Studio Inc. The lockfile and CycloneDX SBOM for this build are attached.
v1.2.1
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.2.1] (2026-08-16)
Headlines: the keyboard is genuinely always-on-top again and has a working taskbar button (both were broken in 1.2.0, and both turned out to be the window's styles not being what the code believed); the Snippets editor behaves like an ordinary text box, so double-click selects a word, Tab moves between the fields and Ctrl+C/V/X/A/Z work; holding a letter repeats it; and moving the caret with Home, End or the arrows now clears the suggestion context, which matters because that context is what a tapped suggestion measures against when it decides how much text to replace.
Added
- Holding a letter or a digit now repeats it, the way a physical keyboard does. Requested, after holding a key and getting exactly one character out of it. Backspace and the arrows have always repeated; letters deliberately did not, because a key here is held by not letting go of a mouse button, and a slow release is ordinary on this keyboard rather than a mistake, so a repeating letter can turn one intended character into several. That argument is about the people this keyboard is built for, which is also the reason the person it describes gets to overrule it. Repeat starts only after about 800 ms of deliberate holding, and Settings -> Smart Typing -> Input -> Hold a Letter to Repeat It turns it back off. Esc and Tab never repeat whatever the setting says: a repeating Esc on a slow release closes a dialog and then whatever was behind it.
Changed
- The Snippets button moved out of the title bar and down next to the clear-context ring, where it is a 45 px circle instead of a 28 px patch of title bar, and it is now Feather's bookmark drawn from path data rather than the "☰" character. That character was typeset as a font glyph, which on Windows resolves through Segoe UI Emoji: it renders in colour and ignores the colour it is given, the same trap that took the padlock off the modifier keys and the ⟲ out of the title bar. The clear-context ring stays exactly where it was and the new button sits to its left, because the ring is pressed from muscle memory and the new control is the one that should have to be found. Two consequences worth knowing: the suggestion row now keeps a wider strip clear on its right, so on a narrow window you may see one fewer suggestion (it drops the lowest-ranked pill rather than squeezing the text, and widening the window gives it back); and because the suggestion bar collapses to nothing when suggestions are switched off, the title-bar button is kept as a fallback that appears only in that state, so turning off suggestions can never take Snippets away with it.
Fixed
- The keyboard was not really always-on-top, and had no taskbar button. Reported. Two separate defects in one place, and both were the window's styles not being what the code believed they were. Always-on-top was written into the window's style word, which records the intention without moving the window into the band Windows keeps its topmost windows in: measured on the running keyboard, it sat fifteenth in the stack, below a dozen ordinary windows including the editor it was supposed to float over, while reporting itself as topmost throughout. Separately, Qt was quietly marking the window as a tool window, the flag that removes a window from the taskbar, so the minimise button had nowhere to put it and clicking the pinned icon did nothing at all. Both are now set the way the operating system documents rather than assumed, and the build asserts them.
- In the Snippets editor, double-clicking a word did not select it, clicking did not move the caret, and dragging did not select. Reported. Each field had a transparent layer over it whose whole job is to catch clicks, put there to track which of the two boxes the keyboard types into, and it was catching them before the text underneath ever saw them. Everything else was already in place: mouse selection was switched on the whole time, and asking the field to select a word worked when it was asked directly. The layer now notes which box you tapped and passes the click straight through, so caret placement, double-click-for-a-word, triple-click-for-the-line and drag-select all behave the way they do in any other text box.
- Tab did nothing in the Snippets editor. It now moves between the Label and Text boxes and selects what is in the one it lands on, so replacing a value outright is one gesture rather than holding Backspace across an address. This mattered more than it sounds: the Snippets window cannot hold the system's keyboard focus, by design, so with Tab dead the only way to change box was landing a click on it.
- Shift with an arrow key moved the caret instead of selecting. Shift plus Left, Right, Home or End now extends the selection in the Snippets editor, as it does everywhere else.
- Moving the caret with Home, End, the arrows or the page keys now clears the suggestion context. Requested. Those keys put the caret somewhere the keyboard did not watch it go, so the record it keeps of the word in progress and the text before the cursor stops matching what is actually in front of the caret. Only half of that record was being cleared. It matters more than a wrong suggestion: that record is what a tapped suggestion measures against when it decides how much to type and how much to replace, so acting on a stale one could overwrite text elsewhere in the line. The suggestions are cleared at the same moment, so there is nothing left on screen to tap. Delete is deliberately unaffected, since it removes the character after the caret and leaves everything before it exactly as it was.
- The keyboard came back on the wrong monitor. If you moved it to a second screen and quit, it reappeared on the primary one at every launch, and a monitor positioned to the left of the primary was worse: those coordinates are negative, and they were being clamped to zero, so the keyboard landed in the top-left corner of the wrong screen. The saved position is now clamped against every monitor you actually have rather than the primary one's dimensions. The snippets window was fixed this way in the previous release; this is the same bug in the window it was copied from.
- Ctrl+C, Ctrl+V, Ctrl+X, Ctrl+A and Ctrl+Z now work while editing a snippet or a suggestion, and Ctrl with any other letter no longer types that letter. Holding Ctrl and tapping A used to insert "a", because the modifier was dropped and the letter typed anyway. Paste is the one that matters: every character of a long address is a click on this keyboard, so being able to copy an address from somewhere else and paste it into a snippet is the difference between a snippet being worth making and not. These act on the box you are editing and are never passed through to the app behind the keyboard.
- Every function key was a dead tap while Swipe Typing was on. The swipe overlay covers the whole keyboard area and passes taps through by looking the key up in a registry, so a key that never registers receives nothing at all. This is issue #15 again, which was fixed for the main grid and the Number Row; the Function row was missed, and it is off by default, which is why it went unnoticed. The keys register as specials, so they stay out of the shape-matching map where an "F7" centre would be a phantom letter in every swipe.
v1.2.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.2.0] (2026-08-16)
Headlines: the keyboard now predicts the strings the word model structurally could not hold (numbers, phone numbers, house numbers and email domains, and it offers to save an email, phone number or address it sees you type); the Snippets window is rebuilt as a tile grid where a tap copies to the clipboard and everything else lives behind right-click or the Manage toggle, with a colour tag per snippet; Compact View, a denser 13x4 keyboard for small screens, with its own Number Row panel and a second symbol page; intelligent spacing, so the auto-space after punctuation knows not to fire inside 3.14 or owen@gmail.com; right-click a modifier to lock it held down; and a security pass whose three worst findings were typed text reaching the plaintext diagnostic log, accepting a suggestion pill bypassing privacy mode entirely, and a malicious backup archive being able to plant a command that ran on one tap.
Added
-
The Snippets window is rebuilt: tap to copy, right-click for everything else, and a colour tag per snippet. Requested. Snippets were a vertical list of rows, each carrying a bright blue pencil and a bright red X four pixels from the button you press every day, and the red one deleted your snippet outright with no confirmation and no undo. That was the worst arrangement available for a window operated with an imprecise pointer, and none of those colours came from your theme, so on Typewriter (a light theme) and Blackboard (a pale accent) the three buttons read as foreign objects stapled to the window. The list is now a grid of tiles, two across, six to a page, with nothing on it but snippets. Tapping one copies it to the clipboard and a toast on the keyboard says which one it took. Right-clicking one turns the window into an actions sheet for that snippet: copy, edit, colour, move earlier, move later, delete, each a full-width row you can read rather than an icon you have to decode. If you cannot right-click, the header's Manage button is the way in: tap it and a plain tap on any tile opens that snippet's sheet instead of copying it, tap Done to go back. Press-and-hold is deliberately not a third way, because a click held a beat too long is ordinary on a keyboard built for slow motor input and must never turn copying a snippet into opening a menu. It is a mode rather than a small button on each tile for the same reason the tiles have nothing else on them: a second target on a 165 px tile sits a few pixels from the one you press every day, which is the arrangement this window was rebuilt to get rid of. In manage mode the whole tile is the target and copying is switched off, so the worst a mis-tap can do is open a sheet you can back out of.
Copying replaced typing, and that is the change to know about. Tapping a snippet used to synthesise the text into whatever app had focus, which is one click against a paste's two and sounds strictly better. It is not, because it only lands correctly when the caret is already in the right field and the app takes synthetic keystrokes cleanly (the entire reason Compatibility Mode exists), a long address arrives one character at a time, and when it goes wrong it goes wrong silently, into whichever window happened to be focused. The clipboard has no focus race. Because a clipboard write is invisible by nature, the toast is not garnish: it is the only evidence anything happened, it names the snippet since colour-tagged near-duplicates are exactly what this window now encourages, and it lives on the keyboard rather than in the snippets window because that window closes itself on the same tap. Copying an empty snippet does nothing at all rather than wiping what you had already put on the clipboard, which is the one way this feature could destroy something.
Colour tags are the reason the grid works. Six tiles at 165 px each are quicker to scan than six full-width rows only if you can tell them apart, and two email addresses look identical at a glance. Red, amber, green, blue, purple, plus grey for untagged, which is the default and is why there is no grey in the list: a tag that reads as the default is a tag you cannot see. The tag inks a bar down the side of the tile and tints the whole tile, because at that size a stripe alone is easy to miss. Tags are stored as a name from a fixed list and never as the colour itself, since
snippets.jsonis replaced wholesale by a Data Backup import and the stored value ends up driving a colour in the interface; an unrecognised tag falls back to grey rather than being drawn, and the rule is re-applied every time the file loads.Everything else in the window was brought along. Deleting always asks first, in place, with Keep as the wider and nearer target. Reordering finally has a way in (it worked in the backend and appeared in no menu), and moving follows the snippet rather than the slot, bringing the grid to the page it landed on. Paging replaced growing downward: this window floats over whatever you are typing into, so a list that kept extending would eventually cover it and at the 50-snippet cap would run off the screen entirely; a short last page keeps its full height so the pager and the Add button never slide up under a pointer already moving toward them. Add goes inert at the cap rather than appearing to work, because the store refuses past 50 and the old button could not tell that from success: it opened the editor on "the last snippet", which at the cap is an existing one you never asked to edit. The window stays where you drag it across restarts, instead of recentring on every launch and undoing the one adjustment anybody makes to it. Every colour now comes from the active theme, with the destructive red picked per theme since a dark red is illegible on Typewriter's cream and a bright one glares on Spaceship's near-black. And no icon in this window is a typeset character any more: the pencil and the cross were font glyphs, which Windows resolves through Segoe UI Emoji, rendering them in colour and ignoring the colour they were given, the same trap the modifier-lock padlock fell into. They are drawn from paths now. Covered by a new
tests/test_qml_snippets.py, which drives the real QML headlessly, plus the tag rules intests/test_snippets.py. -
The keyboard now suggests numbers, phone numbers and email addresses. Requested. Everything Alpha-OSK had ever learned was a word, because the prediction engine splits your typing on
[a-zA-Z']+and throws away every digit and symbol before it stores anything. Your phone number, your zip, your house number and your email address were therefore in a blind spot the engine could not even represent, and they are exactly the strings you retype most and the most expensive ones to type with a pointer: ten digits is ten clicks, and on Compact View the digits sit behind a layer hop. Worse, typing a digit used to blank the suggestion bar outright, so the moment you started a number the keyboard stopped helping at all. Those strings are now kept whole in a separate store, matched by prefix, and offered back the same way words are: type "12" and a house number you have used before completes it, type the first digits of your phone number and the rest is one tap. Accepting one appends a space when the token sits mid-sentence (a house number is followed by a street), and deliberately does not after an email or a phone number, because those are field values and a login form that does not trim its input rejects a trailing space with a validation error you then have to notice and undo.The store is deliberately dumber than the word model in two ways. It has no notion of context, because "this is the number I always type" is already carried by how often you typed it. And it does no fuzzy correction, because correcting a mistyped letter is a favour while "correcting" a digit silently changes a number, and a phone number that is one digit out is worse than no suggestion at all.
What it will not remember matters more than what it will, since anything stored here is offered back in the suggestion bar and travels in a Data Backup export. Any run of more than eight digits that is not shaped like a phone number is refused outright, which puts card numbers, account numbers and a US Social Security number (in every one of its written forms) out of reach by construction. That bar is blunt on purpose: enumerating which kinds of long number are sensitive is a game you eventually lose, so the whole category is declined and the occasional legitimate miss accepted. Phone numbers themselves are admitted by the same digit-grouping rule the snippet detector already uses, so
123-45-6789is not mistaken for one. The rule is re-applied every time your model loads, so tightening it later cleans out an existing store rather than grandfathering things in. Nothing learned here reaches the log, and accepting one counts toward keystrokes-saved but is kept out of the dashboard's "Top Words" and out ofanalytics.json. Your phone number should not turn up on a screen you might share. Off entirely while learning is paused, and cleared by Clear Learned Data along with your words.docs/PRIVACY.mdhas a section on it. -
Typing
@offers common email domains.gmail.com,outlook.com,yahoo.comand the rest appear as soon...
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).