Skip to content

Release LeanTypeDual 0.3.0 - #145

Merged
AsafMah merged 229 commits into
mainfrom
promote/v0.3.0
Aug 20, 2026
Merged

Release LeanTypeDual 0.3.0#145
AsafMah merged 229 commits into
mainfrom
promote/v0.3.0

Conversation

@AsafMah

@AsafMah AsafMah commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Promotes dev to main for the 0.3.0 release.

Uses the documented promotion recipe: main carries release commits that were never merged back into dev, plus files upstream deleted, so GitHub cannot create the merge cleanly. This commit records both parents but adopts dev's tree verbatim (git read-tree -u --reset origin/dev).

Verified: git diff --stat origin/dev is empty — the tree matches dev exactly — and the commit has two parents.

Contents are 0.3.0 / versionCode 4300: the LeanBitLab v4.1.2 merge (200 upstream commits), two fork regressions that merge would otherwise have introduced, the accelerated-backspace emoji fix, and the test-result gate. Signed APKs for this exact content were already built and verified from chore/release-0.3.0.

github-actions Bot and others added 30 commits August 6, 2026 02:42
AsafMah and others added 29 commits August 20, 2026 05:08
Captures the 0.1.0 release, the LeanBitLab v4.0.3-v4.0.8 integration, the
0.2.0 cut, the one open task (signed v0.2.0 blocked on runner
availability), verified build/test recipes, fork invariants, and the
workflow traps encountered (gh account switching, tag-push triggers, the
dev->main promotion recipe, apksigner low-API verification).
docs: session handoff
The repo tree moved from C:/Users/mahle/ to C:/Users/mahle/programming/, which
left every secondary worktree marked `prunable` -- a relocation artifact, not
abandonment. Repaired all 24 with `git worktree repair` and triaged them.

