Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/neural-prediction.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Word prediction shows the statistical model's first three words immediately, then asks SmolLM2-135M Q8 for up to three different words for the remaining slots; see [six prediction slots](six-predictions.md). The statistical words are never reordered or replaced. This runs whenever word prediction is enabled. Existing disabled preferences remain disabled; the compatibility-only enhanced setting remains inert.

Only the isolated prediction worker holds Switchify's tracked typing context. This does not read arbitrary text from focused fields or learn personal text. The neural worker starts on the first prediction, loads once and uses four threads. Windows selects AVX2 only after checking AVX2, FMA and F16C; macOS uses the portable ARM worker. The display keeps its current words selectable while their row is scanned, then shows the queued generated words after leaving the row. Accepting a suggestion uses its exact batch token, never an index into a replacement list.
Only the isolated prediction worker holds Switchify's tracked typing context. This does not read arbitrary text from focused fields. The model itself does not learn; learning from typing changes only the statistical words it excludes. The neural worker starts on the first prediction, loads once and uses four threads. Windows selects AVX2 only after checking AVX2, FMA and F16C; macOS uses the portable ARM worker. The display keeps its current words selectable while their row is scanned, then shows the queued generated words after leaving the row. Accepting a suggestion uses its exact batch token, never an index into a replacement list.

Neural startup, crashes and the 2 second reply deadline leave statistical predictions available. There is no automatic neural retry loop; reopening the keyboard or explicitly retrying prediction starts a fresh session. Context invalidation cancels pending generation. Windows job containment and macOS process groups cover the prediction process tree. Keyboard closure kills the context-bearing worker; its bounded spare loads only the statistical model until the next prediction.

Expand Down Expand Up @@ -36,4 +36,4 @@ The benchmark now requires 1,000 successful warmed refinements across at most fi

## Reply timeout update

The companion inference and reset reply deadline is now 2 seconds, increased from 500 ms after repeated macOS CI timeouts. Startup remains bounded at 30 seconds, statistical suggestions remain immediate, and late or stale results remain rejected. That change pinned both Rust libraries to `171210ee89e3944d1c606d50ad779a5dfb1651b8`, the timeout fix in switchify-prediction PR #22, and kept the v0.2.0 worker assets. Both libraries are now pinned to `2a2ed0c2b0013663ffa9317dba3389ec96b6f14b` with v0.2.1 worker assets, which add six-slot generation; the deadlines are unchanged. The earlier 500 ms measurements above remain historical evidence, not measurements of the increased deadline.
The companion inference and reset reply deadline is now 2 seconds, increased from 500 ms after repeated macOS CI timeouts. Startup remains bounded at 30 seconds, statistical suggestions remain immediate, and late or stale results remain rejected. That change pinned both Rust libraries to `171210ee89e3944d1c606d50ad779a5dfb1651b8`, the timeout fix in switchify-prediction PR #22, and kept the v0.2.0 worker assets. The v0.2.1 worker assets and their converter come from `2a2ed0c2b0013663ffa9317dba3389ec96b6f14b`, and they add six-slot generation. Both Rust libraries are now pinned to `e5223aa8af58d982278db110b70293546db1cdd7`, which adds incremental, bounded personal learning without changing the worker protocol. The deadlines are unchanged. The earlier 500 ms measurements above remain historical evidence, not measurements of the increased deadline.
2 changes: 1 addition & 1 deletion docs/qwerty-keyboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ The bottom control row starts with Close keyboard, followed by Letters, Navigati

Modifier keys are pressed only around each emitted shortcut and immediately released. Selecting a locked modifier does not hold that operating-system key while the scanner runs. Ordinary characters use text injection; command combinations and navigation use native key events. Native shortcuts retain the operating system's layout semantics.

The keyboard and the mouse panel do not depend on other windows. They open and keep working when no window is in front. When the foreground application changes the keyboard stays open and keeps its modifiers, Caps and highlight, and sends subsequent keys to the new foreground application. Only what belongs to the text of one window is let go: the suggestions and prediction context, a space or capital the keyboard owns, and any input still held. Suggestions are unavailable while no window is in front. When its display or work area changes, for example when a taskbar hides, the keyboard moves to fit. On macOS the work area leaves out the menu bar and Dock only while they show. In a full-screen app, or with either set to hide automatically, that is only while the pointer is at the screen edge. So on macOS the keyboard and mouse panel read the work area again only when the Space changes (entering or leaving a full-screen app, or choosing another desktop), following it for a second while it settles. They keep that area on the same display and Space, so they do not jump as the bars come and go. They stay above both and never take clicks. Moving the Dock or changing its size on the same Space takes effect when the Space next changes or scanning starts again. Moving to another display, or this display changing size, still fits them again; if its display is removed it moves to the display under the pointer, and while the displays cannot be read it stays where it is. The mouse panel does the same on the display under the pointer. It closes when its scan session ends. Failed input clears keyboard modifiers and requires Select to resume. Stop, disconnect and application exit use the shared deterministic input cleanup. Word prediction is enabled by default in Scanning settings. Six suggestion slots appear on the Letters page. Selecting a suggestion inserts its missing suffix and a space. Prediction uses local, read-only data; no personal vocabulary is saved. See [word prediction](word-prediction.md) for context availability and validation.
The keyboard and the mouse panel do not depend on other windows. They open and keep working when no window is in front. When the foreground application changes the keyboard stays open and keeps its modifiers, Caps and highlight, and sends subsequent keys to the new foreground application. Only what belongs to the text of one window is let go: the suggestions and prediction context, a space or capital the keyboard owns, and any input still held. Suggestions are unavailable while no window is in front. When its display or work area changes, for example when a taskbar hides, the keyboard moves to fit. On macOS the work area leaves out the menu bar and Dock only while they show. In a full-screen app, or with either set to hide automatically, that is only while the pointer is at the screen edge. So on macOS the keyboard and mouse panel read the work area again only when the Space changes (entering or leaving a full-screen app, or choosing another desktop), following it for a second while it settles. They keep that area on the same display and Space, so they do not jump as the bars come and go. They stay above both and never take clicks. Moving the Dock or changing its size on the same Space takes effect when the Space next changes or scanning starts again. Moving to another display, or this display changing size, still fits them again; if its display is removed it moves to the display under the pointer, and while the displays cannot be read it stays where it is. The mouse panel does the same on the display under the pointer. It closes when its scan session ends. Failed input clears keyboard modifiers and requires Select to resume. Stop, disconnect and application exit use the shared deterministic input cleanup. Word prediction is enabled by default in Scanning settings. Six suggestion slots appear on the Letters page. Selecting a suggestion inserts its missing suffix and a space. Prediction runs on this computer and learns from what the keyboard types unless Learn from my typing is off. See [word prediction](word-prediction.md) for context availability and validation.

## Manual validation

Expand Down
4 changes: 2 additions & 2 deletions docs/six-predictions.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ scan direction. The entire displayed row stays stable until scanning leaves it.
The worker retains exact displayed/latest batch tokens and validates acceptance
against the edit revision, generation, activity epoch and foreground target.
Capitalization, prefix replacement and trailing spaces use the existing path.
No focused-field capture, personal learning or new settings are introduced.
No focused-field capture or new settings are introduced. Learning from typing, described in word prediction, changes only slots 1–3.

The companion uses protocol 2. Model and tokenizer bytes are unchanged. Startup
remains bounded to 30 seconds and each reply to two seconds. Failure leaves the
Expand All @@ -16,7 +16,7 @@ instant slots available, with explicit retry or keyboard reopening required.
## Release assets

The build downloads checksum-pinned v0.2.1 companion release archives from
GitHub. Both libraries and worker provenance are pinned to the release commit.
GitHub. Worker provenance is pinned to the release commit. The Rust libraries are pinned to a later commit that adds personal learning; see word prediction.
Verified local caches work offline, and installed applications need no network
access or model setup. Model, tokenizer and statistical database bytes remain
unchanged.
Expand Down
18 changes: 17 additions & 1 deletion docs/word-prediction.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,30 @@ A passive observer records only an activity counter and timestamp, never externa

## On-device model

