diff --git a/.github/workflows/native-tests.yml b/.github/workflows/native-tests.yml
new file mode 100644
index 000000000..fa8a00b01
--- /dev/null
+++ b/.github/workflows/native-tests.yml
@@ -0,0 +1,38 @@
+name: Native tests
+
+# Builds and runs the native C++ engine unit tests (app/src/main/jni/tests) on a plain Linux
+# host via the standalone CMake build (jni/CMakeLists.txt) — no AOSP platform tree needed. This
+# covers the dictionary/suggest/geometry engine that the JVM/Robolectric suite cannot reach.
+on:
+ push:
+ branches: [dev]
+ paths: ['app/src/main/jni/**']
+ pull_request:
+ branches: [dev, main]
+ paths: ['app/src/main/jni/**']
+
+jobs:
+ native-host-tests:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ # find_package(JNI) needs JDK headers (jni.h + linux/jni_md.h); setup-java sets JAVA_HOME.
+ - uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: '17'
+
+ - name: Install build tools
+ run: sudo apt-get update && sudo apt-get install -y cmake g++
+
+ - name: Configure
+ run: cmake -S app/src/main/jni -B build-host-tests -DCMAKE_BUILD_TYPE=Release
+
+ - name: Build native tests
+ run: cmake --build build-host-tests -j"$(nproc)"
+
+ - name: Run native unit tests
+ # FormatUtilsTest.TestDetectFormatVersion is quarantined: a host-toolchain anomaly in code
+ # that works on-device (the suite had never run in CI before). Tracked in #80.
+ run: ctest --test-dir build-host-tests --output-on-failure -E 'FormatUtilsTest\.TestDetectFormatVersion'
diff --git a/.github/workflows/update-badges.yml b/.github/workflows/update-badges.yml
deleted file mode 100644
index a3f5715c1..000000000
--- a/.github/workflows/update-badges.yml
+++ /dev/null
@@ -1,78 +0,0 @@
-name: Update README Badges
-
-on:
- schedule:
- - cron: '0 0 * * *' # Midnight UTC
- workflow_dispatch:
-
-permissions:
- contents: write
-
-jobs:
- update-badges:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
-
- - name: Fetch GitHub stats
- id: stats
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
- REPO="LeanBitLab/HeliboardL"
-
- # Latest version
- VERSION=$(gh api repos/$REPO/releases/latest --jq '.tag_name' | sed 's/^v//' 2>/dev/null || echo "N/A")
- echo "version=$VERSION" >> $GITHUB_OUTPUT
-
- # Total downloads
- DOWNLOADS=$(gh api repos/$REPO/releases --jq '[.[].assets[]?.download_count] | add // 0' 2>/dev/null || echo "0")
- echo "downloads=$DOWNLOADS" >> $GITHUB_OUTPUT
-
- # Stars
- STARS=$(gh api repos/$REPO --jq '.stargazers_count' 2>/dev/null || echo "0")
- echo "stars=$STARS" >> $GITHUB_OUTPUT
-
- - name: Generate badge SVGs
- env:
- VERSION: ${{ steps.stats.outputs.version }}
- DOWNLOADS: ${{ steps.stats.outputs.downloads }}
- STARS: ${{ steps.stats.outputs.stars }}
- run: |
- mkdir -p docs/badges
-
- # Format numbers with commas
- DOWNLOADS_FMT=$(printf "%'d" "$DOWNLOADS" 2>/dev/null || echo "$DOWNLOADS")
- STARS_FMT=$(printf "%'d" "$STARS" 2>/dev/null || echo "$STARS")
-
- # Download version badge
- cat > docs/badges/download.svg << EOF
-
- EOF
-
- # Downloads count badge
- cat > docs/badges/downloads.svg << EOF
-
- EOF
-
- # Stars badge
- cat > docs/badges/stars.svg << EOF
-
- EOF
-
- echo "Generated: v$VERSION | $DOWNLOADS_FMT downloads | $STARS_FMT stars"
-
- - name: Update README badge URLs
- run: |
- # Replace shields.io URLs with local badge paths
- sed -i 's|https://img.shields.io/github/v/release/LeanBitLab/HeliboardL?label=Download\&style=for-the-badge\&color=7C4DFF|docs/badges/download.svg|g' README.md
- sed -i 's|https://img.shields.io/github/downloads/LeanBitLab/HeliboardL/total?style=for-the-badge\&color=7C4DFF\&label=Downloads|docs/badges/downloads.svg|g' README.md
- sed -i 's|https://img.shields.io/github/stars/LeanBitLab/HeliboardL?style=for-the-badge\&color=7C4DFF|docs/badges/stars.svg|g' README.md
-
- - name: Commit changes
- run: |
- git config user.name "github-actions[bot]"
- git config user.email "github-actions[bot]@users.noreply.github.com"
- git add docs/badges/ README.md
- git diff --staged --quiet || git commit -m "chore: update README badges [skip ci]"
- git push
diff --git a/AGENTS.md b/AGENTS.md
index 095031ad7..516b7e7b4 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -96,3 +96,18 @@ Before merging a non-trivial change — correctness-sensitive input/dictionary l
- Use `effort: high` for correctness/design passes; lower tiers for quick sanity checks.
- Treat its output as adversarial input, not gospel: it sees the conversation but not tool/scout internals, so verify its claims against the code before acting (it has caught real bugs and unverified assertions in this repo's PRs).
- Especially worth running before merging changes to `InputLogic`, `DictionaryFacilitatorImpl`/`Suggest`, or anything touching the two-thumb/spacing state machine.
+
+## Project Board & Issue Tracking
+The roadmap lives in GitHub Project #3 ("Two-Thumb & Keyboard Roadmap", `gh project … --owner AsafMah`), with a `Status` field (`Todo` / `In Progress` / `Done`) and epics (`[Epic]` issues) parenting sub-issues. **Keep it current as you work — it is the single source of truth, not a chat promise:**
+- When you **open a PR** for an issue, set that issue (and its PR, once added) to **In Progress**, and bump the parent epic to **In Progress** if it was `Todo`.
+- When a PR **merges** (and its issue closes), move both the issue and PR to **Done**; if every sub-issue of an epic is `Done`, move the epic to `Done`.
+- **Add** any issue/PR you create to the project, and close issues a merged PR resolves (use `Fixes #N` in the PR body, or `gh issue close` if the squash/merge message only referenced `(#N)`).
+- Field/option IDs for scripting: project `PVT_kwHOAGIGz84BZwMC`, Status field `PVTSSF_lAHOAGIGz84BZwMCzhUsrio` (Todo `f75ad846`, In Progress `47fc9ee4`, Done `98236657`); set via `gh project item-edit --id --field-id --single-select-option-id --project-id `.
+This convention is loaded every session, so any agent (and future-you) is expected to follow it without being re-told.
+
+## Changelog & Releases
+Keep `CHANGELOG.md` current — it is LeanTypeDual's own history, not a per-line provenance log.
+- **Every user-facing or notable change** gets a line under `## [Unreleased]` (or the in-progress version), grouped `Added` / `Changed` / `Fixed` / `Reliability & testing`, with the `(#N)` issue/PR ref. Internal-only refactors go under `Changed`/`Reliability`; do not enumerate them in the user-facing fastlane note.
+- **Provenance is coarse, not per-entry.** Do NOT tag each line ours/LeanType/HeliBoard. When upstream code is merged in, add a single `Upstream` marker line under that release (e.g. `Upstream — merged HeliBoard 3.9`). Everything not under an `Upstream` marker is original to this fork by default. The fork-only feature set lives in the README, not the changelog.
+- **Versioning:** SemVer `versionName` in `app/build.gradle.kts`; `versionCode` follows `major*1000 + minor*100 + patch*10` (e.g. `3.9.0` → `3900`). On release, also add `fastlane/metadata/android/en-US/changelogs/.txt` (terse, user-facing bullets only). Release chores: `tools/release.py`.
+- On cutting a release, rename `[Unreleased]` to the version + date and start a fresh `[Unreleased]`.
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 000000000..956fc56e3
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,109 @@
+# Changelog
+
+All notable changes to **LeanTypeDual** are documented here.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
+and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+> **Lineage & provenance.** LeanTypeDual is a fork of
+> [LeanBitLab/LeanType](https://github.com/LeanBitLab) (the AI layer), which is itself a fork of
+> [Helium314/HeliBoard](https://github.com/Helium314/HeliBoard) (the keyboard engine), based on
+> AOSP/OpenBoard. Rather than re-list every inherited HeliBoard/LeanType version, this changelog
+> records **LeanTypeDual's own releases**. Points where upstream code was merged in are noted as
+> **`Upstream`** markers; everything else is original to this fork.
+
+## [Unreleased]
+
+## [3.9.0] - 2026-06-10
+
+### Added
+- **HCESAR keyboard layout** for Latin-script subtypes. (#74)
+- **Touchpad edge-scroll** — holding a finger near the touchpad edge auto-repeats cursor movement
+ with acceleration. (#74)
+- **Toolbar: swipe down to hide the keyboard.** (#74)
+- **Toolbar: show only the toolbar when a hardware keyboard is connected.** (#74)
+- **Undo-word toolbar key** — reverts the last committed word back to its suggestion
+ alternatives. (#35)
+- **Pointer-trace recorder** (opt-in) — captures gesture traces + keyboard geometry to JSON for
+ debugging/recognition work. (#20)
+
+### Changed
+- **Graduated trust for newly-learned words** — a just-learned word is held below real-dictionary
+ suggestions until you've used it a few times, reducing premature autocorrect to half-typed
+ words. (#39)
+- Two-thumb down-swipe shortcut popup now tiles its icons proportionally across the usable
+ row. (#36)
+- README status badges switched to live shields.io badges (auto-updating; no CI). (#76)
+- Backspace bookkeeping consolidated into a single, unit-tested `BackspaceUnitStack` (internal
+ refactor, behaviour-preserving). (#31)
+
+### Reliability & testing
+- **Native C++ engine tests now run in CI.** A standalone host build (`app/src/main/jni/CMakeLists.txt`)
+ compiles and runs the dictionary/suggest/geometry gtest suite on every change to the native
+ engine — coverage the JVM/Robolectric tests cannot reach. (#78)
+- Added a golden-master **backspace regression corpus** to the JVM suite, and the **unit-test gate
+ is now blocking** on every PR. (#21, #12)
+
+## [3.8.6] - 2026-06
+### Added
+- Flag learned/typed words that aren't in a dictionary; long-press to **Add** or **Block** them,
+ plus a new **Blocklist** settings screen.
+### Changed
+- Two-thumb: the down-swipe shortcut popup now aligns to the letter row (swiping down on a key
+ selects the icon above it).
+### Fixed
+- Two-thumb ghost-merge: a deleted or cancelled gesture trail no longer fuses into the next swipe.
+
+## [3.8.5]
+### Added
+- Enable or disable individual dictionaries (built-in and custom) in settings.
+### Fixed
+- Toolbar key customization toggles not persisting.
+- Emoji-search keyboard not splitting in landscape when split keyboard is enabled.
+
+## [3.8.4]
+### Added
+- Double-tap touchpad gesture to delete selected words.
+- Clipboard screenshot compression toggle; duplicate screenshots prevented.
+- Text Expander: backspace-to-revert, and `%cursor%` / `%greeting%` / `%tomorrow%` / list
+ placeholders (with optional custom count, e.g. `%list_5%`).
+### Changed
+- Gboard dictionary import performance; settings/editor performance, stability and memory.
+### Fixed
+- Missing words on import; corrupted imports (ZIP signatures pre-verified, streams closed).
+
+## [3.8.3]
+### Added
+- Custom "Clear clipboard" toolbar key icon styles (bin, sweep, slanted, legacy).
+- Custom drawable picker highlights in the Customize Icons grid; instant icon updates without
+ restart; quick clipboard-item clear on long-press.
+### Fixed
+- Swipe-to-delete clipboard crash; pinned-section styling.
+- Double-space-period countdown cancellation on Korean & combiner layouts.
+
+## [3.8.2]
+### Added
+- **Text Expander** with placeholders (`%clipboard%`, `%day%`, `%time12%`, …) and a guide.
+- Customizable tags for Custom AI Keys (themed capsules).
+- Option to fold pinned clipboard items by default.
+- Redesigned Sponsor dialog.
+### Fixed
+- F-Droid reproducible-build packaging discrepancy.
+- Large clipboard-text truncation (native paste); clipboard suggestion in split-toolbar mode.
+
+## [3.8.1]
+### Added
+- Fine-grained vibration strength (amplitude) control for keypress haptics.
+- "Clear All" + confirmation in the personal-dictionary settings screen.
+### Fixed
+- `Resources$NotFoundException` crash from obsolete custom-icon overrides.
+- Spacebar cursor-move and delete swipe in the emoji-search input field.
+- Center-crop scaling for custom keyboard background images (no more squishing).
+
+## Baseline
+
+`Upstream` — Forked from **LeanBitLab/LeanType** (AI proofreading/translation, floating keyboard,
+custom AI keys) on top of **HeliBoard 3.8.x** (the keyboard engine: dictionaries, layouts,
+multilingual typing, glide typing, clipboard history, themes). LeanTypeDual ships as a distinct app
+(`com.asafmah.leantypedual`) and adds, on top of that base, **two-thumb (dual-thumb) typing** and
+the per-release changes above. See the [README](README.md) for the full feature set.
diff --git a/README.md b/README.md
index e7702a486..dc4901dd7 100644
--- a/README.md
+++ b/README.md
@@ -1,37 +1,44 @@
-# LeanType
+# LeanTypeDual
-
+
-[](https://github.com/LeanBitLab/HeliboardL/releases/latest) [](https://github.com/LeanBitLab/HeliboardL/releases) [](https://github.com/LeanBitLab/HeliboardL/stargazers)
+[](https://github.com/AsafMah/LeanType/releases/latest) [](https://github.com/AsafMah/LeanType/releases) [](https://github.com/AsafMah/LeanType/stargazers)
-**LeanType** is a fork of [HeliBoard](https://github.com/Helium314/HeliBoard) - a privacy-conscious and customizable open-source keyboard based on AOSP/OpenBoard.
+**LeanTypeDual** is a privacy-conscious, customizable keyboard built for **two-thumb typing** with **opt-in AI**. It is a fork of [LeanBitLab/LeanType](https://github.com/LeanBitLab) (which layers AI proofreading & translation on top), itself a fork of [HeliBoard](https://github.com/Helium314/HeliBoard) — the AOSP/OpenBoard-based engine that does dictionaries, layouts, multilingual & glide typing, themes and clipboard history.
-This fork adds **optional AI-powered features** using Gemini, Groq, and OpenAI-compatible APIs, offering a hybrid experience: a private, offline core with opt-in cloud intelligence.
+The **"Dual"** is **dual-thumb gesture typing**: glide with both thumbs at once and the keyboard fuses the trails into words. On top of that it keeps a private, offline core with opt-in cloud intelligence, and ships in three privacy tiers. It installs as a **distinct app** (`com.asafmah.leantypedual`) so it can run side-by-side with the upstream keyboards.
-## What's New in LeanType
+## What makes LeanTypeDual different
-- **[🤖 Multi-Provider AI](docs/FEATURES.md#supported-ai-providers)** - Proofread using **Gemini**, **Groq** (Llama 3, Mixtral), or **OpenAI-compatible** providers. Supports dynamic fetching of latest models directly from providers.
+### ✌️ Two-thumb (dual-thumb) typing — the namesake feature
+Type with **both thumbs gliding at the same time**: LeanTypeDual aggregates multiple simultaneous gesture trails into a single word (a Nintype-style flow) instead of forcing one-finger-at-a-time swipes. It has a dedicated tuning screen — combining-mode grace timing, tap-promotion, fragment backspace (pop the last swiped fragment), multi-part word recognition, customizable autospace, and an opt-in typing-insight overlay that visualizes the gesture join. *(Gesture typing requires the gesture library — see Download.)*
+
+### On top of that — LeanType's AI layer and quality-of-life features
+
+- **[🤖 Multi-Provider AI](docs/FEATURES.md#supported-ai-providers)** - Proofread using **Gemini**, **Groq** (Llama 3, Mixtral), or **OpenAI-compatible** providers, with dynamic fetching of the latest models.
- **[🛡️ Offline AI](docs/FEATURES.md#5-offline-proofreading-privacy-focused)** - Private, on-device proofreading and translation using ONNX models (Offline build only).
-- **🌐 AI Translation** - Translate selected text directly using your chosen AI provider, with a separate model selector.
-- **[🧠 Custom AI Keys](docs/FEATURES.md#4-custom-ai-keys--keywords)** - Assign custom prompts, personas (#editor, #proofread), and custom text labels/tags (showing as themed capsules) to 10 customizable toolbar keys.
-- **📝 Text Expander** - Built-in expansion tool supporting custom shortcuts and dynamic template variables (date, time, clipboard, custom placeholders).
-- **🪟 Floating Keyboard** - Detach the keyboard into a draggable window for seamless multitasking. Includes a persistent mode option to keep the keyboard floating.
-- **⌨️ Dual Toolbar / Split Suggestions** - Option to split suggestions and toolbar for easier access.
-- **🖱️ Touchpad Mode** - Swipe spacebar up to toggle touchpad with custom sensitivity controls, including full-screen laptop-style touchpad mode.
+- **🌐 AI Translation** - Translate selected text using your chosen provider, with a separate model selector.
+- **[🧠 Custom AI Keys](docs/FEATURES.md#4-custom-ai-keys--keywords)** - Assign custom prompts, personas (#editor, #proofread), and labels/tags (themed capsules) to 10 customizable toolbar keys.
+- **📝 Text Expander** - Shortcut → expansion with dynamic placeholders (`%clipboard%`, `%day%`, `%time12%`, `%cursor%`, lists), backspace-to-revert, and a guide.
+- **🧠 Smarter learned words** - *graduated trust* keeps a just-learned word below real-dictionary suggestions until you've used it a few times (no premature autocorrect to half-typed words); flag unknown words to **Add** or **Block** them via a Blocklist screen.
+- **↩️ Undo word** - a toolbar key that reverts the last committed word back to its suggestion alternatives.
+- **🗂️ Per-dictionary control** - enable or disable individual built-in and custom dictionaries.
+- **🪟 Floating Keyboard** - Detach the keyboard into a draggable, resizable window (true OS-level overlay), with an optional persistent mode.
+- **⌨️ Dual Toolbar / Split Suggestions** - Split the suggestion strip and toolbar for easier reach.
+- **🖱️ Touchpad Mode** - Swipe the spacebar up for a cursor touchpad with sensitivity controls and edge-scroll acceleration, including a full-screen laptop-style mode.
- **🎨 Modern UI** - "Squircle" key backgrounds, refined icons, and polished aesthetics.
-- **🔄 Google Dictionary Import** - Easily import your personal dictionary words.
-- **⚙️ Enhanced Customization** - Force auto-capitalization toggle, reorganized settings, and more.
-- **🕵️ Clear Incognito Mode** - Distinct "Hat & Glasses" icon for clear visibility.
-- **🔍 Clipboard Search & Undo** - Search through your clipboard history directly from the toolbar, undo accidental item deletions, and fold/collapse pinned items by default to save space.
-- **📸 Screenshot Suggestion & Clipboard** - Suggests recently taken screenshots for quick sharing via the suggestion strip and saves them to your clipboard history.
-- **🔎 Emoji Search** - Search for emojis by name. *Requires loading an Emoji Dictionary.*
-- **🔒 Privacy Choices** - Choose **Standard** (Opt-in AI), **Offline** (Hard-disabled network, offline model load), or **Offline Lite** (Minimalist, no AI) versions.
+- **🔄 Google Dictionary Import** - Import your personal dictionary words.
+- **🔍 Clipboard Search & Undo** - Search clipboard history from the toolbar, undo accidental deletions, and fold pinned items by default.
+- **📸 Screenshot Suggestion & Clipboard** - Recently-taken screenshots are offered in the suggestion strip and saved to clipboard history.
+- **🔎 Emoji Search** - Search emojis by name. *Requires loading an Emoji Dictionary.*
+- **⚙️ Enhanced Customization** - Force auto-capitalization, fine-grained haptics, distinct incognito icon, reorganized settings, and more.
+- **🔒 Privacy Choices** - Choose **Standard** (opt-in AI), **Offline** (network hard-disabled, offline model), or **Offline Lite** (no AI, ~20 MB).
@@ -54,17 +61,12 @@ This fork adds **optional AI-powered features** using Gemini, Groq, and OpenAI-c