Section 11 now records the post-cleanup worktree set: five kept deliberately
(PR #106, LeanBitLab PR LeanBitLab#240, three baseline checkouts) and six holding commits
that exist on no remote ref. Thirteen fully-merged worktrees were removed
(~5.6 GB); every branch was kept, so each is restorable with `git worktree add`.

Also documents two traps hit here: never reach for `git worktree prune` when a
relocation makes everything look prunable, and `core.longpaths=true` is
required or `git worktree remove` dies on deep Gradle build output and leaves a
de-registered but half-deleted directory.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a6e8f983-22c2-4f2f-aacd-5316a5924a4d
Backspace appeared dead at the start of a paragraph in rich-text editors
(MS Word, Samsung/Google Notes, Docs). Those editors model paragraphs as
separate blocks and silently ignore a deleteSurroundingText that would
merge two of them, while honouring a real KEYCODE_DEL - which is what
other keyboards send.

HeliBoard already had a KEYCODE_DEL fallback (8f80c2a) but scoped it to
browsers only, so plain multi-line fields never reached it. Upstream
v4.1.2 carries the same logic, so this is not fixed by merging upstream.

Changes in handleBackspaceEvent:
- Route deletion of a '\n' through a key event instead of
  deleteSurroundingText. Equivalent in a plain EditText.
- Broaden the NOT_A_CODE fallback to any editor that hides context, but
  only when the expected cursor position is > 0, so a genuine document
  start stays a no-op and we never dispatch key events at apps that react
  to them (recipient chips, search boxes, web views).
- Never chain the accelerated second delete onto a key event: key events
  take an asynchronous route that ignores batch edits, so a following
  deleteSurroundingText could race ahead and act on pre-merge text.
- Fix a pre-existing bug in the accelerated path, which tested the first
  code point instead of codePointBeforeCursorToDeleteAgain and could
  split an emoji. Also present upstream; worth reporting.

Tests: the fake InputConnection can now simulate a block-based editor
(paragraph-scoped reads, cross-paragraph deletes that report success but
do nothing). Also fixes two latent harness bugs: deleteSurroundingText
did not clamp to available text (drove selectionStart negative), and
setText ignored its own requireIdle parameter.

The tap-into-paragraph variant is added as an @ignore'd test: it measures
expectedSelectionStart == 0, making it indistinguishable from an empty
field, so it needs a device trace before it can be fixed safely.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a88bd77f-d993-44c5-9efc-c7124f0d825e
#133)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a88bd77f-d993-44c5-9efc-c7124f0d825e
The removed-worktree list read as if every LeanType-upstream-* worktree was
retired. They are a recurring, short-lived category created by the section 10
merge recipe; one (LeanType-upstream-412, merge/upstream-v4.1.2) was in flight
when the list was written.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a6e8f983-22c2-4f2f-aacd-5316a5924a4d
Merges 200 upstream commits, pinned at tag SHA
8720abe, covering v4.0.9 through v4.1.2.

Conflict decisions:
- app/build.gradle.kts -> ours. Fork keeps versionCode 4200 / versionName
  0.2.0; never adopt upstream's 4102 / 4.1.2.
- README.md, docs/FEATURES.md -> ours. These are fork-identity documents
  (our badges, two-thumb as the namesake feature, our sponsor links);
  upstream's carry LeanBitLab branding.
- docs/badges/*.svg -> ours, per the fork invariants.
- fastlane changelogs/4100.txt -> ours. Upstream happened to reuse the same
  versionCode; this file is our 0.1.0 release note.
- LatinIME.java -> both sides. Kept the fork's SwipeGestureEngine
  initialize/cancelIndexing alongside upstream's new sInstance tracking and
  voice manager release.
- SettingsValues.java, strings.xml -> both sides. Fork's flag-unknown-words
  and graduated-trust settings plus upstream's personal-dictionary
  learning threshold.
- DictionaryFacilitatorImpl.kt -> both sides. Kept the fork's blacklist
  guard against auto-promotion and added upstream's auto-capitalization
  decapitalize handling for the word actually being added.
- ClipboardHistoryManager.kt -> ours. Both sides define updateClipText with
  different signatures; the fork's returns the deleted entry so the caller
  can offer undo.
- SuggestionStripView.kt -> both sides, with upstream's recycled-view
  restore cancellation ordered before the fork's uncurated-word dialog so
  it cannot be skipped by the early return.
- ClipboardHistoryView.kt -> theirs per hunk. Upstream adds a clipboard
  edit mode; the fork side was only reformatting.
- MainSettingsScreen.kt @Preview arity updated to the merged 15-parameter
  signature.

Fork invariants verified after the merge: applicationId
com.asafmah.leantypedual with its flavor suffixes, version 0.2.0/4200,
INTERNET declared only in the standard and standardfull manifests,
SwipeGestureEngine wired in LatinIME, the two-thumb module still
registered in SettingsContainer, and a single InitializationProvider.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a88bd77f-d993-44c5-9efc-c7124f0d825e
Both were introduced by automatic conflict resolution and were caught by
the fork's own tests, not by the compiler.

1. Whole-word backspace left partial words behind.

Upstream 0cf92cf ("prevent single-click backspace from bulk deleting
numeric sequences") split the end of the composing-word backspace branch
on wasBatchMode: batch mode clears the composing span with
commitText("", 1), everything else now calls finishComposingText()
followed by deleteTextBeforeCursor(1).

The fork's two-thumb whole-word delete also ends with no composing word,
but it is not batch mode, so it fell into the new else branch. That is
exactly the path its own comment warns against - deleteTextBeforeCursor
does not remove the composing region and deletes committed text before
it instead, which is the "This is pretty cool" -> "precool" corruption.
Five tests caught it.

Tracked with an explicit wholeWordDeleted flag so the whole-word and
batch paths both clear the span, while upstream's numeric-sequence fix
still applies to the ordinary last-character-removed path.

2. Custom layouts stopped restoring after symbol mode.

Upstream added `lastCustomIndex = 0` when switching from symbols back to
the alphabet. This fork ships persistent custom layout slots that are
meant to survive symbol mode, orientation changes and keyboard reloads
(0.1.0), and KeyboardStateTest asserts it. Dropped the upstream line.

Test suite is back to the documented Windows baseline of 4 ParserTest
failures, which pass on Linux CI.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a88bd77f-d993-44c5-9efc-c7124f0d825e
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a88bd77f-d993-44c5-9efc-c7124f0d825e
…edure

The signed v0.2.0 release went out on 2026-08-20 with all four APKs, so the
document's headline claim -- a single open task blocked on CI runners -- was
wrong. Verified against the API before rewriting: the release is published, is
marked latest, and carries four signed APKs; Release run 31128748928 succeeded
on 2026-08-06 at 22:04 UTC.

Section 5 keeps its recipe verbatim because it is still the correct procedure for
the next release, but is now framed that way rather than as a blocker, with the
outage signature retained so a recurrence is recognised instead of re-debugged.
Also refreshes the stale dev SHA in section 1 (2c1c828 -> 6ac372d) and lists
the real open items in section 12: device verification of #134 and #137,
re-pointing check-upstream-main to v4.1.2, and reporting the emoji
accelerated-delete bug upstream.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a6e8f983-22c2-4f2f-aacd-5316a5924a4d
Section 5 was still headed "THE OPEN TASK - publish signed v0.2.0", which
is no longer true: v0.2.0 was published on 2026-08-20 with all four
signed APKs and is marked latest. The runner outage that blocked it
resolved on its own; Release run 31128748928 succeeded and produced the
draft.

Reframes section 5 from a blocker into the verified release procedure,
since the recipe itself is still what the next release should follow, and
keeps the outage signature so it is recognised rather than re-debugged.

Also refreshes the TL;DR table (v0.2.0 published, current dev head, the
four open PRs including #134, #136 and #137) and replaces section 12's
"publish v0.2.0" item with the work that is actually outstanding: device
verification of #134 and #137, re-pointing LeanType-check-upstream-main
to v4.1.2 to re-check the two guarded upstream defects, reporting the
emoji accelerated-delete bug upstream, and deciding the fate of the
unfinished worktrees whose commits exist on no remote.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a88bd77f-d993-44c5-9efc-c7124f0d825e
…v4.1.2

Three statements written earlier in this PR went stale within the hour, so they
are corrected before merge rather than shipped as fresh staleness:

- Section 5 said the two runs stuck in queued could be cancelled; both have since
  been cancelled, so it now reads as history with the recovery command kept for a
  recurrence.
- Section 11 said check-upstream-main was intentionally pinned at v4.0.8 and
  should be re-pointed by whoever merges next. It has already been moved to
  v4.1.2 for PR #137, so it is now described as tracking the tag currently being
  integrated.
- Section 12 listed the re-pointing as an open item. The re-pointing is done; what
  remains is the re-check itself, which is in flight in #137 where the runTests
  guards live.

Also adds a forward pointer in section 7 so its v4.0.8 reproductions are not read
as the current state, and drops the stale cancel-the-zombies chore.

Verified against the API before editing: both runs report cancelled, and the
worktree HEAD resolves to tag v4.1.2.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a6e8f983-22c2-4f2f-aacd-5316a5924a4d
Both defects recorded in HANDOFF section 7 are fixed by this merge, so
their runTests skip guards are dead weight and were hiding two tests from
CI.

Verified twice rather than assumed:
- On a pristine upstream checkout re-pointed to v4.1.2,
  SubtypeTest > subtypeStaysEnabledOnEdits passes (3 tests, 0 failures).
- In this merged tree, both subtypeStaysEnabledOnEdits and
  immediateRegexExpansionTriggersForSymbolPrefixedRegex pass on the debug
  variant, where the guards do not apply anyway.

Also fixes a latent test-harness bug: setText accepted a requireIdle
parameter and then called handleMessages() without it, so reset()'s
setText("", requireIdle = false) still asserted an idle message queue.
That was harmless until this merge changed JUnit's hash-based method
ordering, after which a different test preceded
"corpus - default mode char-by-char backspace" and left a delayed message
behind, failing it during setup rather than in its own logic.

Debug-variant baseline moves from 4 failures on origin/dev
(tapOnlyCombiningWordDoesNotShowAutospaceIndicatorWhenGestureGateEnabled,
insertLetterIntoWordHangulFails, the regex expansion defect, and the
subtype defect) to 1 - only the long-standing autospace-indicator test
still fails. testOfflineRunTestsUnitTest remains at the documented
4 ParserTest failures that pass on Linux CI.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a88bd77f-d993-44c5-9efc-c7124f0d825e
Section 7 claimed two tests fail on pristine upstream and are guarded with the
runTests skip. Upstream v4.1.2 fixes both, and PR #137 removed the guards, so the
section described a state that no longer exists.

Verified from the raw JUnit XML rather than taken on report:
- pristine v4.1.2 checkout, SubtypeTest = 3 tests / 0 failures,
  subtypeStaysEnabledOnEdits passing
- merged tree, InputLogicTest = 125 tests / 1 failure, with
  immediateRegexExpansionTriggersForSymbolPrefixedRegex passing
- commit e46454e on origin/merge/upstream-v4.1.2 removes exactly those two
  guards; the guards left elsewhere are unrelated (Linux-only ParserTest
  ordering, XLinkTest network, dictionary-dependent cases, emoji-data versioning)

Section 7 is kept as history rather than deleted, because the reusable lesson is
the technique: reproduce a merge failure on a pristine upstream checkout before
blaming your own merge. Neither defect needs reporting upstream now, so section
12 keeps only the emoji accelerated-delete report.

Section 6's known-failure list is split by baseline, since the merge moved it:
4 debug-variant failures on origin/dev versus 1 on the v4.1.2 branch, v4.1.2
having also fixed insertLetterIntoWordHangulFails. testOfflineRunTestsUnitTest is
unchanged at the 4 ParserTest failures that pass on Linux CI.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a6e8f983-22c2-4f2f-aacd-5316a5924a4d
Section 6's closing failure list was inherited from the original handoff and
carried forward here without being measured -- the exact staleness this PR exists
to remove. Both baselines have now been run on the same Windows machine minutes
apart:

  origin/dev 6ac372d   320 tests, 12 failed
  v4.1.2 merge branch    324 tests,  5 failed

The old list was accurate for dev and wrong for the merge branch, so it is now
presented as two measured baselines rather than one blanket claim, with the
merge-branch set (autospace-indicator plus the same four ParserTest failures as
the CI variant) called out as the current expectation.

Attribution is deliberately split. The two section 7 defects and the Hangul case
are safely merge-attributable: they are deterministic logic tests and were
separately confirmed fixed on a pristine v4.1.2 checkout. XLinkTest makes real
network calls and StringUtilsTest depends on bundled emoji-data, so their
run-to-run difference is not by itself evidence the merge fixed them, and the
document says so rather than claiming the win.

Also links the emoji accelerated-delete report (LeanBitLab#423, still
present at upstream f0ff166) so it is not filed twice.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a6e8f983-22c2-4f2f-aacd-5316a5924a4d
Splits the seven resolved failures by how confidently each can be credited to
the merge, rather than lumping them. Both baselines were run on the same machine
minutes apart, which controls for most environment drift but not for a live
network call: XLinkTest > otherLinks hits Codeberg and can flip with no code
change, so it is explicitly not counted as a fix. StringUtilsTest depends on
bundled emoji-data that this merge does update, so it is marked plausible rather
than confirmed.

Also restates the closing rule around the actual hazard: 12 and 5 are both
correct figures for different trees, so neither should be quoted without naming
the tree.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a6e8f983-22c2-4f2f-aacd-5316a5924a4d
Section 11 warned that the six unfinished worktrees held commits on no remote
ref and that the disk was their only copy. All six have since been pushed to
origin as backups, verified: each local tip is in sync with its origin
counterpart and git branch -r --contains now resolves for every one.

Reframes the risk instead of dropping it, because the backup covers commits and
not working trees. LeanType-b7a still has a modified InputLogic.java and
LeanType-swipe a modified app/build.gradle.kts plus an untracked
app/src/swipetest/, none of which any branch push captured; removing either
worktree would still lose them. The other four are clean.

Also drops the now-wrong no-tracking-branch note on
feat/statistical-swipe-decoder, and rewrites the section 12 item so it reads as
triage without a deadline rather than a race against disk failure.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a6e8f983-22c2-4f2f-aacd-5316a5924a4d
Section 6 buried the attribution reasoning in a paragraph and grouped
ParserTest > backgroundType with the asset/locale-sensitive cases rather than
with the other bundled-data ones. Restructures it as three labelled tiers so a
reader can see at a glance which claims are load-bearing:

  attributable            - the two section 7 defects and insertLetterIntoWordHangulFails
  plausible, unconfirmed  - StringUtilsTest x2 and ParserTest > backgroundType
  not attributable        - XLinkTest > otherLinks

Also states the control precisely: same-machine, minutes-apart runs cover
toolchain, locale and machine state, but not whether a remote host was
reachable, which is why the network test is excluded rather than merely
hedged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a6e8f983-22c2-4f2f-aacd-5316a5924a4d
Three false conclusions were drawn from Gradle test output in a single
session, and none of them was flagged by the test run itself:

- Gradle served results from an earlier run because the task was
  UP-TO-DATE, so a "passing" report described code that never executed.
- An ad-hoc XML reader under-counted, reporting 4 failures where there
  were 12. Under-reporting is the dangerous direction: it looks like
  good news, and nothing contradicts it.
- Failures were attributed to a code change when one of them reaches the
  network over HTTP and can flip with no code change at all.

Each is a habit that has to be remembered, so each is now a check that
runs whether or not anyone remembers.

tools/check_test_results.py:
- refuses to report if any result file predates the run (--started-after)
- counts <testcase> elements AND sums the tests=/failures= attributes the
  suites declare, and refuses to report if the two disagree, so an
  under-counting reader cannot pass silently
- diffs failing test NAMES against a checked-in baseline rather than
  comparing pass counts
- quarantines tests marked `net:` in the baseline, which are reported but
  never counted as a regression or as an attributable fix

Exit codes are distinct: 0 clean, 1 new failures, 2 untrustworthy results.

Wired into the Unit tests workflow as the authoritative gate. The Gradle
step becomes continue-on-error so the checker decides; it is strictly
stronger than the exit code it replaces, since a green Gradle run over
stale results now fails. The checker's own tests run in the same job.

tools/tests/test_check_test_results.py covers each failure mode with
fixtures, including a file holding several <testsuite> elements and one
whose declared totals exceed what it lists.

Verified against real output: on the results where the ad-hoc reader said
4 failures, the checker reports 320 tests / 12 failed, matching the
totals the suites declare. Exit codes confirmed 2 / 0 / 1 for stale,
clean, and new-failure runs.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a88bd77f-d993-44c5-9efc-c7124f0d825e
The pycache directories were committed by mistake; ignore them instead.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a88bd77f-d993-44c5-9efc-c7124f0d825e
Device testing did not confirm the paragraph-merge theory this branch was
originally built around, so the unverified behaviour change is parked on
`feat/backspace-paragraph-keyevent` and only the provable fixes remain here.

On a Samsung SM-S936B, on the unfixed build, both Microsoft Word and
Samsung Notes merged paragraphs correctly in every flow tried: typed
Enter then backspace, caret moved into the paragraph by tap, empty
paragraphs, and a held backspace across the boundary. The premise that
those editors ignore deleteSurroundingText across a block boundary is
therefore not established, and routing '\n' deletion through KEYCODE_DEL
is not justified on this evidence.

What remains is independently correct:

- The accelerated (held-backspace) second deletion measured its length
  from codePointBeforeCursor -- the code point read *before* the first
  deletion -- instead of codePointBeforeCursorToDeleteAgain. When the
  second character back is a multi-code-point emoji that picks the wrong
  length and can split it; in the opposite case it can over-delete.
  Present in upstream v4.1.2 as well, reported as
  LeanBitLab#423.

- Two latent test-harness bugs. deleteSurroundingText did not clamp to
  the available text and drove selectionStart negative on an empty
  field. setText accepted a requireIdle parameter and then called
  handleMessages() without it, so reset() could not tolerate a leftover
  delayed message -- harmless until adding tests reshuffled JUnit's
  hash-based ordering, at which point it failed an unrelated test during
  setup rather than in its own logic.

The fake InputConnection can still simulate a block-based editor
(paragraph-scoped reads, cross-paragraph deletes that report success
while doing nothing). The two tests that assert the parked behaviour are
@ignore'd with the device findings recorded, so the hypothesis and the
evidence against it stay together.

Suite: 324 tests, 4 failed -- the documented Windows-only ParserTest
baseline, confirmed with tools/check_test_results.py (#139).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a88bd77f-d993-44c5-9efc-c7124f0d825e
Merge upstream LeanBitLab/LeanType v4.1.2
docs(handoff): correct worktree paths after tree relocation; v0.2.0 is published
chore(tools): gate test-result analysis instead of trusting it
…paragraph-delete

# Conflicts:
#	CHANGELOG.md
…delete

fix(input): correct the accelerated backspace emoji measurement
Version 0.3.0, versionCode 4300 per the fork's offset formula
(4000 + major*1000 + minor*100 + patch*10), keeping versionCode monotonic
above 0.2.0's 4200 so Android upgrades continue to work.

Contents are the LeanBitLab v4.1.2 merge (200 upstream commits from
v4.0.9 onwards: clipboard edit mode, voice recognition language selector,
personal-dictionary learning threshold, physical-keyboard suggestion
shortcuts, localized settings), the two fork regressions that merge would
otherwise have introduced, the accelerated-backspace emoji fix, and the
test-result gate.

Adds fastlane/metadata/android/en-US/changelogs/4300.txt and
docs/releasenote/release_notes_v0.3.0.md, renames [Unreleased] to
[0.3.0], and records the test-result gate which had not been added to the
changelog when it merged.

NOT tagged or published: the build has not had real use on a device yet,
and device smoke is a release gate this project has never actually
executed.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a88bd77f-d993-44c5-9efc-c7124f0d825e
chore(release): prepare LeanTypeDual 0.3.0
Promotes dev to main for the 0.3.0 release. Records both parents but adopts
dev's tree verbatim, per the documented promotion recipe: main carries release
commits that were never merged back into dev, so a plain merge cannot be
created cleanly.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a88bd77f-d993-44c5-9efc-c7124f0d825e
@AsafMah
AsafMah merged commit b90d79d into main Aug 20, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants