# WM Keyboard feature inventory Built for a comparison table. Three levels: family, feature, capability. A rival keyboard can match a family and miss every capability under it, which is the whole point of the nesting. `RARE` marks something few or no mainstream keyboards ship. `uncommon` marks something only some of them do. Unmarked means Gboard or SwiftKey has it too. ## Size of the surface | Area | Families | Features | Capabilities | |---|---|---|---| | Typing core: prediction, autocorrect, learning, spell check | 9 | 50 | 205 | | Input behaviour: glide, gestures, cursor, editing, keys | 11 | 80 | 219 | | Languages, scripts, layouts, transliteration | 11 | 64 | 213 | | Themes and appearance | 14 | 73 | 183 | | Emoji, GIFs, stickers, kaomoji | 16 | 88 | 94 | | Toolbar and the tool set | 10 | 85 | 311 | | Clipboard, snippets, text expansion | 7 | 37 | 191 | | AI, voice, handwriting, scanning | 11 | 70 | 162 | | Privacy, backup, storage, statistics | 14 | 69 | 173 | | Accessibility, form factors, platform integration | 13 | 61 | 111 | | Extensibility: addons, plugins, imports, formats | 5 | 35 | 164 | | Modes, rows, field adaptation, runtime | 12 | 97 | 203 | | **Total** | **133** | **807** | **2229** | ## Typing core: prediction, autocorrect, learning, spell check - **Suggestion engine** — One SuggestionEngine merges completions, corrections, splits, contacts and predictions per keystroke - Trie-guided fuzzy beam search `RARE` — Corrections and completions from one best-first walk, not generate-and-test - Admissible best-first bound — State bound = sourceWeight + ln(1+maxSubtree) - cost; pruning never loses a top-K word - Walks the trie, not the alphabet — Substitution/insertion candidates come from real edge labels, so any script works - Log-space scoring — score = sourceLogWeight + ln(1+frequency) - editCost; max-merge across sources - Ranks 32 deep for the strip, 8 for autocorrect — Deep ranks feed context boosts; silent replacement still judges only the top 8 - Heaviest source first — Sources sorted by root bound so early emissions raise the floor and cut later walks - Pooled per-thread workspace — Parallel primitive arrays plus index heap; no per-keystroke allocation beyond emitted words - Cached ranked walk — Keyed on word, generation, lexicon mutations, K and tap list; strip and commit share one walk - Runaway backstop — 4096 expansions per source maximum - Weighted candidate sources `uncommon` — Seven merged sources with explicit multipliers over dictionary frequency - Personal lexicon x500 - Contact names x3,000, contact emails flat 4,000 — Emails need a 2-character prefix so one letter doesn't dump the address book - App-label names x400 — Below contacts because app labels contain ordinary words like Files and Clock - Imported custom list x100, secondary language x40 — Secondary weight further scaled 0.5x-2x by measured use - Bundled English as a secondary at half frequency — Covers the native-language plus English pairing - Next-word prediction — Empty composing buffer predicts followers from five ordered stores - Trigram context beats bigram — Learned (prev2, prev1) followers consulted before followers of prev1 alone - Contact name chaining — "Wasi" offers "Mollik" from the indexed name - Corpus pack then bundled seed pairs — 827 bundled English pairs cold-start a fresh install - Learned skip-gram followers (#195) — What has followed the word two back one word later, then the word three back two words later; personal, so above the corpus, gappy, so below every direct follower - Skip-gram rescue on an unknown previous word — Treats an OOV prev as transparent and backfills from the word before it - Sentence-start sentinel — U+0001 pseudo-word learned as context only; never offered, never a follower - Numbers from the letter keys `RARE` — A word typed entirely on digit-hinted keys also offers the number those hints spell (#181); off by default - Read off the pressed keys, not the letters — AZERTY's 1 is on a, Dvorak's on the apostrophe, German has none; a number row that is showing has already stripped the hints, so it is quiet then - Last slot, never the primary — Never what a space commits and never autocorrected to; two letters at least, so a lone I is not an 8 - Grouped like the number chip — Five to fifteen digits, no leading zero, under the chip's grouping style, when that chip is on - Next-letter distribution `uncommon` — nextLetterWeights feeds autopilot - Weighted across three sources — Dictionary x1, lexicon x500, custom list x100; 24 completions scanned per source - Boundary-tap remap at pointer-down — Distance divided by (1 + strength x bias); capped reach; the touch is never consumed - Letters layer only, off by default — Skipped for transliterating composers where a Latin nudge is wrong - Case matching — Typed capitalization applied to the suggestion - All-caps typed gives an all-caps suggestion - Leading capital preserved - Emails committed verbatim — A candidate containing @ skips case matching so the address isn't corrupted - **Autocorrect** — Silent replacement on space/enter, gated by a likelihood ratio - Confidence gate `uncommon` — Top candidate must outscore the runner-up by a user-set factor - Slider 1.5-10, default 4 — Reads directly as a likelihood ratio between the top two candidates - Two-source agreement bypasses the gate — Dictionary and personal lexicon independently naming the same word is confidence enough - Synthetic solo runner-up floor — An unopposed weak candidate must clear a fixed floor rather than fire for lack of rivals - Adaptive confidence `RARE` — Gate scaled by the measured fired/reverted ratio - Target revert rate 3%, multiplier clamped 0.85x-2.5x - Needs 20 fired corrections before it says anything - Halving counters at 200 — An exponential moving window with no timestamps - Typing-rhythm gating `RARE` — Mean inter-key gap of the word scales the gate - 150ms burst corrects eagerly, 450ms deliberate demands near-certainty - Gaps over 2s are thought, not rhythm, and are dropped - Needs 2 gaps; strength slider 0 (default) to 1, plus or minus 50% at full - Words it never rewrites — Six hard exclusions before any candidate is considered - Anything known — Bundled, downloaded, imported, secondary-language or learned - Contact names and app names - Under 3 characters - All-caps words — Acronyms and shouting; user toggle, on by default - Anything with two or more digits - Blacklisted or offensive words as targets - Edit model `uncommon` — Six edit types priced as negative logs of multiplicative weights - Substitution, insertion, deletion, transposition — Adjacent and far variants for substitution and insertion - Doubled-character deletion is the cheapest edit — "helllo" from key auto-repeat costs the same as a transposition - Missed-doubling insertion priced as adjacent — "aple" to "apple"; self-adjacency isn't in the proximity maps - Two edits allowed at length 5+ — Budget cap 2.0 nats plus a 2.0 surcharge so a 2-edit fix never outbids a 1-edit one - Completion after an edit costs per character — Stops "skiml" resolving to "skills" over "skill" - Only classic one-edit shapes may fire silently — Edited-then-completed words are neither a target nor a blocking runner-up - Layout-aware key proximity `uncommon` — Adjacency derived from the grid the layout actually draws - Derived, not hand-tabled — A custom or rearranged layout gets correct neighbours for free - Row-above and row-below straddle — i-1 to i+1 in adjacent rows covers a half-key-staggered phone grid - Number row stitched on when shown — Prices a number-row slip as an adjacent substitution - proximityRows override — For split or staggered grids whose rows share no column origin - Continuous touch model `RARE` — Where the finger landed, not just which key won the hit test - Per-key 2D Gaussian in key-width units — Sigma 0.5 key widths; independent of density, size and orientation - Off-center taps make even an exact match cost something — Capped at 1.0 nat, which is what lets a neighbour's word win - Falls back to discrete adjacency — Hardware keys, pasted text and re-armed words carry no tap positions - Missing-space correction `uncommon` — Splits a run into two known words when no single word explains it - "kortehobe" becomes "korte hobe" — Scored by the rarer half; halves at least 2 letters, typed run at least 5 - Fat-fingered spacebar reading — A stray spacebar-adjacent letter between two words is dropped, at a discount - Spacebar-adjacent set follows the layout — Defaults to the QWERTY family's cvbnm - Digit-slip correction `RARE` — One digit inside a word read as a number-row miss - Same-length single-digit swap only — "as3" becomes "ase"; "room3" is never shortened to "room" - Only on the path that buffers number-row digits — The standalone spell checker never rewrites genuine alphanumerics - Learns from your own fixes `RARE` — A typo you repair by hand teaches the pair and the slip behind it - Taught pairs — Fixed twice, autocorrect makes the fix for you; fixed once, it offers it and never applies a different guess over it - A real word swapped for another — "form" for "from" needs three fixes and the bigram context on its side - Slip habits per layout — Each fix aligned letter by letter (3 for e, b for space, a doubled letter missed) discounts that edit in the fuzzy walk, floored at the adjacent-slip tier, one-sided, after eight sightings - Tap model — Where your finger lands per key, learned from every settled word and every kept correction; the glide hand model's class with its own file - Three ways a fix is seen — Backspace-and-retype, tapping into a word and editing in place, selecting a word and typing over it; only the keyboard's own edits are followed and the word is checked against the field once, on leaving it - Autocorrect's wrong fix, fixed by hand — The pair starts from the original typo and the wrong correction is graded as rejected - Learned corrections screen — Every taught pair with its count, the slips noticed, per-pair delete, forget all; both stores ride the Dictionary export section - Undo chip `uncommon` — The word you typed, offered back on the strip after autocorrect rewrote it - Outlives backspace — Backspace reaches a correction for one keystroke; the chip stands until the sentence ends, and finds the word behind the caret rather than assuming it is still there - Gated on how obvious the correction was — Certainty (margin over the confidence gate, or two-source agreement) times how little it moved the word (edit cost and word length); a split correction always qualifies - Configurable bar — Toggle plus a 0–100% slider; 50% ships - Between words only — The tap edits committed text behind the caret, so it waits for an empty composing buffer - Counts as a full rejection — Same path as a backspace revert: the pair is retired and the typed word is noted towards the lexicon - Refuses an ambiguous target — The corrected word must stand as a whole word exactly once in the 96-character lookbehind, and the caret must not have gone back through it - Revert memory `uncommon` — Autocorrect remembers its own mistakes, per pair and in aggregate - Backspace restores the typed word — Also counts double towards learning it, immediately: the user said so directly, so there is nothing left to settle - Exact pair blocked for the run of typing — 20 further verdicts or the next field, whichever comes first; other corrections of the same typed word stay live - Second persisted revert blocks the pair outright — One revert costs a x0.25 handicap and loses shortcut privileges - Deliberate and indirect undos weigh differently — Only a backspace on the correction earns the in-process block; a verdict read back off the settled field does not - An indirect undo of a non-word is discarded — The reproduced-typo case: the user fixes a typo by hand, never having seen it was already fixed, and slips again. A personal word reaches the lexicon after its three sightings and its undos then count in full - Retired pairs come back on probation — 40 quiet saves and the pair reaches the offer chip again, never the silent rewrite; 3 accepts make it ordinary - Four memory levels `RARE` — Off keeps no pair memory, Light never retires a pair, Normal is the shipped balance, Strict retires on the first undo however it was read - Penalties age out — A pair untouched for 180 saves loses a count; 500-pair cap - Stored outside the lexicon — A rejection persists even with learning off or in incognito - **Personal learning** — On-device lexicon of words, bigrams, trigrams and 1-skip and 2-skip bigrams (named by words skipped); nothing leaves the device - Graded reinforcement `RARE` — How deliberately a word was typed decides how hard it teaches - Tapped suggestion counts double - Typed and committed counts once - Autocorrected word earns nothing — Only the word pair around it is learned - Manually added word starts at one use — Marked as added by hand instead of boosted (#164): shielded from autocorrect at any learn-after threshold, evicted last, and it earns weight by being typed like any other word; the row reads "You added this word · Seen N times" (#165) - Store shape and bounds `uncommon` — JSON snapshot in app-private storage - 10,000 words, evicting to 9,000 — 10% hysteresis so compaction doesn't churn on every save - 5,000 bigram heads, 2,000 trigram contexts, 2,000 1-skip and 1,000 2-skip bigram heads, 32 followers each - Exponential decay at compaction — count x 2^(-age/64 save-generations); user-added words evicted last - Dirty-flag save on dismissal — A dismissal with nothing new re-encodes nothing - Word length 32, count capped at 1,000,000 - Word-key normalization `RARE` — One spelling per word across every store - Lowercase plus NFC — Bengali precomposed U+09DF vs decomposed nukta form would otherwise be two words - Applied at the store boundary, not the producer — Also covers pasted text and text typed on another keyboard - Per-word language tag `RARE` — A learned word remembers the language it was typed under - Cross-language damp x3 — A learned-only word tagged for another language is handicapped, never banned - Damp is relative to the detected field language — In a Banglish field the English-tagged habits get damped instead - Untagged words belong to every language — Legacy files and settings-app additions - Personal dictionary screen — Learned words listed with seen-counts - Delete a word, or add one by hand - Version bump makes the IME reload — Stops the in-memory copy clobbering an edit made in settings - Android personal dictionary interop `uncommon` — Two independent opt-ins against the platform UserDictionary provider - Mirror learned words out — So other keyboards and spell checkers know them; a session dedupe set avoids duplicate rows - Expand shortcuts stored there — An entry with a shortcut ("omw" to "on my way") offers the phrase as the top chip - Import from Android's dictionary — Personal dictionary screen lists the platform words missing here as a checklist; added by hand, capitals kept, multi-word rows split (#174) - Export to Android's dictionary — Checklist of personal-dictionary words the platform lacks, most used first; counts only rows the provider accepted; needs WM Keyboard to be the current keyboard (#174) - Learning gates — Three conditions must all hold before anything is remembered - Learn-from-typing setting - Not incognito, when incognito is set to pause learning - Field allows typing intelligence — Password and secure fields never teach - Only a word is learned unasked `RARE` — WordContext.isLearnableWord gates learnWord, PendingLearn.sight, the add chip and the held-menu Add (#185): letters and digits with at least one letter, single interior apostrophe/hyphen/ZWJ/ZWNJ, nothing else; `manager"` never reaches the store or the mirror. addWord (dialog, card, chip yes) is not gated, and UserLexicon.load drops pre-gate junk once, keeping hand-added words - Settle before learning — Nothing reaches the dictionary while the text is still moving - Every committed word queues — 500-word buffer, one field, memory only, never written out - Counted at the flush points — Keyboard closed, message sent, field cleared, another field, buffer full - What the keyboard deletes or writes over is dropped — Backspacing over the word, a delete swipe through it, re-picking a swipe, a fix typed on top of it; a glide that read the wrong word never counts (#160) - The caret alone decides nothing — A caret parked on a word, or walked through it by a spacebar swipe, leaves it waiting; it settles with the rest if nothing is done to it (#159) - The words it jumped over settle instead — Going back to fix one line says nothing about the rest, so a proofreading pass counts the text it left alone rather than discarding the session - A dropped word is remembered for pairing — The word committed in its place, within a slip's distance, carries it as a suspected fix; confirmed against the field at the flush before anything is taught - Known words need one settled use, unknown ones three — The unknown count is the "Remember a new word after" setting - Deleting a word drops its queued copy — Otherwise the flush writes back what the user just removed - Add-by-hand and the Add chip skip the queue — The user answered directly instead of by leaving text alone - An added word is not counted twice — The commit that finishes the word just added by hand adds nothing on top of it - One count per word per place — The same word rewritten in place is one use, not one per pass - Per-app recent-word overlay `uncommon` — Words recently typed in this app get a ln(1.3) ranking edge - **Context ranking** `uncommon` — Layered n-gram evidence over the walk's frequency order - In-strip context boost `uncommon` — Bounded log-space bonus on candidates the context knows - Capped at ln(4) — A habit may re-rank the strip, never bury a frequent exact match - Trigram counts weighted double over bigrams - Corpus pack counts divided by 50 — One personal use is worth about 50 corpus sightings - Bundled seed pairs at ln(1.5) — English modes only - NgramReranker `RARE` — Interpolated rescorer over the top 8 candidates, on-device only - Base is the engine's rank, not raw frequency — Preserves edit costs and touch likelihood already encoded in the incoming order - Nine weighted, capped terms — User trigram/bigram/two skip-grams, pack trigram/bigram, seed bigram, two OOV backoff terms, plus recency - Stored 1-skip bigram, always consulted (#195) — The word two back vouches across any middle word; pools what the trigram splits, still speaks when the middle word is unknown; lifts one rank alone, never two - Stored 2-skip bigram (#195) — The word three back vouches across two middle words ("gotten so that you've"); weaker, lifts one rank alone only once seen a dozen times - Returns null with no evidence — A reorder can only ever be evidence-driven - Skip-gram backoff behind an OOV previous word — Weaker than the direct bigram it stands in for - Never read by autocorrect — shouldAutocorrect reads the raw walk ranking, so a rerank can't become a silent replacement - Register priors `RARE` — Chat-speak ranked by where you're typing - 147-word informal set — Initialisms, contracted spellings, interjections - Casual +ln(1.5), formal -ln(2.5) — Misranking "lol" up in an email costs more than missing it in a chat - Ranking only, off by default — Never gates what commits; the next-word path reorders instead of scoring - Revision advisor `RARE` — The word after sometimes proves the word before wrong - 8 confusable sets — their/there/they're, your/you're, its/it's, whose/who's, were/we're/where, then/than, lose/loose, affect/effect - Chip only, never rewrites committed text - 8x dominance within a single source — User and corpus counts never mixed; minimum 3 personal or 25 corpus sightings - Join chip `uncommon` — "some" plus "thing" offers "something" when the joined word clearly wins - Chip only — It would rewrite text already committed to the field - Joined word needs frequency 50 and a 1.25x lead over the rarer part - Max joined length 24, letters only - Sentence-aware context extraction `uncommon` — WordContext derives prev1 and prev2 from the field text itself - Sentence enders break the chain — "Hello. " yields a sentence-start sentinel, not "hello" - Combining marks count as word characters — Otherwise Bengali words ending in a vowel sign read as ending early - Trigram context degrades to bigram silently — prev2 is null across any boundary rather than guessed - **Dictionaries and binary formats** `uncommon` — Two custom on-disk formats, both memory-mapped and read zero-copy - .wmdict v3 packed trie `RARE` — 5.9 bytes per node, down from 18.1 in v1 - Memory-mapped, never parsed — Load time is one mmap call; resident memory is evictable page cache - Edge-child array eliminated — Breadth-first numbering makes the child of edge e always node e+1 - Symbol-table byte labels — 1-byte labels at up to 256 distinct code units; u16 otherwise, for Hangul and Japanese - Child counts plus checkpoints every 32 nodes — childStart reconstructed by adding bytes already in the cache line - 16-bit minifloat frequencies — Exact below 2048 and monotone, so subtree bounds stay exact and pruning is unchanged - Header-only word count read — UI can show a word count without mapping the file - .wmng v1 n-gram pack `RARE` — Corpus bigrams and trigrams in one file, 1.7MB where the old shape was 17.4MB - Followers are u24 word ids into a shared vocabulary - Ids are ranks in UTF-8 byte order — A follower run sorted by id is also alphabetical, so lookups binary-search by string - Nine 4-byte-aligned sections with padded tails — u24 reads as one getInt shift, u48 as one getLong shift - Three cached head slots per instance — Two bigram heads and one trigram context is exactly what a keystroke needs - Bundled lists — Compiled at build time by the :tools:dictc host tool - English 17,217 words, Bengali 20,645 words - Compiler shares the app's own trie and codec sources — The format cannot drift between writer and reader - Extracted to device-protected storage — One copy serves a normal run and direct boot; re-inflated on app update - Bengali list loads only when Bengali is enabled - Downloadable wordlists `RARE` — 333 catalog entries covering 331 languages - Four size tiers — 50k, 150k, 300k or all most-frequent words - Transfer aborts at the cap — Lists are count-descending, so Thai's 41MB file costs a few MB - 14 romanized transliteration lists — Banglish, Hinglish, romanized Tamil, Telugu, Urdu and others as first-class languages - Compiled to .wmdict on device — The gz never touches disk; .part plus atomic rename means presence equals valid - Frequency floor 2, word length cap 48 — Below the floor is subtitle-scrape noise - Free-space check before starting — About 64 bytes per word plus an 8MB margin - Per-variant source tracking — Portuguese Europe vs Brazil: only the matching row reads as downloaded - Stale-format sweep at startup — An unreadable .wmdict is deleted and offered for download again - Downloadable n-gram packs `RARE` — 3 catalogued languages: English, Bengali, romanized Bengali - Caps of 150,000 bigrams and 75,000 trigrams - Silent automatic fetch plus an explicit retry — A failure is remembered for the process rather than retried each settings emission - Legacy two-file packs deleted eagerly — About 17MB per language of a format nothing reads - User-imported word lists `uncommon` — Any language, several lists stacking per language - wordfrequency, comments and junk lines skipped — Most lists found in the wild import as-is - Imported from a file picker or a URL — 32MB ceiling either way - Additive to the bundled list, not a replacement - A file that parses to zero words is deleted again — Picking a PDF fails visibly instead of contributing nothing - Legacy folder migration — ENGLISH, BANGLA, FRENCH folders renamed to language ids, merging without clobbering - Three trie implementations behind one interface `uncommon` — Mutable node trie, heap-packed CSR trie, mapped trie - WordSource and TrieWalker split — The walker gives node-level access so fuzzy search steers its own traversal - One shared branch-and-bound completer — TrieCompleter replaces three parallel copies of the same loop - CompositeWordSource union — Downloaded plus imported lists merged, keeping the highest frequency per word - **Multi-language ranking** `RARE` — Bilingual typing without switching layouts - Secondary suggestion dictionaries `RARE` — Per primary language, consulted alongside it - A word valid in a secondary is never autocorrected away - Tagged with a language id so its share adapts to real use - English as a secondary rides at half its real frequency — It carries real frequencies unlike the freq-1 imported lists - Language mix confidence `RARE` — Learns which language of the mix you actually lean on - Per-commit geometric decay of 0.999 — Roughly the last thousand words stay in play - Square-root share curve, 0.5x to 2.0x — Moderate use isn't punished as hard as the raw share would - Neutral 1.0 until trained — An untrained keyboard behaves exactly as flat weighting did - Languages faded to noise are pruned from the tally - Per-field language detection `RARE` — Which language this particular field is being written in - Starts from the app's habit — A per-app tally of which language the words typed there belong to seeds each field at up to one and a half words of evidence, so a chat app leans Banglish and mail leans English before a word is typed; one real word outweighs it, and a toggle turns it off - Per-word decay of 0.7 — The last three words are in charge; code-switching swings it back in two or three - Seeded from the words already in the field on focus — Oldest first, so the words nearest the caret dominate - Three calibrated strengths — Gentle 1.4, balanced 2.6, aggressive 4.0 nats of maximum swing per language - Ambiguous words move the mix nowhere — A word valid in several languages credits all of them at once - Evidence ramp — One classified word moves it halfway, three make it certain; unclassified words only decay - Never persisted — Thrown away when the field is left - **Suggestion filtering** `uncommon` — Two independent suppression lists over every suggest path - User blacklist `uncommon` — Words never offered and never used as an autocorrect target - Case-insensitive, stored lowercased - Suppresses suggesting, not typing — The word can still be typed and committed verbatim - Applies to strip, next-word, glide results and split halves - Added by hand in a settings dialog, or by holding a chip on the strip — "Never suggest", with "Suggest again" to undo - Per-language lists (#136) — blacklistByLanguage, one string-set pref per language id; the engine sees global + the language being typed, re-pointed on every layout switch - "Blocked from the keyboard" scope — blacklistScope decides where the hold menu's Never suggest lands (all languages by default, or the current one); the editor asks per word; Suggest again clears every list - **Held-word menu and word card** `uncommon` — Press and hold a suggestion for a menu about it (#99) - Contextual items with icons — Never suggest / Suggest again; Add "typed" while the word being typed is unlearned; Delete for a word the keyboard can forget; Edit always - Add pins the capitals — The typed spelling goes in at full strength and no later vote changes it (#100) - Delete reaches every mutable store — Personal dictionary, waiting room, rank adjustments, Android's dictionary, and the user's imported word lists, whose files are rewritten without the word (#190); a word still in a downloaded list is blacklisted instead - "Delete edits imported lists" switch — Off leaves imported lists as imported and blacklists the word the old way - Word card — In-window modal over the keyboard: every source the word was found in, its "#N of M" place in each list, the word's spelling, its rank controls, and the same actions (#138) - Respell from the card — "Change" hands the keys to a bar in the strip's row (the card covers the board, and an IME cannot raise a field for itself); the draft starts from the word, Enter or ✓ applies it, back or ✕ leaves it, and nothing typed there reaches the app behind (#138) - The draft edits like a field (#204) — its own caret and selection: tap/drag/hold on the bar, spacebar scrub (shift or selection mode extends), hardware arrows/Home/End, shift re-cases a selection, forward delete, and a glide types at the caret with no space and nothing learned; the scrub used to walk the app's caret behind the keyboard - A respelling carries the count, language tag and word pairs across, moves the rank adjustment, and drops the swipe shapes, waiting-room sightings and Android's copy of the old spelling; a case-only respelling pins the spelling instead; a word the lexicon does not have is added at the new spelling - Keep these capitals — The dictionary screen's pin switch, on the card, for a word the keyboard has learned (#138) - Two rank mechanisms, both shown by default and chosen in settings — Learned weight (the lexicon count, decade stepper) or a per-word −10..+10 offset applied in the engine to every source, glide and next-word included; never to autocorrect - Which menu items show is a chip setting — Edit cannot be hidden, since the card carries the rest - Synonyms (#321) — "Synonyms for "word"" on English Latin-letter words opens a sheet of synonyms grouped by part of speech and meaning; a tap goes through the strip's own pick path (replaces the typed word, the caret word or a swipe's word, wears its capitals, is learned like a pick); stored menu sets from before it read it as on (`~synonyms` marker) - Ordered, switchable sources, first with synonyms wins, a source that is down or empty falls through — Vocabulary packs (offline), Datamuse `ml=` filtered to `syn` tags, Wiktionary via kaikki, Free Dictionary API, Datamuse similar words (flagged as such); `SynonymLookup` in `:core:tools` `core.thesaurus`, 32-word answer cache, bypasses data saver, own `NetSource.SYNONYMS`, Datamuse overridable on F-Droid - Pinned capitals — A word added or respelled by hand keeps its case against the vote; "Keep these capitals" switch on the dictionary screen; a tapped chip no longer teaches an auto-capital (#100) - Offensive-word filter — 60-word bundled English list, on by default - Never suggested, never an autocorrect target — So a neutral typo is never corrected into a slur - **Word-boundary fixes** — Commit-time rewrites that sit outside the correction engine - Apostrophe restoration — 67-entry contraction table applied before autocorrect - Outranks autocorrect — "dont" is a known contraction slip, not a typo for "font" - Excludes forms that are real words — its, were, well, ill, shed and lets are left alone - Typed capitalization preserved — Dont to Don't, ARENT to AREN'T; im to I'm stays capitalized - English modes only, never on a fragment glued to an existing word - Bengali fixed-spelling map `RARE` — 14,283 mapped spellings across two bundled TSVs - 2,369 English loanwords — Convention-spelled forms the phonetic rules would never produce - 11,914 romanized chat spellings — Vowel-dropped chat forms; no fold recovers a vowel that was never typed - Curated list outranks the generated one — Load order decides which form leads when the two disagree - Shows in the composing preview, not just at commit — So what the preview shows is what a space would commit - Per-language opt-out — For someone who wants the letter-for-letter reading instead - Bengali phonetic sibling ranking `RARE` — Dictionary siblings outrank the literal transliteration only at 2x frequency - A near-tie sibling never silently replaces what the preview showed - A literal that isn't a dictionary word always yields to siblings - **System spell checker** `RARE` — Keyboard-independent SpellCheckerService backed by the bundled Harper engine - Sentence-level checking `RARE` — Lints carry offsets and lengths so hosts underline the exact span - Spans can cover several words — A their/they're fix is a sentence-level lint, not a word-level one - Per-word API implemented as a fallback — Only lints spanning the whole handed-over word are meaningful there - Insertions rewritten as whole-span replacements — Deletions return nothing: an empty correction row can't be read or chosen - Contact awareness `RARE` — Loads contacts in its own process, independent of the active IME - A contact name or known address is never flagged - Close contact names lead the correction menu — Prefix completions plus one Norvig edit, kept in source capitalization - Squiggle-only mode `RARE` — Flags the typo but suppresses the correction popup - Uses the DONT_SHOW_UI_FOR_SUGGESTIONS result attribute — Android 12+ only; the flag is meaningless below that - Warm-up on the first settings value `uncommon` — Harper's rule set takes about 100ms to build, so the first underline isn't late - Dialect mirrored through a collected flow `uncommon` — A DataStore read per binder call would cost more than the check itself **Conditions and gating** | Feature | Needs | |---|---| | System spell checker (HarperSpellCheckerService) | full flavour only — lite ships no libharper_jni.so, and the service plus its manifest and spellchecker.xml live in src/full | | Contact names and contact-email completion in the strip | needs the READ_CONTACTS grant plus the per-feature setting; both off by default | | Contact-aware spell checking (names never flagged, name corrections lead the menu) | full flavour plus READ_CONTACTS grant plus the contactSuggestions setting | | Downloadable wordlists (333 entries, 331 languages) | needs network; fetched from the public wmkeyboard-data GitHub repo, no API key | | Downloadable n-gram packs (English, Bengali, romanized Bengali) | needs network; fetched silently from the same repo | | Importing a custom word list from a URL | needs network; the file-picker import path does not | | Mirroring learned words into Android's personal dictionary | writes the platform UserDictionary provider, which only permits the current IME or a spell checker to write | | Personal-dictionary shortcut expansion | reads the platform UserDictionary SHORTCUT column; off by default, silently empty on OEM builds that hide the provider | | Squiggle-only spell-check mode | Android 12+ (API 31); the flag is ignored below that | | App-name suggestions | no permission needed — reads installed app labels; off by default | | Personal stores under direct boot | UserLexicon, CorrectionStats and LanguageMixConfidence take a nullable file and run memory-only when credential-encrypted storage is unavailable | ## Input behaviour: glide, gestures, cursor, editing, keys - **Glide typing** — Trie-lattice beam decoder; works in any language whose word list covers the layout, not just English - Best-first lattice decoder over the dictionary trie `RARE` — GlideBeam: no flat word-list scan, candidates come from whatever tries are loaded (bundled, imported, personal, any script) - Arc-length resampling — Stroke resampled to equally-spaced samples so travel between letters is measured in steps, not pixels - Two-term placement cost — Distance-to-key plus disagreement between finger travel and key distance — stops 'hello' decoding as 'ho' - Shape channel rescore — Top candidates re-scored on stroke shape with size and position normalised out; weight 45.0 - Loop-only double letters — 'good' vs 'god': an unlooped doubling pays a charge; a pause or a wiggle says a letter is in the word, never that it is there twice (#337) - Loop-aware double letters — A small circle drawn on a key is Swype's mark for the letter twice: found on a fine resample by path-to-extent ratio and enclosed-area roundness, its arc collapsed out of the travel term, the doubling's charge waived, and every word that leaves it undoubled charged for it - Unclaimed pauses charged — A finished word pays, once per pause, for each pause none of its letters sits on; only the key the pause happened on claims it, never its neighbour, which is what separates 'write' from 'wrote' on one row. A wiggle, when on, files the same event - Exclusive loops — A loop credits only the key nearest its centre, and the samples inside it belong to that key alone, so the keys its edge passes over cannot enter the word - Admissible bound pruning — logWeight + ln(1+maxSubtree) - shapeWeight*minCol; anchor radius 1.6 and near radius 1.5 key widths - 17 injectable tuning weights — sigma, maxPointCost, gapWeight, gapWindow, shapeWeight, repeatCost, unloopedRepeat, unclaimedDwell, loopExtent, loopMinArc, wiggleExtent, wiggleWeight, unclaimedLoop, shapeChannel, anchorRadius, nearRadius, vocabularyRank — swept by a test harness - Per-language glide readiness gate `RARE` — Glide enables itself only where it can decode honestly; measured, not flagged - Coverage measurement — Top 1500 words per word source; grid must be able to spell 90% of them - Converting-layout block — Off on Avro, Hangul, Vietnamese and every CJK conversion layout — a stroke there spells a reading, not a word - Re-checked on dictionary download — Source epoch bumps re-ask the coverage question - Bengali phonetic glide `RARE` — Swipe roman letters on the Avro grid, get Bengali words back via RomanizedIndex - Multi-word glide across the spacebar `uncommon` — One unbroken stroke crossing space commits several words; spacebar points anchor no letter and are dropped - Sequential context — Each segment decoded after the previous one is committed and learned, so word 2 sees word 1 as context - Toggleable — Off makes a spacebar-crossing stroke decode as one word - Capitalize by gliding over Shift `uncommon` — Drawing through the Shift key mid-stroke capitalizes the word, twice shouts it; the key's points are dropped from the word the way the spacebar's are, so the detour spells nothing - The one capital a swipe can teach — A capital the decoder chose is never case evidence; a capital the finger drew is - Readings kept for proofreading — The last 500 swiped words in the field keep their stroke's other readings; putting the caret back on one puts them in front of the strip, and taking one counts as a correction of that stroke - Manual full search — The newest 24 of those also keep the stroke itself, so "Search all words" on a held strip word decodes it again against every list, past the sandbox and the vocabulary cap; a pick teaches the hand model, the shape and the pair, and an optional strip button — a bare search icon, so the candidates it offers to improve on keep the room — offers it without the hold (#135, #264) - Swipe style learning `RARE` — Everything a kept swipe teaches apart from the word itself, behind one switch and one Forget; off freezes it, Forget deletes it without touching learned words - Hand adaptation — Kept swipes teach where the finger really lands on each key; later swipes decode against a grid moved to match (KeyOffsets) - Keyed by position, not letter — Half-key cells, so layouts with the same geometry share what either learned; a barely-swiped key follows the whole hand's mean - Reads a small or large hand as a trend — The slope of the cells' misses against their positions, so a thumb that draws at 85% of the keyboard is followed to the far keys the per-key means alone could not reach; fitted across cells, where each word's own corner cutting cancels - Undo un-teaches — Backspacing a swiped word, or replacing it off the strip, retracts exactly what it taught; the replacement teaches instead - Learned shapes — A few of the user's own shapes per word, from the glides they keep, for the shape channel to compare a stroke against beside the word's ideal path (GlideShapeStore) - Graduated, never taken at commit — The shape rides the word through the learning buffer and lands only when the word settles as a learned word; an undone or replaced glide marks the shape that read it, and a shape rejected more than accepted goes - Bounded benefit — A word's learned shape can shorten its shape distance by at most a fixed gain, so a word swiped before gets the benefit of how it is drawn and never a free pass over words that were not - Outcome memory — A word taken off the strip over the one a stroke was read as, a picker word that was not the leader, a pick off the deep-retry strip, or a swiped word backspaced straight away, nudges the next decode of that pair (GlideOutcomes) - Pairs, not words — "nor over not" says nothing about "nor" against "now"; a pick the other way is counter-evidence, and a pick repays an undo strike - Bounded and fading — At most one close-call margin up and under half of one down, so three consistent picks flip a close call and no habit overturns a clear reading; strength fades with use, not time - Nothing readable on disk — Salted fingerprints, strengths and logical ages; the salt rotates on Forget; not in the backup - Apostrophe in a glide `RARE` — One chosen key (comma, full stop, spacebar or a real apostrophe key) stands for `'` on the glide grid, so "it's" is drawn as i-t-'-s instead of guessed from "its"; off by default - Exactly one key — Emitted before every other pass so it takes the claim on `'` off the long-press alternate a layout may hide one behind; two apostrophe keys measurably blur both - Spacebar is exclusive — Picking it stands down the multi-word split, since one crossing cannot mean both "word ends" and "apostrophe" - Declared spelling when the list has no entry — A stroke through the key rewrites the top reading through the ambiguous contraction table (its → it's, were → we're, lets → let's) that the automatic fix refuses to guess at, keeping the plain spelling next on the strip - Never required — The same word drawn straight still decodes, and both `'` and `’` land on the chosen key - Possessive flick `RARE` — A short straight swipe from the apostrophe key to `s`, drawn straight after a glide, appends `'s` to that word; on by default once an apostrophe key is chosen (never the spacebar) - Detour test — Travel must stay within 1.6× the direct key-to-key distance, so a real word drawn between the same two keys still decodes as the word - Takes the glide's own space back — The possessive lands inside it, and backspace still removes stem and ending in one press - Learned — The possessive enters the personal dictionary, so the next one is glidable in a single stroke - Mid-stroke ambiguity picker `RARE` — Hold still (350 ms by default, 150–1000) while the decode is a close call and the top 2–5 words appear above the fingertip; slide onto one and lift - Frozen stroke — The picker's words are the words that commit: nothing after the open feeds the decoder, so a pick is never dropped (#96) - Timer-driven — The dwell fires from a timer, not the next touch report, so a finger held genuinely still (which sends no events) still opens it - Drift-proof stillness — Measured against where the finger stopped, not the last sample, so slow drift never counts as a hold - Sensitivity tiers — Near ties (0.4 nats), Close calls (1.1, default), Any doubt (2.5), Every pause - Hold to ask — A confident stroke asks after twice the dwell, so any swipe can be second-guessed without lifting - Free to ignore — Lifting off a target commits the decoder's own first choice, outlined so it can be told apart - Drag down to cancel — More than a key below the anchor the targets dim and a lift types nothing - Chained strokes — A pause on the last word of a spacebar-crossing stroke asks for that word - Live preview while swiping — Decoded word drawn over the keys and pushed to the strip at a 40 ms wall-clock cadence (rate-independent of digitizer Hz) - Auto-space after a glided word `uncommon` — Keyboard's own space: punctuation typed after takes it back, a space press is spent confirming it - One-backspace glide undo — Backspace straight after a swipe removes the whole word plus its auto-space, then re-derives bigram context - Shift and caps applied to swipes — CAPS_LOCK uppercases, one-shot shift title-cases the glided word - Apostrophe restoration `uncommon` — 'dont' swiped commits as "don't" (English, gated on the auto-apostrophe setting) - Customisable comet trail `uncommon` — Head width dp, point lifetime ms, peak opacity — all user sliders - Start-sensitivity controls `RARE` — Threshold as a multiple of system touch slop (default 2x) - Post-typing cooldown — For 160 ms after a keypress a glide needs up to 2.5x the travel, fading to none across the window; 0 disables - Must start near a letter key — A drag beginning off the letter grid is never read as a glide - Handwriting instead of glide `RARE` — letterSwipeAction=HANDWRITE turns the same swipe into an ink stroke recognised on the keys - Dot window — For 700 ms after a drawn stroke a tap over the letters is captured as another ink mark (i/j dot, t cross) instead of typing - Backspace discards ink — Backspace with strokes pending throws the ink away rather than deleting committed text - **Spacebar gestures** `uncommon` — Two independently-assignable swipe slots plus hold behaviours - Short-swipe and long-swipe slots `RARE` — 4 actions each (none / language / cursor / numpad); default Language and Cursor - Resolved by hold time, not distance — A drag crossing slop before the long-press delay is 'short'; after it is 'long' — a fast flick travels further than a careful drag - Numpad is one-shot — Crossing slop opens the numeric panel once and the rest of the drag goes inert - Language ring on the spacebar `uncommon` — 44 dp per layout step, live 5-chip tooltip window centred on the selection - Wrap detent — Wrapping past either end costs 2.5x a normal step, so the list parks on the boundary first - Two-language toggle rule — With exactly two layouts one run of travel toggles once; only reversing direction switches back - Flick counts immediately — The movement that crossed the slop already advances one language - A flick moves one language — Further same-way steps wait 200 ms after the last, with banked travel capped at one step, so a fast swipe cannot skip the middle of a 3+ ring; a reversal is ungated - Switch echo — The preview lingers 700 ms after a swipe commits, held above the key grid so it survives the layout swap; a flick used to end before the popup drew - Hold-to-open language picker `RARE` — 250 ms hold (or the long-press delay, whichever is shorter) opens a chooser without any swipe - Only when the hold is free — Skipped when the long-swipe slot names cursor or numpad, since that slot is the setting for what a hold does - Two shapes — Inline preview for up to 4 layouts, scrollable tappable list beyond that - Hold-drag walk — Vertical drag steps the highlighted row; release commits, a still hold leaves it up for tapping - Carousel picker `RARE` — Language picker shape turns the list sideways: a chip strip centred on the current layout, the hold-drag walking it left and right by the swipe's own 44 dp step, the strip re-centring on the highlight; chips tappable, Other keyboards… a glyph past the divider (#150) - Never types a space — Release with the picker or preview up commits a layout instead - Spacebar long-press keys `RARE` — Characters typed into one setting become the spacebar's own alternates popup, on every layer - Claims the hold outright — Authored keys beat the language picker and the space repeat, because a hold cannot mean two things - Editor path too — The layout editor now offers the alternates fields on a space key, so a layout can carry its own set - Corner hint says so — The first key is drawn in the spacebar's corner, which is what tells you the picker moved - Cursor slide — 16 dp of horizontal drag per character; commits the composing buffer first and marks a scrub window - 2-D cursor touchpad `uncommon` — Vertical drag also steps the caret by lines; claims the down direction from swipe-to-hide - Swipe down to hide `uncommon` — Downward drag past 40 dp, steeper than wide, dismisses the keyboard; separate from the toolbar's own swipe-down - Key flicks `RARE` — Short (0.5–2 key heights), straight (detour ≤1.4×), quick (≤250 ms) flicks inside a 30° cone, judged at the lift by keyFlick in two places (inside the glide lift, and a loop of its own on the Initial pass for strokes glide never claimed). Down types the corner hint (layoutBehavior.hintFlick, #178); up types the shifted form, shiftLabel or the letter's capital, without touching shift (layoutBehavior.capitalFlick). Both off by default. An up-flick off a key carrying an octopus word is left to the octopus - 🌐 typing guard `RARE` — layoutBehavior.globeTypingGuardMs, 0–1000 ms in 50 ms steps, default 0 (off): a 🌐 tap within the window after a key that typed into the field (Text, Keyman, Space, Delete, ⌦, Enter, Newline) is dropped at the top of dispatchKey; 🌐 bursts, shift and mode keys do not arm it, and the hold picker is untouched - Spacebar label control `uncommon` — Language / layout / both, or a custom string where %s is the live language name - Direction arrows — ◀ ▶ drawn around the label only when a slot is set to Language and more than one mode is enabled - **Backspace behaviour** — Tap, hold-repeat and word-swipe are one state machine on the key - Grapheme-correct deletion — 64-character lookback so multi-codepoint emoji go in one press - ZWJ and tag sequences — 👨‍👩‍👧, 👍🏽, ☠️ delete whole rather than shedding a piece per press - Surrogate pairs — Falls back to a 2-unit delete when no emoji cluster matched - Per-language conjunct deletion — Opt-in per language id: Bengali/Devanagari clusters delete as one unit instead of code point by code point - Word-swipe delete with acceleration `uncommon` — Drag left past slop: 72 dp for the first word, 56 dp for the next, -6 dp each after, floor 28 dp - Reversal re-anchors — Dragging right stops the run and resets the acceleration; it never un-deletes - Own haptic toggle — Per-word buzz can be silenced separately from the plain backspace - Trailing whitespace goes with the word — Repeated steps chew back like desktop ctrl+backspace - Hold-to-repeat — Starts after the long-press delay at its own interval (20–200 ms, default 50) - Stops at an empty field — The repeat loop polls canDelete() rather than buzzing against nothing - Bypasses the tremor debounce — Repeat ticks go through an un-debounced sink so the rate is not silently capped - Separate repeat-buzz toggle — Only the first press buzzes when off; the key sound still plays - Revert-on-backspace `uncommon` — One press takes back what the keyboard put in the field - Autocorrect — Restores the typed word and counts double towards learning it; the pair that fired is blocked on the spot either way; toggleable - Pattern snippet — Always revertible; reads one character past the commit and puts it back verbatim - Join and revision chips — Tapped word-join and revision suggestions are undone the same way - Context-aware targets `uncommon` — Backspace edits whatever is actually taking keystrokes - Panel search queries — Emoji, dictionary, clipboard, media, plugin, AI-instruction, calculator, converter and typing-test boxes each get the press - Pending morse sequence — Edits the dot/dash sequence and only falls through to a real delete when empty - Composing buffer — Shortens the buffer with the same cluster length the field would have used - Dedicated field backspace in emoji search — A second backspace in the search pill edits the real field with its own hold-to-repeat - Forward delete key `uncommon` — ⌦ deletes after the caret through the InputConnection (works in WebViews), with its own repeat and its own end-of-text predicate - **Cursor control and text editing** — Panel, toolbar tools, volume keys and spacebar all reach the caret - Text-editing panel — Gboard-style d-pad cluster with a Select toggle; 16 actions - Action set — UP/DOWN/LEFT/RIGHT, HOME/END, PAGE_UP/PAGE_DOWN, WORD_LEFT/WORD_RIGHT, SELECT, SELECT_ALL, SELECT_WORD, SELECT_LINE, COPY, PASTE, BACKSPACE - Selection mode — With Select on, every move carries shift and extends the selection; word moves become Ctrl+Shift+Arrow - Own repeat interval — Arrow and backspace auto-repeat, default 60 ms, user-adjustable - Panel layout `RARE` — The pad is a PanelLayoutSpec (issue #63): a grid of Keys with KeyAction.Edit, edited in the key layout editor with its row tools, key sheet and undo; the shipped cluster is BuiltInPanelLayouts.TEXT_EDIT - Same geometry as a key layout — Drawn by PanelLayoutGrid with real KeyButtons, so the tall left/right arrows are just a key three rows deep and the middle rows flow around them - Alternates on press and hold — Edit keys whose operation does not repeat open the alternates popup; Home and End ship holding to the start and end of the text - Repair on read, never reject — The panel repair drops components, shift and chorded keys, clamps widths and spans, and falls back to the shipped grid; an old TextEditLayout preference migrates key for key - 14 one-tap cursor tools `RARE` — CursorTools list: left/right, word left/right, up/down, home/end, page up/down, select word, select line, select all, selection mode — placeable on the toolbar - Shift extends the selection `RARE` — A shift the user pressed turns every toolbar cursor move into shift+arrow; auto-capitalise's own shift is excluded, and is held off the state while the selection is live - Hold to repeat `RARE` — The eight non-idempotent moves repeat on hold at the text-edit interval; one switch for the toolbar, defaulted on - Per-tool toolbox opt-in — A second switch, one tool at a time and off by default, since a repeating tool spends the toolbox hold that opens its settings page - Drag survives the repeat — Travel past the drag slop stops the repeat and picks the tool up, on either surface - Selection mode tool `RARE` — One tap arms it, the next disarms it; while it is on every caret move extends the selection instead of collapsing it - Every caret path obeys it — KeyboardUiState.selectingText is read by the panel's arrows, the toolbar's cursor tools, the spacebar cursor swipe, the volume keys and a layout's own arrow keys - Press and hold for a temporary mode — Selection mode lasts as long as the finger stays on the tool; the release stops the extending and leaves the selection intact, and drag-to-reorder still wins on travel - Double and triple press — Two quick presses select the word at the cursor, three the line, both leaving the mode on; the shift key's own double-tap window, and switchable off - Shared with the panel's Select key — Either surface arms it, either one turns it off, and both light up while it is on; the toolbar's mode outlives a panel opening, the panel's does not - Trackpad tool (issue #39) — The key area as a relative pointing surface: one finger drags the caret a character per stepXDp sideways and a line per stepYDp vertically, sub-step travel carried between frames (TrackpadAxis), every move a real arrow key through onTextEdit - Hold, then drag selects — The long press arms selectionHold through the Selection mode tool's own callback and the release disarms it; the selection stays in the editor - The selection-actions bar waits for the lift (#136) — selectionMacroBarVisible is false while selectionHold is on, so the bar never pushes or swaps the toolbar out from under the held finger; it appears once the finger comes up - Two and three taps — Select word and select line at the caret (TrackpadTapCounter: window plus a distance test, since a surface is wide); switchable off - Two fingers — Drag moves by words (Ctrl+Arrow) at twice the character step; a two-finger tap types a space; a finger arriving or leaving re-anchors the centroid rather than counting as travel - Tap and hold modes — Tap toggles the panel; a press and hold on the toolbar tool opens it only while the finger stays (onTrackpadHold, paired like onSelectionHold, released on drag pick-up and on onFinishInputView); a hold over a tapped-open panel is a no-op; not while the toolbox is open (#320), where the panel swap took the grid away and the tool could not be dragged back into it - Visual feedback — Tool lit while open; finger trail and crosshair drawn from a plain-array ring buffer the draw lambda alone observes; the idle hint fades through a graphicsLayer read - Its own panel layout — BuiltInPanelLayouts.TRACKPAD: the TRACKPAD field over the abc / space / backspace row, editable like the other panels; never full-bleed so the toolbar survives the hold - Caret magnifier (discussion #303) — While a drag or a hold-select moves the caret, a bubble over the text shows the caret's line enlarged, caret centred, selection highlighted, edges faded. Keyboard-drawn (an IME cannot read the app's pixels): CaretMagnifierController reads 64 chars each side off the main thread after every onUpdateSelection and cuts to the line (magnifierLine); requestCursorUpdates(IMMEDIATE | MONITOR) only for the drag's length places it over the insertion marker (screen coords via CursorAnchorInfo.matrix), above the line or below near the top of the screen, else just above the keyboard. FLAG_NOT_TOUCHABLE Popup composed only while up. Paired onCaretDrag per CaretDragSource (TRACKPAD, SPACEBAR), stopped on onFinishInputView, released in a finally on both surfaces, rebound to the new connection when the field restarts mid-drag; skipped for keyboard-owned fields. Every field call on one serial background lane, reads coalesced to one in flight plus one pending; one getSurroundingText call on API 31+ (three calls below); a selection over 1,024 chars is never read back (only the free side, selected side tinted); cursor updates filtered to the insertion marker on API 33+, an invisible-region marker ignored, bubble clamped above the keys; password fields drawn as bullets per code point. trackpad.magnifier and textEditing.spaceCursorMagnifier, both default on - Nested TrackpadSettings — stepXDp 12, stepYDp 28, holdToOpen, multiTap, haptics, trail, magnifier; one KeyboardSettings slot, flat DataStore keys `trackpad_*` - Direct-boot safe, no focus ring, no default leader letter — Only touches the input connection; pointer-only so panelFocusRegions is empty; every letter was taken, T is the toolbox - Survives navigation, not a new field — A caret move never cancels it; a genuinely new editor does, while a restart of the same field keeps it - Volume keys as cursor `RARE` — Volume down/up move the caret left/right while the keyboard is showing; off by default - Media-aware release — Re-checks isMusicActive on every press and hands the keys back to the system while audio plays - Free auto-repeat — Holding repeats via Android's own hardware key repeat; both DOWN and UP are swallowed - Undo / redo `uncommon` — Sent as Ctrl+Z and Ctrl+Shift+Z, with a Ctrl+Y option for apps that want it; commits the buffer first - Composing resume at the caret `uncommon` — Tapping into a finished word re-arms it as the composing region so typing extends it and backspace shortens it - Combining-mark aware — Word boundaries account for Bengali/Devanagari/Tamil/Thai vowel signs, Arabic harakat, Hebrew niqqud — not just isLetter() - Stale-caret guard — Verifies the reported selection is still live before setComposingRegion, and only mirrors into the buffer if the editor accepted the region - Blocked while a panel owns the screen — No resume behind Grammar/AI/Translate, on-keys handwriting, or an in-flight dictation - Caret-scrub window — A spacebar cursor drag suppresses resume mid-drag so the caret's landing spot does not churn - Auto-close brackets `RARE` — Off by default; an opening bracket types its closer behind the caret and leaves the caret between the two - 18 pairs — ( [ { ( [ { 〈 《 ⟨ ⟦ 「 『 「 【 〔 〖 〘 〚 — brackets only; quotes and < are left alone, an apostrophe and a less-than sign being the commoner reading of those keys - Types over its own closer — Pressing the closer when it is already the next character steps the caret past it instead of doubling it - Backspace takes the pair — One press on an empty pair removes both halves; a pair with text in it deletes a character at a time - Stands down in front of a word — A bracket typed immediately before a letter or digit is being put around it by hand; structured and secure fields are excluded like every other typing rule - Selection-aware key behaviour `RARE` — What a key does changes when text is selected - Shift re-cases the selection — lower → Title → UPPER → lower, keeping the text selected so presses walk the cycle; mixed case normalises to lower - Brackets and quotes wrap — 11 pairs — ( [ { < " ' ` “ ‘ « 「 — wrap the selection and leave the inner text selected for another pass - Space and backspace replace — Both drop the composing region first and commit over the selection - Selection macros `RARE` — With text selected, the keyboard offers what that shape of text is for and what any text is for: Undo, Select all, then Call | Message | WhatsApp | Contact on a number, Email first on an address, Open, Remove trackers and QR on a link, and the edits, a find and replace, line tools, case and chat formatting, conversions, grammar, AI, both Bengali directions, speech, maps and calendars on prose. Off by default - Reads the selection whole — A sentence with a number in it is a sentence, and two lines are never one thing; its own pass over the selection rather than the clipboard's fragment scan, which is the surface that has room to show what it found and where - Four readings — Phone, address, link, plain text; bare domains need an ending off a short allow list so a selected filename is not a link, and phone shapes reuse the clipboard's own PhoneFormats masks - Format is entity-aware — A number is re-rendered into the user's own mask separators and all (01712345678 to +880 1712-345678), a link loses utm_*/fbclid/gclid and about twenty more while its path and fragment survive, an address is lower-cased; the chip is dropped when the rewrite would change nothing - Case ladder behind Format on prose — lower / Title / UPPER / Sentence in place of the row, each chip written in the case it applies; acronyms survive Title and Sentence unless the whole selection is capitals, and the result stays selected so the chips chain - Two placements — Its own BarRow.MACROS, animated in and out and reorderable with every other row, or over the suggestion strip at no height cost, since nothing is being typed while text is selected - Forty-nine actions in seven groups behind one allow list — Editing, Lines, Format, Convert, Language, Look up, Open in; twenty-eight shipped on, the chat markers, programmer's cases, decoders, speech, zones, maps, calendars, Search and Translate off because each is a taste or a round trip - One flat row in the user's own order — A reorderable list in settings decides the sequence; the row is filtered to what the selection allows and Undo is pinned first, so the row seen is shorter than the list - Ladders only where an action is a pick — Format's case options, the Fancy styles, a colour's other spellings and the time zones replace the row with choices; every pick converts from the selection as it stood when the ladder opened, so picking again replaces the last choice - Undo per selection session — Every rewrite is stacked (twenty deep) while the selection stays live; the chip checks the field still reads as the last rewrite before putting the original back, and the stack ends with the selection, a new field or a move onto other text - Find and Replace — Find jumps to the next occurrence and wraps; Replace opens a panel with two buffers of the keyboard's own, Aa/whole word/regex toggles, previous/next, replace one and replace all as one undo entry, a count, and a matcher under a time budget enforced through a clock-watching CharSequence - Line tools — Sort (natural order, digits as numbers, again for descending), Unique, Number and Bullets as toggles that convert into each other - Programmer's cases — camelCase, snake_case, kebab-case and CONSTANT_CASE inside the Format ladder, each reading any convention in - Chat markup per app — Bold, italic, strike and mono in the host app's own markers from a package table (WhatsApp, Telegram, Discord, Slack, Messenger, Google Chat and more), toggling off on a second tap, hidden in any app not in the table - Converters — Any numeral system's digits to ASCII, a colour code's other spellings with a swatch on the chip, JSON pretty or minified with key order kept, Base64 and percent decoding behind gates strict enough that a word never qualifies - Content detectors behind switches — Dates and times (numeric in either order, month names in English and Bengali, DateSuggest phrases, zone abbreviations) and places (coordinates read exactly, addresses guessed conservatively) only run while a macro that needs them is on - Banglish both ways — To Bangla through the spelling map, then the phonetic index under the strip's own confidence rule, then literal Avro, letter runs only; to Banglish through the inverted map and a rule romaniser with the inherent vowel dropped where speech drops it - Hinglish both ways — To Hindi and To Hinglish, the same converter over the Hindi scheme; back to Latin through a schwa-deleting romaniser (करना to karna, नमस्ते to namaste), offered only while the Hindi phonetic layout is enabled - Grammar, AI and speech — Harper over the selection alone with every fix committed at once; AI buttons that run a chosen action on the selection and land in the AI panel, whose Replace now swaps the selection rather than the field; reading aloud through the vocabulary speaker with a chip that reads Stop - Intents with fallbacks — Contacts, Maps and Calendar each try two intents and toast when no app takes them - One-time list reset — The on-list and the order carry a list version; a list stored under an older one reads as the shipped list, so every existing user gets the new actions once - Select all leads every row — A long-pressed word widens to the whole field in one tap; the chip is dropped once the selection already spans the field, which costs a one-character read only when the selection starts at offset 0 - Only offers what can work — WhatsApp hidden without WhatsApp installed, Search/Translate/QR hidden when their tools are off, which the power-saving and direct-boot settings views have already decided - Never over a password field — isSecureField is asked before the read, so a password never reaches a Share chooser - Free while idle — Feature switch, secure field, collapsed caret and a span over 4,000 characters are all answered from the offsets onUpdateSelection reports, before the getSelectedText IPC that would otherwise run on every caret move - Acts on the text the chips were drawn for — The offer carries its own text, so a tap runs against what was read, not whatever the field holds by the time the finger lands - Direct-boot aware — Every macro that would start an activity is dropped before the first unlock, leaving select all, copy and the case ladder, which only touch the field the user is already in - Toolbar switch — The Selection actions tool flips the feature from the keyboard, so the bar can be on only for the stretch it is wanted - Long-press letter shortcuts `uncommon` — A/C/V/X/Z/Y carry select-all, copy, paste, cut, undo and redo as entries in their own alternates popup, after the accents the key already has; all six on by default, and each key stays on any layout by naming its own letter - Slide from 🌐 `RARE` — longPressLetterActions.globeDrag, off by default: a drag off 🌐 rides the #67 chord-drag loop (first on the Initial pass, trail line from the key) and the key it lifts on runs its shortcut through the same six letters (letters rebinding included), independent of the six popup switches; a drag that starts after the long-press delay is left to the language picker; glide, octopus and handwriting refuse strokes that start on 🌐 while it is on - Raw-keystroke mode — Optional Ctrl+A/C/V/X as real key events instead of performContextMenuAction, for terminals - **Key press behaviour** — Long press, popups, repeat, chording and press feedback - Long-press alternates — Delay 150–700 ms (default 300); popup radius, shape and font scale all themed or user-set - All-accents mode — Merges the full Latin accent set into every letter popup — 16 base letters with 2–10 variants each; off by default - Corner hint character — First long-press alternate drawn small on the key, with its own size multiplier; drawn by every key that opens a popup, action keys included - Multi-row popups — A popup with more entries than fit across the display wraps onto further rows rather than running off the edge - Alternates sized apart from the bubble — Own text multiplier reaching ×3.20 (the bubble stops at ×1.60, having a fixed box to stay inside) and own 0–32 dp spacing per entry, which is both the gap and the touch target - Fixed column grid `uncommon` — Auto wraps on measured width; 3–11 lays every row on one column grid, so a long list reads as a block and each entry keeps its place between holds - Per-key column count `RARE` — `Key.alternateColumns` overrides the global for one key, set in the editor's key sheet: a fractions key wants a six-wide grid where a letter's four accents want the one row they already get - Fill from the key outward `uncommon` — Optional AOSP row order: the first alternate lands on the row nearest the finger and the overflow climbs away, instead of the popup filling top-down like lines of text - Choose without lifting — The first alternate is highlighted as the popup opens, a slide moves the highlight and the lift commits it, so a hold and a straight release types the first alternate; sliding clear of the popup types nothing. On by default; off restores the popup that stays up for a second tap - Action alternates `RARE` — A popup entry that runs an action instead of typing: Tab, a layer switch, or any tool. Available on every key whose hold is free, which is all of them bar backspace, forward delete, space and the braille dots - Custom currency popup — The $ key's popup list is user-ordered; built-in set is ৳ € £ ¥ ₹ ₿ - No-alternates fallback — A long press on a key with no popup behaves like a tap rather than doing nothing - Key preview bubble `uncommon` — On-key or floating style, each with its own remembered height - Minimum linger 140 ms — A fast tap still leaves a readable bubble instead of a one-frame flash - Stuck-bubble ceiling 750 ms — Hard cap from press, above the long-press timeout, so a dropped release cannot strand a bubble - Off in numeric fields by default — A floating character over a PIN pad is shoulder-surfable - Per-pointer press lifecycle `RARE` — Raw pointer tracking instead of detectTapGestures, so a second finger landing on the same key before the first lifts counts as two keystrokes - Forgiving release bounds — A drift up to half a key beyond the edge still commits; a deliberate slide further cancels - Stolen-pointer handling — When glide consumes the pointer the press is cancelled without committing - Separate key-up event — Fires per finger, for sound packs that recorded a release sample - Tremor debounce `RARE` — Ignores a repeat press of the same key within N ms (0 = off); scoped per key so alternating keys are unaffected - Flick keys — 4-direction flick arms with a live cross popup; ships on the Japanese 12-key layout (ja_flick) - Dominant-axis resolution — 22 dp slop; a flick toward an undefined arm falls back to the centre tap - Long press still works — Committing to an arm cancels the pending alternates timer - Kana variant key — 小゛゜ cycles the last kana through dakuten/handakuten/small forms - Chorded and timed input modes `RARE` — Braille and morse ride the same key dispatch as every other press - 6-dot braille — Perkins contract: dots gather on the way down, the cell commits when the last finger lifts; stolen pointers still count as lifts - Morse — Dot/dash keys with a commit timer; backspace edits the pending sequence - Dead keys `uncommon` — Any key emitting a U+0300–U+036F combining mark is a dead key; fuses via NFC so every precomposed pair Unicode defines is reachable - Spacing fallback — 13 spacing accents for a lone press; no-precomposed-pair falls back to accent-then-letter - Modifier latch keys `uncommon` — Ctrl/Alt/Meta keys: tap arms, double-tap locks, third tap clears - Modifiers sent as real key events — KEYCODE_CTRL_LEFT down/up pairs, because TextView reads modifier state off the key events not getMetaState() - Armed latches consumed, locked ones kept — So Ctrl+Shift+Arrow selection sequences work - Fn layer key `RARE` — One-shot shift into the layout's Fn key map, springing back after one key; a quick second tap sticks - Broadcast key `RARE` — A user-authored key that fires an Android intent action — a macro key for Tasker and friends; types nothing - Tool key `RARE` — A key bound to any of the keyboard's tools: voice typing, the editing pad, the clipboard, a scanner. Runs whether or not the tool is on the toolbar, and wears the tool's own icon - 24 key actions total `uncommon` — text, shift, caps_lock, delete, forward_delete, space, enter, symbols, letters, language_switch, input_method_picker, emoji, numpad, mod, send_key, fn, kana_variant, tool, broadcast, braille_dot, morse_dot, morse_dash, none, unknown - Press feedback gating `uncommon` — Fine-grained beyond a master haptics switch - Per-event vibrate toggles — Space, delete-swipe steps and auto-repeat ticks each have their own switch - Haptic coalescing — A minimum gap defers the second buzz of a fast burst so two presses are felt as two clicks - DND respect — Optionally suppress all keyboard haptics while Do Not Disturb is on - **Shift, caps and auto-capitalise** - Shift state machine — OFF → one-shot ON → OFF, with a double-tap to caps lock - Tunable caps-lock window — 150–600 ms, default 350 (separate from the fixed modifier double-tap window) - Dedicated ⇪ key — Its own action that toggles caps lock outright, for grids wide enough to carry both keys - Auto-capitalise — Driven by getCursorCapsMode, so it follows the app's own sentence rules - Skipped on caseless scripts — Bengali and other scripts with no letter case never arm shift - CAP_CHARACTERS fields — A field asking for all-caps arms caps lock, not a one-shot - Text-class only — Never fires in number, phone, date or time fields - Shift cancels an auto-inserted space `RARE` — One shift press after a double-space '. ' or an auto-space-after-punctuation removes the space and cancels the armed capital - User-shift vs auto-shift distinction `RARE` — shiftPressedByUser is tracked separately so Shift+Enter's newline override never fires on an auto-capitalised empty chat box - Shift+Enter newline — Opt-in: types a real newline instead of firing the field's declared Send action - **Space and punctuation behaviour** - Double-space period — Inserts '. ' within 400 ms, plain text fields only so email/URI/number boxes keep two spaces - Double-space tab `RARE` — Second space inserts a tab instead; wins over the period rule and works even at a line start - Auto-space after punctuation `uncommon` — 'hello,world' becomes 'hello, world'; plain-text fields only, and typing a space yourself right after does not double it - Space hold-to-repeat `uncommon` — Its own interval, separate from delete's, because a runaway spacebar costs more than a runaway backspace - Symbols layer springs back `uncommon` — Typing one of a configurable char set (default !?.,;:) on ?123 returns to the letters; digits deliberately excluded - Long-press ?123 for the numpad `uncommon` — Opens the numeric keypad panel on any field instead of the long press acting as a tap - Long-press enter for emoji `uncommon` — Opt-in; prepends an emoji entry to the enter key's popup, so hold-and-release opens the panel; a Send/Search field's newline alternate stays behind it. Toggle lives in Key press and Bottom row keys - Number row behaviours `uncommon` - Shift swaps digits for symbols — Holding shift on the letters layer turns the digit row into the =\<>[]{}|~ fill row - In-symbols toggle — The digit row can be kept on letters but dropped from ?123 - Untouched-default tracking — Derived from DataStore key presence so tablet defaults can apply without breaking a user's explicit off - **Words on the keys** `RARE` — A predicted word floats over the key that would reach it, taken by a flick up or a tap; the BlackBerry Z10's in-letter prediction (discussion #102) - One placement rule — The word hangs off the first character where the candidate stops agreeing with the buffer, which covers completions, corrections and next-word predictions alike; that index is also the two-tone split - Never relocated — A candidate whose key is already claimed is dropped rather than moved, because a word over a key that would not type it is a lie about the affordance - The strip's own words — the pool is `suggest`'s finished list, so the keys and the strip never disagree; the engine's raw walk only fills keys the strip had no room for, and past a density of 9 the tries are fanned for the rest - Three placements — Floating in the gap above the key (no height change), a reserved lane per row, or inside the key; top-row words straddle the grid and are tapped on the half still over it - Words per key (#136) — wordsPerKey 1–3, default 1; a key honours that many claims in rank order (OctopusWord.tier), stacked away from the key; the flick takes the nearest, a tap the one touched, density still caps the board - Flick or tap `uncommon` — Both switchable; the flick is judged at the lift against a sensitivity tier whose cone matches the glide picker's, so a glide that opens upward stays a glide - Live during a glide — Mid-stroke each alternate hangs off the key where it leaves the decoder's leader, which no other surface can show; one decode, one state write - Hints step aside — A key carrying a word drops its corner hint, read at draw time so the board keeps its per-keystroke recomposition skip; the transliteration hint is never hidden - Off on phonetic layouts — Avro's candidates are readings of the buffer rather than words longer than it, so there is no next key to derive; needs a roman-side completion source - **Autopilot** `uncommon` — Two independent touch-position systems: a visible target nudge and an always-on typo model feed - Next-letter target nudge `uncommon` — Touch target of each letter biased toward the letters most likely to come next; opt-in, letters layer only - Bias source — Dictionary completions from the active, user and custom lexicons, frequency-weighted and normalised to the max - Decided at pointer-down — Recorded on the Initial pass before keys see the touch, consumed by the owning key on release; never consumes the event - Bounded reach — Strength 1-10 (5 by default), and never remaps to a key more than 1.3 key widths from the finger, or when the plain-nearest key already wins - Invalidated on layout change — An in-flight remap is dropped so a release cannot apply a decision made against the old grid - Visible touch areas `RARE` — Optional overlay draws the three likeliest letters at the size their areas have grown to, and outlines the same edge; both off by default, clamped to the board, and gated on recent typing so a glide or a caret move never raises them - Adjustable exaggeration — The drawn face multiplies each side's growth by ×1.0-×3.0 for readability; the outline and the hit test itself ignore it - Touch positions fed to the typo model `RARE` — Every letter-key down position is normalised by key width and paired with the character it committed - Live key-centre publication — Layout letter centres pushed to the engine as a KeyTouchModel, coalesced through snapshotFlow - Per-character tap frame — The composing buffer carries a touch point per character so autocorrect scores against where fingers actually landed - Full-cell touch targets — Keys have no layout spacing — each touch target fills its whole grid cell including the visual gap - **Hardware keyboard** `uncommon` — Physical keys as a first-class input source plus a full shortcut layer - Typed characters through the IME pipeline `RARE` — Transliteration, composing buffer, suggestions and autocorrect all apply to physical keystrokes; toggleable - Interception gate — Mirrors the soft keyboard's composing gate, plus every open panel search / typing test that eats keystrokes - AltGr passthrough — Ctrl+Alt together is treated as character production, not a shortcut, so German AltGr+Q types @ - Space, backspace and enter reuse the soft handlers — So double-space period, glide undo and panel-search Enter all behave identically - Half-press safety — Every consumed DOWN is recorded so the matching UP is swallowed and the app never sees half a keypress - Leader key and tool letters `RARE` — Double-tap Ctrl (default) or a chord arms a picker; a letter then opens a tool - 25 default letter bindings — E emoji, C clipboard, X text edit, V voice, A AI … fully remappable as a complete map - Cheat sheet on ? — Stays armed while the legend is read - Armed timeout 1–30 s — Also how long the on-screen badges stay up; default 8 s - Chord validation — Warns on reserved chords (Ctrl+A/C/V/X/Z/Y/S/F, Ctrl+Space, Ctrl/Alt+Tab), bare leaders, disabled tools and duplicate bindings - Direct chords `RARE` - Ctrl+1…9 toolbar digits — Opens the tool drawn under that digit, no leader first; exact-modifier match so Ctrl+Alt and Ctrl+Shift stay with the app - Alt+1…9 suggestions — Picks the nth suggestion; alternative leader-then-digit mode avoids the collision entirely - Always-on suggestion digit badges — Digits drawn under the strip whenever a physical keyboard is attached - Panel navigation without touching the screen `RARE` — A drawn focus ring moved by the keyboard - Arrow, Tab and Shift+Tab — Tab only claimed once the ring exists — before that it is still field navigation or terminal completion - Home/End and PageUp/PageDown — Jump to the ring region's first/last item or page through it - Escape closes a panel — Only consumed when the keyboard actually has something open - Auto-show and restore — A shortcut that opens a tool forces the input view up and puts it back when the tool closes - Mac shortcut mode `RARE` — 14 actions on Cmd and Option: copy/cut/paste/select-all, undo/redo, line and document ends, word moves, word delete, delete-to-line-start - Ctrl is a disqualifier — Not an alias — an app that handles Ctrl+C keeps getting it - No implicit select-all — Cmd+C with nothing selected is a no-op, unlike the long-press C path - Ctrl+Space language cycling `uncommon` — Forward, Ctrl+Shift+Space backward; holding Ctrl browses an overlay list and releasing commits - Tappable overlay — Rows are tappable with a close button, so unplugging the keyboard mid-browse still has a way out - **Keyboard geometry and reach** — One-handed, split, floating and an inline resize mode - One-handed mode — Docks left or right with an in-keyboard rail - Per-orientation profiles — Width percent, height scale and dock side stored separately for portrait and landscape (landscape defaults to 55% vs 78%) - Rail controls — Flip side and exit buttons; the flip also rewrites that orientation's remembered side - Height compression — Key and number-row heights scale down together to bring the top rows into thumb reach - Split keyboard — Configurable gap percent between halves - Rounded board corners `uncommon` — layoutBehavior.boardCornerTopDp / boardCornerBottomDp (0–40 dp, default 0) and boardCorners (any of the four, default all): the docked frame's board box is clipped to an AbsoluteRoundedCornerShape so the app shows through; no clip at all while square, none on the television card, the floating panel or in resize mode - Midpoint cut — Rows split by accumulated width, ties go right; a straddling spacebar is divided in half with the left half's label blanked - Empty-row safety — A custom layout with a deleted row splits without throwing - Floating keyboard — Draggable panel with a resize grip - Drag handle bar — Position stored as x/y fractions of the available slack so it re-anchors on rotation - Resize grip — Width 240 dp to screen width, height scale 0.6–1.6, both quantised so a drag costs a handful of recompositions - Touchable-region insets — Content insets say the keyboard occupies nothing and the touchable region shrinks to the panel, so the app behind neither resizes nor loses touches - Keep the keyboard on screen `uncommon` — The keyboard stays up after its field is gone and over windows with no field (issue #58); docked or floating - Hacker's Keyboard mechanism, no overlay permission — onEvaluateInputViewShown says yes while pinned; a posted requestShowSelf puts it back after any hide the user did not ask for - User hides suspend it — The hide tool, toolbar swipe and Back all go through requestHideSelf, which parks the pin until the system next shows the keyboard on its own - Bounded re-shows — At most 4 per field, 250 ms apart, so an app that force-hides on every show gets a few flashes and then wins; never re-shown over the lock screen - Fieldless windows get key events — Letters commit through the framework's fallback connection as key events; backspace sends KEYCODE_DEL on TYPE_NULL, Enter and modifier chords already did - Inline resize tool `uncommon` — Gboard-style drag mode over the docked keyboard - Three drags — Top handle scales key heights (keeping the number-row ratio), bottom handle and a centre move button ride the bottom padding - Nothing persists until Done — All changes preview through session state; Reset returns to entry values, Cancel writes nothing - Unchanged fields written as null — A padding-only session does not freeze the key height and lose device-form defaults - Limit feedback — Handles turn red and buzz when pinned at a bound; the scrim eats every touch so resize mode can never type - Per screen variant — Commits against the current variant (portrait / landscape / unfolded) - Docked width and padding `uncommon` - Width percent and alignment — Shrink the board and park it left, centre or right - Symmetric side padding — 0–30% shaved off each edge, narrowing keys toward the centre without docking - Independent bottom-row height — Space/enter row can be made taller than the rest; 0 means follow the key height - **Accessibility-facing input behaviour** `RARE` - TalkBack gesture pass-through `RARE` — An accessibility service hands the key grid's rectangle back to the keyboard so spacebar slide, backspace swipe, glide and handwriting survive touch exploration - Grid only — Strip, toolbar and panels stay outside the carve-out so TalkBack keeps exploring those normally - Retracted automatically — A panel replacing the keys removes the region - Tremor and timing knobs `uncommon` — Long-press delay, key debounce and both repeat intervals are exposed on the Accessibility screen as well as Key press **Conditions and gating** | Feature | Needs | |---|---| | Handwriting instead of glide (letter-area swipe drawing) | full flavour only (BuildConfig.ENABLE_ML_KIT_HANDWRITING); also needs a downloaded ML Kit digital-ink model, so a network fetch on first use | | TalkBack gesture pass-through for spacebar slide / backspace swipe / glide / handwriting | needs the 'WM Keyboard gestures' accessibility service granted by the user | | Glide typing in a non-bundled language | needs that language's word list present — a downloaded dictionary is a network fetch; coverage below 90% keeps glide off | | Auto-download of language data that unlocks glide readiness | network | | Volume keys as cursor control | no permission, but only active while the input view is shown | ## Languages, scripts, layouts, transliteration - **Language registry** — 845 languages, each with script, locale tag and its own layout list - Catalogue size and shape — 845 LanguageDef entries across two Kotlin files, 361 hand-written and 484 generated from the Keyman corpus; unknown ids fall back to a generic Latin language - Endonym · English naming — Every row reads "বাংলা · Bangla", native name first - Script distribution — 234 Latin, 38 Cyrillic, 16 Arabic, 14 Devanagari, 5 Myanmar, 4 Bengali, plus 32 more scripts - Bundled dictionaries only for English and Bengali — bundledDictionary flag is true for 2 of 352; the rest download or learn - Never-throwing lookups — byId/byLocale/languageOf degrade to a generic Latin language rather than crashing - Language search `uncommon` — Matches endonym, English name, id, locale tag, and layout name/id - Layout names are searchable — "avro" or "bepo" finds the language that ships that layout - Prebuilt lowercase index — Memoised on AssetLayouts.generation so asset names join once parsing finishes - Device-derived language suggestions — Ranked from the phone's own signals; no permission, nothing leaves the device - Signal order — System language list first, then one region; SIM country > network country > time zone > locale region - Only the first useful region is read — Stops en-GB ROM regions burying Bengali under Welsh/Irish/Gaelic - 164-country region-to-language table — e.g. IN lists 22 languages, RU 26, ZA 10; English deliberately omitted throughout - Reason shown per row — SYSTEM_LANGUAGE / REGION (with country code) / FALLBACK - Romanized variant offered beside its parent — Adding Bengali also surfaces Banglish, even when Bengali is already on - Fresh-install seeding — Up to 4 layouts, one per recognised system language, English always included - Region is not used to seed — Being in a country offers a language; only the phone's language list enables one - Default enabled set — QWERTY + Avro + Probhat + Jatiya when nothing else matches - Romanized (transliterated-suggestion) languages — 14 `*_rom` entries: ar, bn, gu, hi, kn, ml, mr, ne, pa, ru, si, ta, te, ur - Plain Latin QWERTY, dictionary-only distinction — Stays in Latin script; suggestions come from a transliterated word list - Automatic two-way pairing — RomanizedPairing cross-wires every (romanized, same-script) pair among enabled languages in both directions - Paired words are exempt from autocorrect — Banglish words stop being "corrected" on the English keyboard - Constructed languages `RARE` — 12: Esperanto, Interlingua, Ido, Interlingue, Novial, Lingua Franca Nova, Kotava, Volapük, Lojban, Klingon, Quenya, Toki Pona - Per-language settings screen `uncommon` — Up to eight groups, only the ones that language supports - Layouts toggles — One switch per layout; the last enabled layout across the whole set cannot be turned off - Numerals — Two buttons - the language's own digits or 0-9 - hidden for languages already on Latin digits - Also suggest from — Blend other enabled languages' words into this language's suggestions - Clusters — Conjunct-aware backspace, with a real sample cluster per script (ক্ষ, क्ष, ක්ෂ) - Spellings — Use-known-spellings toggle, shown only for languages with a spelling map (currently bn) - Dictionary / Emoji / CJK conversion groups — Word list, emoji keyword pack, n-gram pack downloads with a combined size prompt - **Scripts and script behaviours** `uncommon` — 37 scripts, each a data row rather than a branch in the service - ScriptDef attributes `uncommon` — Direction, letter case, default composer, font hint, Unicode range, sentence-final mark - 37 registered scripts — Latin, Cyrillic, Greek, Armenian, Georgian, Arabic, Hebrew, Syriac, 11 Brahmic, Thai, Lao, Khmer, Myanmar, Hangul, Ethiopic, Thaana, Japanese, Han, IPA, Tifinagh, Cherokee, N'Ko, Canadian Syllabics, Tibetan, Ol Chiki, Meetei Mayek, Music, Braille - RTL scripts — Arabic, Hebrew, Syriac, Thaana, N'Ko drive the suggestion strip's layout direction - Uncased scripts disable shift semantics — hasLetterCase gates auto-capitalisation and shift-uppercasing rather than an is-Bengali boolean - Script-specific sentence mark `RARE` — ScriptDef.fullStop; Bengali types দাঁড়ি (।) from the key beside the spacebar - ASCII period demoted, not lost — "." moves to that key's long-press for numbers, filenames and URLs - Skipped when the layout already types the mark — Only rewrites a Period-role key whose output is literally "." - Grapheme-cluster (conjunct-aware) backspace `uncommon` — One press deletes a whole cluster; per-language toggle, off by default - Virama table for 13 scripts — Devanagari, Bengali, Gurmukhi, Gujarati, Oriya, Tamil, Telugu, Kannada, Malayalam, Sinhala, Myanmar, Khmer, Meetei Mayek (Apun Iyek) - Bengali has its own tested path — Walks consonant+hasant[+ZWJ][+nukta] pairs; a bare trailing hasant still deletes alone - Generic walk for the rest — Driven by the script's unicodeRange plus its virama, with a guard against a dangling virama over-deleting - Contextual vowel forms `RARE` — A Bengali vowel key types kar, য়-glide, or the independent letter depending on the character before the cursor - Glide only where a bare vowel is invalid — কা + আ gives কায়া, দি + এ gives দিয়ে; ই/উ/ও stay independent (খাই, বউ, খাও) - Per-script pinned fonts `uncommon` — 25 scripts map to a specific Google font; Music to Noto Music, Braille to Noto Sans Symbols 2 - Per-script font pickers — Curated alternative families per script, shown only while a language on that script is enabled - Per-glyph fallback — A glyph the pinned face lacks falls back to the system font rather than blanking - **Keyboard layouts** — 1,599 shipped layouts: 22 compiled built-ins + 1,577 JSON assets (862 of them converted Keyman keyboards) - Shipped catalogue — 20 Kotlin LayoutSpecs (boot-critical) plus 354 .wmlayout.json assets parsed off the main thread - Latin ergonomic alternates — QWERTY, AZERTY, Dvorak, Colemak, Workman, Halmak built in; BÉPO, Swiss German, LatAm Spanish, Turkish-Q as assets - Ambiguous boards — T9 and Compact QWERTY built in; keys twice to four times the usual size, decoded rather than multi-tapped (#103); T9 keypads for 309 more languages as assets (#332) - Indic InScript family — InScript for Marathi, Nepali, Sanskrit, Telugu, Kannada, Malayalam, Gujarati, Punjabi, Odia, Konkani, Maithili, Bodo, Dogri, Bhojpuri; plus Tamil99 and Sinhala Wijesekara - Minority Cyrillic alphabets — Tatar, Bashkir, Chuvash, Chechen, Ossetian, Yakut, Udmurt, Komi, Kalmyk, Tuvan, Buryat, Erzya, Mari, Adyghe, Kabardian, Abkhaz, Avar, Lezgian and more - A failed asset costs one language, not the build — Each file is decoded in runCatching; a malformed one is skipped - Generation counter guards cold-start caches — Anything derived from the shipped set is discarded when the 354 assets finish parsing - Layout model `uncommon` — 9 layers with inheritance; a layer left undefined falls back to the shipped grid - Nine layers — letters, symbols, symbols2, number, phone, date, time, datetime, fn - Key fields — label, output, shiftLabel, action, width, rowSpan, longPress, actionAlternates, clipboardAction, role, icon, iconHint, hideHint, forceHint, flick map - Per-key hint override — hideHint drops the corner hint on one key, icon hint and first alternate alike, while press and hold keeps working; forceHint draws it on one key while the global hints switch is off, and hideHint wins if a file sets both - 24 key actions — text, shift, caps lock, delete, forward delete, space, enter, symbols, letters, language switch, input method picker, emoji, numpad, mod, send_key, fn, kana_variant, tool, broadcast, braille_dot, morse_dot, morse_dash, none, unknown - Broadcast-intent key — Fires an Android broadcast so a key can drive Tasker or any receiver; user-authored only - Per-row height multipliers — rowHeights, index-aligned with rows; short or over-long lists tolerated - Explicit typo-proximity rows — proximityRows lets a staggered/split grid state its own key neighbourhoods - Field-adaptation roles — KeyRole.Comma/Period tag the slots that become @ in email fields and gain / on hold in URI fields, with a legacy label-match fallback - Unknown actions survive decoding — A key written by a newer build decodes to Unknown(tag), is reported, and is dropped by repair instead of failing the file - Tablet auto-expansion `RARE` — Render-time transform widens an eligible grid by one column each side and mints Tab, backslash, caps lock, a mirrored shift and arrows - Row count never changes — The IME window is sized from layer row counts, so the transform only moves and widens keys - Backspace can never go missing — Delete only relocates to the number row when that row is actually drawn - All-or-nothing eligibility — Declines 322 shipped layouts - the 310 T9 keypads, kana flick pads, braille, morse, the Chinese shape/phonetic pads, and grids with no shift key - Per-layout opt-out — tabletExpand flag, default true, for grids already laid out wide by hand - More than one letter per key `RARE` — Key.letters lets a grid put a set of letters on one key and leave the language model to say which was meant (#103); AOSP-derived keyboards cannot, because their decoders are one keystroke to one letter - Two shipped boards — T9 on the ITU E.161 keypad (8 letter keys, digit hints, 4 columns) and Compact QWERTY (QWERTY's own rows folded two to a key, 5 columns) - T9 in 310 languages (#332) — Every keypad follows ETSI ES 202 130's table for its language, or the table the standard names for it (Catalan → Spanish, Faroese → Danish, Rusyn → Ukrainian); languages the standard does not cover start from the table their phones used (German, Russian, Persian, Urdu, or plain E.161) with their own letters on the key ETSI's language-independent table gives them. Indic scripts use the Indian-market plan of ETSI Annex A (signs on 1, vowels with their signs on 2 and 3, consonant rows from 4, punctuation in the shift slot), which Burmese, Khmer, Ol Chiki and Syloti Nagri follow too; Thai and Lao use the Thai phone keypad; Vietnamese's toned vowels ride with their vowel; standalone tone marks take fixed keys. Armenian keeps 9 for punctuation as the standard does; caseless scripts trade the shift key for a comma. Every keypad is checked against its language's 30,000 commonest words - Letter sets hold combining marks — Key.withLetters keeps matras, viramas, nuktas and tone marks, and the decoder walks a key's composition-excluded letters (क़, ড়, אַ) along the decomposed spelling the NFC word lists use, as one keystroke - The buffer stays one character per keystroke — A tap commits the key's anchor letter, so backspace, the caret, word boundaries and every consumer of the composing buffer are untouched - Key sets ride beside the buffer — A per-keystroke frame, the twin of the tap-position frame, snapshotted with the word and size-checked against it - Free unedited matches — Every letter of the pressed key matches at zero cost and zero edits, so readings are not corrections and survive the "a known word suppresses corrections" gate - Ranked by the language model alone — Frequency, n-gram context, the personal lexicon and the register prior order the readings, exactly as a T9 phone ordered them - Honest readings beat commoner edited ones — Edits cost an extra ln(400) here, so throwing a keystroke away needs overwhelming evidence; edits also cap at one, which keeps the frontier from exploding - Long press spells a letter outright — Every letter of a key is in its popup; a picked letter pins that position and decodes as itself, which is how names and unknown words are typed - Commits the reading, not the buffer — No confidence gate: the anchors are not a word, so "leave it as typed" would be the wrong answer rather than the safe one - Glide came free — GlideKeyMap has resolved several characters to one key since Probhat's ক/খ, so a swipe across T9 or the compact grid decodes with no decoder change at all - Proximity and the touch model follow — Neighbours derive between keys rather than letters, and every letter of a key reports that key's measured centre - Secondary layouts `RARE` — Grids of your own reached by a key or the Secondary layout tool, never by picking a language (#62) - LayoutSpec.secondary — Stored beside the other custom layouts; excluded from the language cycle, the OS subtype list and the Languages screen - KeyAction.Layout(id) — Shows the named layout over the letters; a second press, ABC, or ?123 leaves it; also works as a press-and-hold alternate - Secondary layout tool — Toolbar toggle lit while any secondary layout is up; which one it shows is a setting on its page, the first by default - From scratch — A new one is three rows of blank keys plus an ABC key; validate and repair skip the delete/space/enter guarantees for it - Persistent layers `RARE` — LayerSpec.persistent keeps a symbols, Fn or secondary grid up across a close and reopen of the keyboard and across fields (#60) - One switch per layer — No global "persist if…" settings; the editor's toggle carries the "make sure you have a way to exit" warning and the Problems list repeats it - Panels too — The same LayerSpec.persistent on a panel layout's grid keeps the text-edit, emoji, clipboard or trackpad panel open across fields - Panels as tabs of every layout `RARE` — The four panel grids are tabs beside Letters…Fn in each layout's editor (#63, per-layout half); a layout's own copy under `panel_` in `layers` beats the shared panel layout, which beats the shipped grid - Routed repair — `LayoutSpec.repair`/`validateLayout` send `panel_*` layers through the panel rules, so component cells survive and panel problems reach the layout's list - One editor body — `PanelEditorBody` + `panelPreview` shared by the panel's own screen and the layout editor's tabs - A theme per panel — `LayerSpec.themeId` on the panel grid; `screenThemeId` reads the open panel's own theme first, then the typing grid's - Numpad is a panel too — `PanelKind.NUMPAD`, keys only; the tool's pad resolves layout tab → shared pad → the layout's Number layer (#55) → shipped pad in the calculator/dialer order - A theme per layout and per layer `RARE` — LayoutSpec.themeId / LayerSpec.themeId, picked from every built-in and custom theme in the editor; layer beats layout beats mode beats settings (#61) - A view, not a write — applyLayoutTheme overlays keyboardThemeId and switches the auto pair off for as long as the grid shows; the user's own choice is untouched - Missing theme is ignored — A layout shared with a theme this device lacks keeps the pairing and draws normally - Authored Number layer reaches the Numpad tool — The panel and the ?123 long press draw the layout's own Number layer when it has one (#55) - Repair versus validate `RARE` — Two separate passes: one that only reports while editing, one that rewrites on import and activation - Repair guarantees delete, space and enter — Runs on import and when a layout becomes active, so you can never type on a layout you cannot backspace in - Caps enforced — MaxKeyWidth 12, MaxRowWidth 40, MaxKeysPerRow 24, MaxRowsPerLayer 8 - Findings are severity-tagged — BLOCKING vs WARNING; a missing shift key is the one warning - Notes are resource ids, not strings — LayoutMessage resolves at draw time so the report follows the current device language - Selection and migration — Active layout, enabled ids and enabled languages all derive from stored ids - Pre-registry InputMode names translate at read time — Nothing is rewritten, so a downgrade still finds input_mode where it left it - Retired ids fold onto replacements — The 22 per-style asset_fancy_* ids canonicalise to one asset_fancy - Spec version migration — v1 to v2 turns baseMode into langId + composer, moving Avro onto ComposerType.TRANSLITERATE - **Layout editor and sharing** `RARE` — Every shipped layout is editable visually or as raw JSON - Visual grid editor `RARE` — Renders with the user's real theme, key shape and font - Nine layer tabs with inheritance markers — A pencil marks an authored layer; an unauthored tab shows the built-in grid live and forks on edit - Shift-plane preview — Redraws every key with its shiftLabel (or uppercased label) without switching layers - Action picker — 26 options in 6 groups: typing, layers, modifiers, keys apps understand, chorded input, other - Icon catalog on text keys — 46 named vectors plus 24 case-insensitive aliases (delete to backspace, globe to language); unknown names fall back to the label - Width controls — Quarter-step slider, presets 1 / 1.25 / 1.5 / 2 / 4, and a fill-the-row button - Row operations — Add/duplicate/delete row, drag-reorder rows and keys, row-height slider x0.5-x2, reset a layer - 30-step undo/redo with coalescing — Whole-layout snapshots; typing inside one key sheet is a single undo step - Autosave with live problem list — No save button; validation findings update as you edit - Raw JSON editor `RARE` — Draft with an explicit Apply, theme-coloured syntax highlighting, no reformatting - Defaults omitted for humans — encodeDefaults off shrinks a QWERTY from ~33 kB to about a tenth - The edited id always wins — Pasting another layout's id is ignored so you cannot overwrite a different layout - Reaches fields the grid editor has no control for — Clipboard long-press actions and per-direction flick maps - Layout files `RARE` — .wmlayout.json versioned envelope tagged "wmkeyboard-layout" - Format tag is the only strict check — Everything else is repaired and reported rather than rejected - Export, duplicate, import — Filename derived from the layout name; import registered as a system file handler - Editing a shipped layout is an override, not a fork — Stored under the same id; the button reads Reset rather than Delete - Content-sniffed import — A renamed settings backup inside a .wmlayout.json imports as a backup - Imported layouts are never auto-enabled — Added to the list; you turn them on under Languages - Community layouts from addon repositories `RARE` — "layout" is one of the addon types a repo URL can serve, capped at 4 MB - **Importing layouts from other keyboards** `RARE` — Reads FlorisBoard/HeliBoard JSON and HeliBoard's plain-text layout format - Two formats, one parser `RARE` — Format guessed from the first non-comment character, so a commented JSON file is not read as text - Lenient JSON — Comments and trailing commas allowed; rows accepted bare or under arrangement/rows/keys/layout - HeliBoard text — `label popup1 popup2` per line, blank line ends a row - Written from published formats, not upstream source — Deliberate, because Floris is Apache-2.0 and HeliBoard GPL-3.0 while this app is MIT - Key-code translation `RARE` — ~50 foreign codes mapped to native actions, including F1-F12 from the -10039..-10028 range - Space/enter/tab rescued from code points — 0x20 and 0x0A become real actions so auto-space and the field's enter action survive - Unmapped codes are dropped and reported — Never turned into Unknown, which would break the save path - Approximations counted — delete-word to delete, symbols2 to symbols and friends are tallied so the report cannot claim a clean import - Geometry fix-ups `RARE` — Foreign fractional widths scaled x10 onto this app's grid unit; -1 "fill the row" distributed first - Rows already in grid units pass through — Detected by a row total above 2.0 - Character normalisation `RARE` — NFC plus manual nukta recomposition for 22 Devanagari/Bengali/Gurmukhi/Oriya pairs NFC refuses to join - ASCII returned untouched — Nothing below U+0080 can carry a combining mark - Language is asked, never guessed silently `RARE` — A script histogram over the keys seeds the picker; a blank langId would otherwise migrate to English - Repaired once, in front of the user `RARE` — So the grid approved in the dialog is the grid that gets typed on - **Transliteration and composers** `uncommon` — 16 ComposerType values chosen from a layout's script plus optional override - Bengali Avro phonetic `RARE` — Rule-based greedy longest-match transliteration with ~90 rules bucketed by first character - Context-sensitive vowels — Independent letter at word start or after a vowel, kar after a consonant - Inherent vowel handling — lowercase o is silent after a consonant but breaks the cluster: kolokata gives কলকাতা, kolkata conjuncts - Reph, jofola and ba-fola — rr gives র্, y after a consonant gives ্য, w after a consonant gives ্ব (swasthyo to স্বাস্থ্য), w elsewhere gives ও - Khanda-ta three ways — TH, a trailing backquote, or a word-final hasant all collapse ত্ to ৎ - Cluster breaker — Backquote writes nothing and breaks the join: k`s stays কস where kos conjuncts to ক্স - Signs join nothing — ং/ঃ/ঁ hang off the previous syllable; ng before a vowel becomes ঙ (bangla to বাংলা, bangali to বাঙালি) - Ridmik spellings accepted alongside Avro's — qq/cb for ঁ, HH for ঃ, hs for hasant (only after a consonant, so "ahsan" is safe) - Stray capitals read as lowercase — "Bangla" still types বাংলা instead of dropping a Latin B into the buffer - Digits and currency — 0-9 map to ০-৯, $ to ৳, single dot to ।, double dot to a literal period - Bengali Khipro `RARE` — Compositional ক্ষিপ্র layout (#400): the Khipro team's own m17n spec (v36.5.0, MIT) run by a small interpreter, passing their conformance cases - Deterministic — space commits exactly the composed text; no spelling map, no phonetic fold, no autocorrect - Completion strip — composed word first, then bundled/downloaded Bangla words and learned words that start with it - Touch grid per the Khipro guideline — / slicer where shift sits, ; separator beside the spacebar, danda key kept - Hardware keyboard uses the desktop spec — digits to ০-৯, . to ।, $ to ৳, chosen per word by the key that starts it - Lenient Banglish matching `RARE` — Both input and every dictionary word fold to a phonetic key where confusable sounds collapse - Aspiration kept as a parallel mask — Typing the h and not getting it is penalised 20x; leaving it out is penalised 2x; only ক/খ, গ/ঘ, ত/থ, ট/ঠ, দ/ধ, ড/ঢ count - ch and ph excluded on purpose — They are ordinary English-influenced spellings, not aspiration claims - Final ও kept apart — kotha gives কথা and only kothao gives কথাও, despite the fold dropping a trailing o - Nukta normalisation and corpus repair — Decomposed ড়/ঢ়/য় recomposed; the scraped-corpus অ+া pair folded back to আ - Bengali spelling map `RARE` — 11,914 Banglish chat spellings plus 2,369 English loanwords, consulted before the transliterator - Vowel-dropped chat spelling — tmr to তোমার, amk to আমাকে, trpr to তারপর - unreachable by any fold - Loanword conventions — table to টেবিল, keyboard to কিবোর্ড rather than the phonetic তাবলে - Curated list outranks the generated one — Duplicate keys accumulate in stream order, hand-written file first - Per-language off switch — Turning it off is the only way to reach the letter-for-letter reading - Hindi phonetic `RARE` — Latin keys in, Devanagari out (kaise ho gives कैसे हो); its own layout under Hindi, InScript still the default (#239) - Schwa-aware rules — clusters split unless they open or close the word, double a consonant, precede y, or sit in a short join table; a grammatical ending (na ta ka ko se me kar wala) never joins its stem, so karna gives करना and namaste gives नमस्ते - Long finals and anusvara — a word-final a/i/u is long (mera, bhi, tu), a before o/u is long (jao), a nasal before its stop is ं (hindi, lamba) - Other readings on the strip — last a long (pyar, sarkar), all clusters joined, none joined, final left short; what fills the strip when no word list is installed - Lenient Hinglish matching — a fold that forgets aspiration, dental/retroflex, virama, every inner a and vowel length; ड़ filed under d and r, unwritten nasalisation forgiven (nahi/nahin/nhi); typed details (aa, kh, a doubled consonant, ai) handicap siblings that contradict them - Download-only vocabulary — index built over the downloaded and imported Hindi lists, only while the phonetic layout is enabled; a one-time strip chip points at the download when there is none - Hindi spelling map — hand-written everyday words, chat shorthand (h, nhi, kr, bhut) and English loanwords, consulted first; per-language off switch - Glide — strokes decoded against the spelling map and the downloaded Hinglish list, resolved through the same index - Vietnamese Telex and VNI — Two shared-engine transliterators; letters spell the marks in Telex, digits in VNI - Standard tone placement — A marked vowel wins; else single vowel, else last vowel of a closed cluster, else first of an open one (oa/oe/uy take the second) - qu/gi onsets excluded from the nucleus — The u or i is a glide unless it is the syllable's only vowel - VNI buffers digits — bufferDigits on, digitsStartBuffer off, so a plain number on an empty buffer still types - Korean Hangul — Compatibility jamo (two-set) and conjoining jamo (three-set) composed into syllable blocks - 19 initials, 21 medials, 28 finals — Plus 7 compound medials and 11 compound finals - Re-split — 간+ㅏ gives 가나, 갉+ㅣ gives 갈기; two-set only, a three-set final stays where it was typed - Three-set layouts — 세벌식 390 and 세벌식 최종 ship beside the two-set grid; a plain initial typed twice doubles (ᄀᄀ → 까) - 천지인 (Cheonjiin) pad — Samsung-style 12-key grid; vowels folded from the strokes ㅣ ㆍ ㅡ by CheonjiinComposer, consonants picked by multi-tap; space or a 1 s pause ends a consonant's cycle (#372) - No candidate step — Korean gets the ordinary suggestion/autocorrect strip, not a conversion bar - Dead keys — Any key emitting a combining mark U+0300-U+036F arms a dead key - NFC composition, not a table — Every precomposed pair Unicode defines is reachable, including ǹ, ẍ, ṽ - Spacing accents stay literal — Typing ´ ^ ~ ¨ on their own gives ordinary characters so a^2 and backticks still work - Visible fallback — A pair Unicode has no character for emits the spacing accent, or the mark on a dotted circle - **CJK conversion input** — 6 Chinese schemes, Cantonese Jyutping, 3 Japanese layouts, all through one Composer interface - Chinese input schemes — Pinyin, T9 Pinyin, Zhuyin, Cangjie, Cangjie Quick, Stroke - six layouts on one language - Prefix commit — Pinyin/T9/Zhuyin/Jyutping/Japanese: tapping 你 for `nihao` consumes only `ni` and re-converts the tail - Lattice decoding — Viterbi over syllable boundaries with n-gram scoring, capped at 24 units before falling back to prefix lookup - Zhuyin and T9 need no dictionary of their own — Both derive their tables from the pinyin inventory and translate each segment to pinyin for lookup - Cangjie radical glyphs on the keys — a-y printed as 日月金木水...; the composing region shows glyphs, and hardware letters are accepted too - Stroke with a wildcard — Five stroke classes 一丨丿丶乙 (also h/s/p/n/z or 1-5) plus * ? * as an uncertain stroke - Code-table methods cap at 24 candidates — Cangjie and Stroke look up a fixed table rather than ranking a lattice - Double Pinyin — 5 schemes: Microsoft, Sogou, Xiaohe (小鹤), Ziranma, Pinyin++ - Validity-filtered translation — An ambiguous final key resolves to whichever combination forms a real syllable - Apostrophes skipped, not paired — Otherwise one stray apostrophe desyncs the parity of every syllable after it - Jianpin 简拼 — a bare initial is a syllable unit: `wm` → 我们, `zgrm` → 中国人民, mixed `haod` → 好的; z/c/s cover zh/ch/sh, a typed digraph reads both as one initial and as two (`sh` → 是 and 上海) - No second index — Initials are per-unit option lists resolved against the sorted pinyin table by prefix range, the same path T9 and fuzzy use - Exact over abbreviated — A syllable typed in full is never widened; a/e/o rank their exact reading first; a phrase covering the buffer beats a stitch - Fuzzy Pinyin — zh/z, ch/c, sh/s, n/l, r/l, f/h initials and an/ang, en/eng, in/ing, ian/iang, uan/uang finals - Ranking signal, not a filter — Two variants per syllable behind an ln(0.15) penalty, so an exact match only loses to a much likelier fuzzy word - Validity-filtered expansion — Only real syllables are ever generated - Cantonese Jyutping with lazy pronunciation `RARE` — Own 163k-entry pack; tone digits 1-6 buffer without being able to start a buffer - Bidirectional mergers — n/l, ng/none, z/j, c/ch - hypercorrection is as common as the merger itself - Conditional labial mergers — gw to g and kw to k only before a rounded final, so 誇 kwaa1 never becomes kaa1 - Coda mergers — -ng to -n, -m to -n, -k to -t, -p to -t - Japanese — Romaji, 12-key Flick, and Kana JIS layouts all feeding one composer - Romaji to kana transduction — Tracks the romaji span behind every kana unit so consumed lengths report keystrokes, not morae - Flick pad — Per-key four-direction flick map in the layout data (あ flicks to い/う/え/お) - Kana without marks — かつこう finds 学校 (がっこう): a plain kana typed on Flick or Kana JIS also reads as its small, dakuten or handakuten form behind a small per-mark penalty tuned against the real pack; marks are only ever added, and romaji is never widened - Kana-variant key — 小゛゜ cycles the last kana through dakuten/handakuten/small forms (か to が, は to ば to ぱ, つ to っ to づ) - Always usable as a bare kana keyboard — With no pack downloaded the reading still commits as hiragana, katakana or half-width katakana - Tap resolved by index, not text — ja_kana lists 行 under い, いき, ゆき and こう, so matching by string would eat a mora the user never chose - Simplified to Traditional with regional wording `RARE` — Character map plus Taiwan phrase table and TW/HK character preferences - Applied inside each composer — Converting a finished candidate list would break prefix-commit's string matching - Three regions — Standard, Taiwan (出租車 to 計程車), Hong Kong (character preferences only) - Order is load-bearing — Regional tables are keyed to Traditional text, so the character pass must run first - Learning from candidate picks `uncommon` — Per-reading history reorders candidates; namespaced by reading space, not by language - Five reading spaces — pinyin, pinyin_t9, zhuyin, jyutping, ja_kana each keep their own history; Cangjie and Stroke deliberately do not learn - Separate from the Latin user lexicon — That store drops one-character keys, is keyed by surface word, and feeds the gesture decoder where Hanzi is pollution - Governed by the on-device learning setting — Incognito and no-learning fields stop new picks being recorded - Conversion dictionary packs `uncommon` — 5 downloadable packs, none bundled: pinyin 2.5 MB, ja_kana 41.5 MB, jyutping 3.4 MB, stroke 446 KB, cangjie 351 KB - SHA-256 verified before going live — A corrupt conversion table would silently mistranslate - Resumable — .part file plus HTTP Range, with a preflight free-space check - Presence means valid — Atomic rename after verification, so a final file only exists if the download completed - Hot-swap token — A state token summarising which packs are on disk makes a finished download go live on the next field focus - Candidate surface — The suggestion strip is replaced by ranked candidates while a buffer is composing - Expanded grid — Ranked to depth 128 for lattice methods so a tap in the wrapping grid still resolves - Prefix-consistency contract — candidates(b,100).take(12) must equal candidates(b,12) or strip and grid would disagree - Raw reading always commits — No dictionary match never traps the user in an empty buffer - **Numeral systems** `RARE` — Per-language digit glyphs applied at draw time and commit time, layouts stay ASCII - Four non-Latin digit sets `uncommon` — Arabic-Indic ٠-٩, Persian ۰-۹, Bengali ০-৯, Devanagari ०-९, plus AUTO and LATIN - Per language, not global — Arabic shows ٠-٩ while the English beside it stays 0-9 - Only two choices offered per language — Its own digits or Latin - nobody writes Bengali with Persian numerals - Commit scope `RARE` — TEXT_ONLY (default), EVERYWHERE, DISPLAY_ONLY - Numeric fields stay machine-parseable by default — Number/phone/date/time keypads commit ASCII unless EVERYWHERE is chosen - DISPLAY_ONLY is purely cosmetic — Native glyphs on the keys, ASCII in the field - Late application `uncommon` — Layout data, number-row auto-detection and digit long-press data all keep seeing plain ASCII - **Multilingual typing and switching** — Enabled layouts form one ordered switch ring reached four ways - Spacebar gestures `uncommon` — Two independent slots: quick swipe (default Language) and hold+swipe (default Cursor) - Slot chosen by timing, not distance — Crossing touch slop before the long-press delay uses the quick slot - 44dp per step, 2.5x wrap resistance — The boundary layout parks before a deliberate extra pull cycles round - Two layouts toggle instead of cycling — Extra drag in the same direction will not toggle twice - Live preview chips — Every enabled layout as a chip, windowed to five centred on the selection past that count - Spacebar label modes — Language / Layout / Both, auto-falling back to both when two enabled layouts share a language - Hold-drag picker `RARE` — Hold the spacebar and walk a scrollable list without lifting - Threshold at four layouts — Inline preview at four or fewer, scrollable list above that - Threshold can be switched off — Picker for more than four layouts (default on); off keeps the windowed, scrolling inline preview at every ring length. The quick-swipe rule and the 🌐 long press still open the picker - List or carousel — Language picker shape (default List) picks the vertical list or the sideways strip; the threshold and the swipe slots are untouched by it (#150) - First movement absorbed as calibration — Drift from before the hold fired cannot select a neighbour - Held-but-unmoved leaves the list open — So it can be tapped directly - Language-switch key — Tap cycles layouts (not languages); long-press opens the picker - Replaced by an emoji key on a fresh install — globeAsEmoji ships on; switching lives on the spacebar until it is turned off - Hidden altogether — showGlobeKey off takes it, or the emoji key in its slot, off every layout's bottom row; the spacebar takes its width (#139) - Recently-used order `uncommon` — globeRecentOrder (off by default): a tap goes back to the previous layout, taps within 800ms walk further back through a snapshot of the recent list like Alt+Tab, and only where the run ends is recorded; the physical language key follows it too (#311) - Hardware-keyboard overlay — A floated list with tappable rows and a close button, so unplugging the keyboard still leaves a way out - Android input-method subtypes `uncommon` — Every enabled layout registered as a subtype, mirrored both ways - Stable 31-bit id from the layout id — Android persists the user's enabled-subtype choice by this int, so it must not shift when a name changes - Layout id carried in the extra value — layoutId=, so a system-sheet selection maps back to a layout - Both locale spellings set — Legacy underscore subtypeLocale plus modern languageTag, because some OEM switchers still read the former - ASCII-capable flag from the script — True only for Latin-script layouts - Explicit enabling on Android 14+ — setExplicitlyEnabledInputMethodSubtypes; below that the user must tick them in system settings - App-name-first label option — A %s string resource formats "WM Keyboard - " - Per-app language memory `uncommon` — Remembers the layout you explicitly switched to in each package and restores it - Only explicit picks are stored — Apps with no stored pick follow the last-used layout globally - Stale ids self-heal — A remembered layout that no longer resolves to itself is ignored - Field-driven language overrides `uncommon` — The focused field can outrank both the active layout and per-app memory - IME_FLAG_FORCE_ASCII is a hard constraint — Outranks a hintLocales preference, which is only ever a hint - hintLocales picks the first layout of the matching enabled language — The field names a language, not a layout - Field-scoped, never persisted — The override dies with the field - Secondary-language suggestions `uncommon` — "Also suggest from" blends other enabled languages' words in without switching to them - Blended words are exempt from autocorrect — A borrowed word is not corrected back - Long-term mix confidence — Persisted decaying per-language usage weights the secondary tier, so a daily second language outranks a monthly one - Fast per-field language mix — Seeded from text already in the field, updated per commit, decayed per word, never persisted - swings back within two or three words on a code switch - **Notation pseudo-languages** `RARE` — Four non-language entries that behave like languages: IPA, music, braille, Morse - IPA `RARE` — Four layers: pulmonic consonants, vowels, clicks/implosives/ejectives, diacritics and suprasegmentals - Long-press variants — p long-presses to pʰ, ⁱp, p̚ - Rides Latin fonts — No pinned face needed - Latin faces carry IPA Extensions and the modifier letters - Musical notation `RARE` — Two layers over the Musical Symbols block plus BMP ♩♪♫♬; Fn layer holds dynamics, ornaments, repeats - Exists as its own script mainly to pin a font — Noto Music, because device fonts rarely carry the SMP block - Chorded braille `RARE` — Six-key Perkins chord; the cell commits when the last finger lifts - Dot keys fire on press, not release — No long-press timer, unlike every other key - Grade-1 English decoding — a-z, 8 punctuation cells, dot 6 as capital indicator, dots 3456 as number mode until a non-digit cell - Unknown chords commit the raw cell — U+2800 + mask, which doubles as a way to type the Braille Patterns block - Half-chords never survive a context change — Field switch, layout switch or panel open resets it; a pointer stolen by the glide handler still counts as lifted - Morse code `uncommon` — Two big keys; ITU-R M.1677-1 table covering 26 letters, 10 digits and 20 punctuation marks - Fixed 750 ms commit pause — Each signal restarts the timer; no setting to change it - Backspace edits the sequence — Drops the last signal, only falling through to a real delete once nothing is pending - Live readout in the suggestion strip — Sequence in dot/dash glyphs plus the letter it currently spells - Rides Latin script — Autocapitalise and shift still apply to the decoded letters - Rolling SOS window — An 8-character buffer watches for "sos" to fire an easter egg - **Fancy text** `uncommon` — 22 Unicode styles applied at draw and commit time over one plain QWERTY grid - 22 styles `uncommon` — Bold, Italic, Bold Italic, Script, Bold Script, Fraktur, Bold Fraktur, Double-struck, Sans, Sans Bold, Sans Italic, Sans Bold Italic, Monospace, Fullwidth, Circled, Circled Filled, Squared, Squared Filled, Small Caps, Superscript, Strikethrough, Underline - WYSIWYG keys — The style substitutes on the key labels and on what a tap commits; the stored layout stays plain a-z - Style strip with plain-name dropdown — Each chip written in its own style, with a text list for glyphs the device cannot render - Two Unicode techniques — Precomposed Mathematical Alphanumerics for most, combining marks over ASCII for Strikethrough and Underline - Case coverage varies — Small caps, superscript, squared and circled-filled have no capitals; superscript has no q - One layout, not 22 `RARE` — The 22 old per-style asset ids canonicalise onto asset_fancy at read time - Editing applies across every style — Because the base grid is a single ordinary layout - Fancy-text toolbar tool `uncommon` — Turns the fancy keyboard on and off from the keyboard, adding and removing the language as needed - Three settings — Style to start with (or last used), keep the language after turning off, turn off when the keyboard closes - Return-layout tracking — layoutAfterFancy returns to where you came from, or the first remaining stop, or the built-in default - Hardware leader key then F — Reachable from a physical keyboard **Conditions and gating** | Feature | Needs | |---|---| | CJK conversion dictionary packs (pinyin, ja_kana, jyutping, stroke, cangjie) | network download required - none are bundled; until downloaded the composer shows the buffer but no candidates. Both flavours. | | Downloadable word lists, emoji keyword packs and n-gram packs per language | network download; prompted once when adding a language | | Region-based language suggestions | reads TelephonyManager SIM/network country - no permission needed, but absent on Wi-Fi-only devices (falls back to time zone, then locale) | | Per-script pinned fonts and per-script font pickers (Noto Music, Noto Sans Symbols 2, Noto Sans