One Word prediction setting controls the English `en-aac-oanc-v1` SQLite database and the SmolLM2 model through the pinned `switchify-prediction` Rust libraries. The saved `enhancedWordPrediction` field remains compatible but does not choose an engine. Only the isolated worker opens the read-only baseline; there is no personal database, saved learning, import, or runtime download.
One Word prediction setting controls the English `en-aac-oanc-v1` SQLite database and the SmolLM2 model through the pinned `switchify-prediction` Rust libraries. The saved `enhancedWordPrediction` field remains compatible but does not choose an engine. Only the isolated worker opens the read-only baseline and the personal file described in [Learning from typing](#learning-from-typing); there is no import or runtime download.

The worker ranks up to three words using interpolated trigram/bigram/unigram probabilities (0.6/0.3/0.1, renormalized when context is unavailable) and shows them immediately in slots 1–3. The SmolLM2 model then fills slots 4–6 with up to three different words; see [default neural-assisted prediction](neural-prediction.md). Suggestions begin with the first typed character and continue after a space as next-word suggestions. An untouched keyboard remains empty. The prefix and preceding text are supplied separately, and sentence punctuation resets language context. There are no generated phrases, only single words. A prefix the database does not know leaves slots 1–3 empty; the model may still offer words for slots 4–6. The vocabulary can contain names, disfluencies and inappropriate words; it is English-only and is not a clinical recommendation system.

Loading is asynchronous and typing remains available. The existing badge distinguishes loading, waiting for typed context, held suggestions, no match, available suggestions, paused activity tracking, and failure. **Retry predictions** replaces the worker without closing the keyboard. A clipped buffer without a complete earlier word yields no suggestions. Repeated calls exceeding 1.5 seconds mark prediction unavailable; a missed two-second reply deadline triggers the existing worker replacement policy. Errors never expose buffered text.

The loader checks the pinned database SHA-256 before opening it. Missing, corrupt or incompatible resources fail closed. `npm run prediction-model` obtains the versioned upstream release, verifies its archive and individual file hashes, and installs only allowlisted resources after verification. Verified local files work without network access. Bundles carry the source manifests, attribution and separate corpus notices; see [model provenance](../src-tauri/resources/PROVENANCE.md).

## Learning from typing

**Learn from my typing**, under Suggestions in the keyboard's scanning settings, is on by default. It belongs to this computer: it is saved with this computer's app settings, is not part of synced settings, and changing it does not change the synced settings schema. Words and phrases learned this way are suggested sooner in slots 1–3. The AI slots do not learn, but they exclude the instant words, so they offer different words as learned ones fill the first three.

Learning uses only the prediction buffer, which is successful Switchify keyboard input, never existing, pasted or hardware-keyboard text. The worker gives each piece of text to the library once:

- A sentence is learned once the next sentence begins, so a correction typed just after its punctuation is learned corrected. Backspacing into learned text learns again from the start of the corrected word, so the part of that word kept before the correction counts twice.
- Enter or Tab finishes the text, and everything typed so far is learned.
- When context is lost any other way, the completed words are learned but a word still in progress is not, because it may be unfinished.
- A stretch longer than the 512-character buffer is learned in long pieces before it is clipped. The latest 256 characters are held back so they can still be corrected.
- An unfinished sentence is not learned when the keyboard closes, since closing kills the worker that held it.

The personal file is `prediction/personal.sqlite` in this computer's app data. It holds counts of words and of the one or two words before them. When predicting, one learned use counts as up to 500 uses in the built-in English data, less as more is learned, so learned words can never outgrow it. A word the built-in data lacks, or has seen fewer than three times, is suggested once it has been learned twice, so a word typed wrongly once is not. It is not a copy of the text, but it is unencrypted and can reveal what was typed, including names or passwords. Nothing learned is logged or synced. Learning a sentence takes tens of milliseconds in the worker, and up to about 200 ms once tens of thousands of words have been learned, well within its two-second reply deadline. A personal file that cannot be opened leaves prediction working without learning.

Turning learning on or off replaces the worker at the next keyboard update, so a worker never learns against the setting. A suggestion being accepted at that moment fails, as it does when a worker misses its deadline. **Forget learned words** asks for confirmation, then records the request on disk, kills any spare worker and replaces the keyboard's worker in the same way. The file and its SQLite journals are deleted as soon as no worker holds them. Until then no worker opens the file. A deletion left unfinished when Switchify closed finishes when it next starts. The settings page says whether the words are already gone.

## Casing

The label on a suggestion always reads as the text will after it is accepted. Casing is decided in three steps.
Expand Down
2 changes: 1 addition & 1 deletion scripts/prediction-neural.json
Original file line number Diff line number Diff line change
Expand Up @@ -303,5 +303,5 @@
"url": "https://github.com/switchifyapp/switchify-prediction/releases/download/v0.2.1/switchify-prediction-neural-0.2.1-x86_64-unknown-linux-gnu.zip"
}
},
"library_revision": "2a2ed0c2b0013663ffa9317dba3389ec96b6f14b"
"library_revision": "e5223aa8af58d982278db110b70293546db1cdd7"
}
4 changes: 2 additions & 2 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "2", features = [] }

[dependencies]
switchify-prediction = { git = "https://github.com/switchifyapp/switchify-prediction", rev = "2a2ed0c2b0013663ffa9317dba3389ec96b6f14b" }
switchify-prediction-neural = { git = "https://github.com/switchifyapp/switchify-prediction", rev = "2a2ed0c2b0013663ffa9317dba3389ec96b6f14b" }
switchify-prediction = { git = "https://github.com/switchifyapp/switchify-prediction", rev = "e5223aa8af58d982278db110b70293546db1cdd7" }
switchify-prediction-neural = { git = "https://github.com/switchifyapp/switchify-prediction", rev = "e5223aa8af58d982278db110b70293546db1cdd7" }
anyhow = "1"
base64 = "0.22"
directories = "6.0.0"
Expand Down
1 change: 1 addition & 0 deletions src-tauri/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ fn main() {
"modifier_overlay_ready",
"modifier_overlay_present",
"forget_device",
"forget_learned_words",
"save_settings",
"set_telemetry_consent",
"mark_setup_shown",
Expand Down
1 change: 1 addition & 0 deletions src-tauri/capabilities/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"allow-reject-pairing",
"allow-disconnect-all",
"allow-forget-device",
"allow-forget-learned-words",
"allow-save-settings",
"allow-set-telemetry-consent",
"allow-mark-setup-shown",
Expand Down
11 changes: 11 additions & 0 deletions src-tauri/permissions/autogenerated/forget_learned_words.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!

[[permission]]
identifier = "allow-forget-learned-words"
description = "Enables the forget_learned_words command without any pre-configured scope."
commands.allow = ["forget_learned_words"]

[[permission]]
identifier = "deny-forget-learned-words"
description = "Denies the forget_learned_words command without any pre-configured scope."
commands.deny = ["forget_learned_words"]
Loading
Loading