Release 1.0.0: nine-key reading rail, swipe clear/undo, release pipeline - #114
Merged
Merged
Conversation
Local UI iteration on top of the v1 visual language: updated design tokens, key/panel renderers, icon set (new delete/download/floating/pin/return/shield/ text-cursor/undo vectors), zh resources, reference emoji assets and the reference capture/review scripts. Known state: :app:testDebugUnitTest has 12 failing tests (IconLintTest, KeyboardLayoutMetricsTest, NoDecorativeGlyphTest, NoRawColorTest, NoRawTextSizeTest, ProductionKeyPolicyTest, TokenDriftTest) that guard the design-token/metrics contracts; these need follow-up. output/ (screenshot artifacts) is intentionally not committed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…swipe-clear
Nine-key left rail
- While composing, the rail lists the real syllables that can start the open
digits (zhong/xiong/xin/yin), longest first, led by the previewed syllable.
Tapping one fixes it and the rail moves on to the next position
(rime-t9-shiyin / Trime style). Backspace unlocks the last fixed syllable
and the segment key's boundary instead of deleting letters.
- Fixed syllables reach Rime as letters (luna_pinyin accepts letters and 2-9
in one input) so zhong/xiong stay distinct and candidates match the choice.
- The pre-edit pinyin follows the word Rime ranks first
(9694264244326 -> wo'xiang'chi'fan / 我想吃饭) instead of the local
decoder's independent guess.
- The fixed prefix is the one the view recorded, not whatever follows a space:
decoder-inserted spaces between guessed syllables were being treated as
user-fixed boundaries. Edit/delete logic is now divider-aware.
- Side-rail symbols are centred by their ink bounds; full-width punctuation
sat in the corner of its em box.
Enter / space
- Space commits the first candidate; Enter ("确定") commits the typed pinyin as
text, matching Rime, fcitx and Gboard Pinyin. They were identical before.
Backspace swipe-up clear
- Arm distance lowered from 56dp to 32dp (CLEAR_ARM_DP); ordinary flicks fell
through to a single delete. The debug clear-swipe hook moved only 48dp and
never armed it; it now uses the shared constant.
Tests: core_regression.sh gains enter-keeps-pinyin cases (9/9 on emulator);
unit tests added for locked-code, syllable options and word readings. The 12
design-contract unit failures from the previous commit are unchanged.
Known open: choosing a partial candidate still drops the remaining input.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Choosing a word that spells only the start of the input (你 for 你好吗, in 26- or 9-key) committed it and dropped the remaining input; the native whole-composition commit also taught the user dictionary a phrase the user never chose (probe: input 64426, pick 你, librime committed 你好). - JNI: nativeCandidateEnds() returns each candidate's input extent (Candidate::end()). RimeCandidateEntry / NativeCandidateReference carry it as `consumed`. - Service: a candidate with consumed < input length commits only its text and republishes the leftover (nine-key keeps any fixed syllables fixed). Space and tap share this path. No native learning for a partial pick. - The commit and the new composing span run in one batch edit. Without it the editor reports a half-way selection state and onUpdateSelection discards the new composition as "user moved the cursor". core_regression.sh: +033..036 (9-key/26-key partial pick, then space). 13/13 on emulator. Unit: the same 12 design-contract failures as before. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ation row Swipe-up clear did nothing in editors that are not an EditText (Compose, custom canvas, web: chat apps such as the user's own). clearAllText() only accepted editors with a select-all action or a complete ExtractedText and otherwise failed silently, while the gesture itself fired correctly (log: clearArmed=true, finish commit=true). It now falls back to before/after-cursor text: capture everything, delete until the editor reports empty (safe for editors that cap their answers), keep an undo snapshot. An answer as long as the request is treated as a possible window and is never deleted. A new debug CustomEditorTestActivity reproduces such an editor; old build left the text untouched, new build clears and restores it. Gesture UI is one component now. Clear and restore were a red pill, a white card and a tiny label inside the key; the pill was flush against the screen edge. Now a single bubble (same size/position/animation) beside the key where the thumb does not cover it: dark "上滑清空" -> red "松手清空" for clear, dark "下滑撤回" -> accent "松手撤回" for restore. After a clear the toolbar shows "已清空 [撤销]" for the five-second undo window. The restore hint is driven by the gateway's real undo snapshot instead of a flag that never expired. The release coordinates count as a last move (quick flicks cross the threshold on the UP itself). The arm distance is 32dp (was 56dp). Association row (after committing a word) matches the design: "‹ words ∨". The hide chevron kept layout weight 1 and shared the row with the words, so the chevron floated mid-row and the words were pushed right. Accent: pre-edit text, tool icons and setup accents used a hard-coded #006AB1 in light themes and ignored the chosen accent, so a custom accent put two unrelated blues side by side. selectedText() now derives a readable shade from the active accent. Known: 12 design-contract unit tests still fail (unchanged). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…xed syllables stay fixed Left rail (composing): a list of readings, one continuous panel with flat items and an accent pill on the selected one, as in the design draft. Short input lists whole readings (ni'hao, mi'gan, ni'gao ...); once a whole reading no longer fits the rail it lists first syllables (zhong, xiong) and fixing one moves the list to the next position (Baidu / rime-t9-shiyin). A tap fixes exactly what the item shows. Readings come from a beam search over real syllables that rewards word-forming paths and drops digit-grid artefacts (lone a/o/e, vowel-less ng/m); choices that leave unreadable digits are never offered. A syllable the user fixed stays fixed when more digits follow (it is sealed with a boundary instead of being re-decoded), and backspace unlocks the last fixed syllable or the boundary the segment key made. Pre-edit boundaries are apostrophes everywhere (ni'hao), not spaces. Ranking: words the typed digits spell exactly now come before longer words that merely start with them. Typing xian (9426) led with 自从 (zi'cong, a prediction) while the pre-edit said xian. Rime's order is kept inside each group and nothing is dropped. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…cisions); changelog Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…p a11y and clipboard targets Visual refresh (design tokens, vector marks, typography roles, setup page) and the fixes it needed to land green: - layout: widen the reference-scale change epsilon (0.001 -> 0.01) and post a relayout after a rebuild in onSizeChanged. Configuration.screenWidthDp is a whole number while the measured width is not, so the same window looked like a new geometry, every key row was rebuilt mid-layout and the keyboard stayed blank on first show. - setup: hide each card's decoration from accessibility in code. The layout refresh dropped the importantForAccessibility attributes and the runtime status pill never had them, so a screen reader landed on every child. - clipboard: retention / confirm actions keep a 48dp touch target and are painted 36dp inside it (as the settings segmented controls, 34dp in 48dp). - tests: IconLint, TokenDrift, KeyboardLayoutMetrics and ProductionKeyPolicy follow the redesigned contracts; instrumented tests follow the current UI (skin sub-page, appearance picker, voice language segments, preview popup, scaled cursor-drag distances, arrow glyph cursor keys, setup cards, nine-key reading list, expanded candidate overlay). isKeyPopupShown() and scaledPx() are the new test hooks: the key preview is a permanent child that only toggles visibility, and gesture thresholds scale with the reference size. - docs: changelog, SOP swipe-clear criteria; ignore generated output/. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- VERSION (MAJOR.MINOR.PATCH) is read by app/build.gradle.kts; versionCode is derived as major*10000 + minor*100 + patch, so it cannot drift and every release is strictly greater than the one before (1.0.0 -> 10000; development builds were at 4, so in-place upgrades keep working). - scripts/release_check.py applies the same rules to the repository (VERSION is plain semver, CHANGELOG.md has the dated section for it right below [Unreleased], tag == v<VERSION>) and to a built APK (package, versionName, versionCode via aapt2), with unit tests. - CHANGELOG.md is reorganised into Keep a Changelog form with a user-facing 1.0.0 section (it doubles as the GitHub Release notes). The old development-period entries are kept verbatim in docs/CHANGELOG_PRE_1.0.md. - .gitignore: Python caches and signing material (*.jks, *.keystore, *.p12). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…l CI Release workflow (scripts/release_build.sh does the work, so it can also be run locally): - tags must be vX.Y.Z, equal VERSION, sit on main and have passed Build and verify plus both compatibility jobs; the changelog section becomes the release notes. - verifies the APK: signed by a non-debug key, only arm64-v8a, package and version match VERSION, certificate matches docs/release-cert.sha256 once it is recorded. - the job that holds the signing key cannot write to the repository; a second job (contents: write, no key) creates a draft release, checks all three assets are attached, then publishes. - the same pipeline runs with a throwaway key on manual runs and on pull requests that touch the pipeline (nothing is published), so the first real release is not the first run. lintRelease, which PR CI never ran, is part of it. - scripts/setup_release_signing.sh creates the key and the four Actions secrets without printing the passwords, and refuses to overwrite an existing key. Android CI: version/changelog checks run before the toolchain is installed, the built APK's identity is verified, and the API 29/31 jobs now fail when the instrumentation report does not end in "OK (N tests)" (am instrument exits 0 on failures). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- docs/RELEASE.md: versioning rules, changelog rules, signing key custody, release steps, rehearsal, rollback. - docs/REPOSITORY.md and scripts/apply_repo_settings.sh: squash-only merges with the PR title/body as the commit message, protected main (PR + Build and verify, linear history, no force push), immutable v* tags, security features, so the settings are reviewable and reproducible. - Dependabot (Actions and Gradle, weekly, grouped), CODEOWNERS, PR template changelog/version checklist, SECURITY.md supported versions and private reporting, CONTRIBUTING branch/merge/version rules, README download and verification instructions, CI gate documentation. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The release workflow writes three secrets into GITHUB_ENV as NAME=value lines, so a value containing a newline could inject further variables; refuse it. release_build.sh needs mapfile and empty-array expansion under set -u (bash 4.4+); fail with a clear message instead of an obscure error on macOS bash 3.2. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This was referenced Oct 2, 2026
Slacker-LLC
added a commit
that referenced
this pull request
Oct 2, 2026
… resilience (#117) Typing in every environment, and surviving the ones that go wrong: the second half of 1.0.0 (the first half is #114, already on main). It supersedes #116 (same commits rebased onto main) and records the release signing certificate fingerprint, so the first release is checked against the key in the repository secrets. ## Found by running the keyboard through real environments - **Landscape went fullscreen.** The framework default hides the app's own editor behind an unthemed copy of the field. `onEvaluateFullscreenMode` is now false: the keyboard is a bottom panel in every orientation. - **Rows were clipped in landscape, on tablets and foldables.** Content-box padding and row widths were applied from `onSizeChanged`, to views laid out later in the same pass; their `requestLayout()` calls are dropped (each view clears its force-layout flag when it finishes laying out), so rows kept the full window width inside the clamped box. Geometry is now applied in `onMeasure`, before children are measured. - **200% system font broke key labels** ("m" became an ellipsis, "中/英" lost a glyph). Key labels follow the system font up to 1.3x; function labels keep autosizing. - **TYPE_NULL editors (terminals, games, remote desktops)**: Backspace did nothing, because their dummy `BaseInputConnection` answers true to `deleteSurroundingText` and removes nothing. They now start in English, get each letter as a real key event, and delete with key events. Date/time fields start on digits. - **No physical keyboard support**: letters reached the app as Latin text. In 26-key Chinese mode they now compose pinyin (space, 1-9, Enter, Esc, `'`, full-width punctuation; shortcuts and capitals go to the app). ## Crashes, freezes, conflicts - A failing typing handler is contained (recorded without typed text, composition dropped) instead of killing the keyboard. - Crash history from Java crashes, native crashes and ANRs (Android 11+ exit reasons); three in ten minutes start safe mode (no librime, no voice preload). The About screen can copy the diagnostics or leave safe mode. - librime start marker with escalation: clear compiled data, set the user dictionary aside, skip native. A start killed by the user or system is not counted. - Media volume muted for voice input is restored after an unclean exit and by a two-minute watchdog. - Huge commits are chunked below the Binder limit; the lexicon and nine-key decoder are built off the main thread; Backspace no longer makes three synchronous round trips into the app when the editor reported a collapsed cursor. - Nine-key left rail: one character's pinyin per item, as Baidu and rime-t9-shiyin do it. ## Verification | Check | Result | |---|---| | Unit tests, lint, assemble | pass (plus 9 crash-resilience, 12 physical-keyboard, 6 editor/gateway tests) | | Instrumented suite, API 36 emulator | 102 tests, 0 failures, 2 skipped (`assumeTrue` on API level) | | `scripts/display_matrix_regression.py` (11 environments) | 11/11, fails on the unfixed build | | `scripts/core_regression.sh` | 19/19, including 037 (one pinyin per character), 038 (injected failure), 040-043 (physical keyboard) | | New regression tests fail without their fixes | checked for the layout and font tests | `docs/COMPATIBILITY.md` lists every environment, how it is handled and verified, and what is not covered (nine-key with a physical keyboard, candidates when the keyboard panel is hidden, OEM differences on real phones). CI now also runs the instrumented suite on API 26 and 34 (informational; the release gate still requires 29 and 31), so a failure there is a finding rather than a regression. 🤖 Generated with [Claude Code](https://claude.ai/code) --------- Co-authored-by: limuzi013 <128580527+limuzi013@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is the 1.0.0 release PR: the nine-key and gesture work, the visual refresh brought back in sync with its tests, and a versioned, rehearsed release pipeline with documented repository rules. Merging it does not publish anything; publishing is a tag (see "Releasing" below).
Version management
VERSION(plainMAJOR.MINOR.PATCH) is the single source. Gradle reads it:versionName = VERSION,versionCode = MAJOR*10000 + MINOR*100 + PATCH(1.0.0 -> 10000). Development builds were1.0.3/4; an in-place upgrade to1.0.0/10000was checked on an emulator.scripts/release_check.py(with unit tests) enforces the same rules in CI: VERSION is valid,CHANGELOG.mdhas the dated section for it right below[Unreleased], tag ==v<VERSION>, and the built APK's package / versionName / versionCode match (viaaapt2). A version bump and its changelog section can only land together.CHANGELOG.mdis now Keep a Changelog with a user-facing 1.0.0 section, which doubles as the GitHub Release notes. The old development entries are kept verbatim indocs/CHANGELOG_PRE_1.0.md.Release pipeline (
.github/workflows/release.yml,scripts/release_build.sh)vX.Y.Ztags release. The tag must equal VERSION, sit onmain, and its commit must have passed Build and verify plus both compatibility jobs. The release notes come from the changelog.arm64-v8a, identity matches VERSION, and the signing certificate matchesdocs/release-cert.sha256once that is recorded (it isunsetuntil the first release).contents: writebut never sees the key. It creates a draft, checks that all three assets are attached, then publishes.lintRelease, which PR CI never ran. A local rehearsal produced a 304 MB signed arm64 APK that passes every check above.scripts/setup_release_signing.shcreates the key and the four Actions secrets without printing the passwords, and refuses to overwrite an existing key.OK (N tests)(am instrumentexits 0 on failures, so they could never fail before). Both jobs passedOK (99 tests)on the previous head.Repository rules (
docs/REPOSITORY.md, applied byscripts/apply_repo_settings.sh)Already applied to the repository (reviewable and re-appliable from the script):
main: PR required,Build and verifyrequired, linear history, no force push or deletion, conversations must be resolved. Admins are not locked out; no approval count while there is one maintainer.v*tags: only admins can create them, nobody else can move or delete them (rulesetrelease-tags).Product changes
docs/NINE_KEY_REFERENCE.md.nativeCandidateEnds()JNI call); no phrase the user never chose is learned.ExtractedText(surrounding-text fallback that never deletes a possibly truncated window); one shared hint bubble; arm distance 56dp -> 32dp; "已清空 · 撤销" for 5 s.screenWidthDpdifference exceeded a 0.001 epsilon and rebuilt every key row mid-layout), setup cards exposing every child to screen readers, clipboard and settings controls below a 48dp touch target (painted size unchanged), association row layout, accent-derived colours.Verification
:app:testDebugUnitTest :app:lintDebug :app:assembleDebug :app:assembleDebugAndroidTest(the CI command), 1.0.0python3 -m unittest discover -s scriptsandrelease_check.py check/apkllc.slacker.openime 1.0.0 (10000)assumeTrueon API level)OK (99 tests)on bothscripts/core_regression.shon the 1.0.0 buildlintRelease,assembleRelease, all verifications)Not covered: a physical phone (none connected), and the real release job, which needs the signing secrets (not set yet).
Releasing (after this merges)
bash scripts/setup_release_signing.shon a trusted machine, then back up~/.openime-release/(a lost key means every user has to reinstall).git tag -a v1.0.0 -m "openIME 1.0.0" && git push origin v1.0.0on the merge commit. The workflow builds, verifies and publishes.docs/release-cert.sha256.Open decision for the owner: the project has no main
LICENSEyet (docs/LICENSING.md), and it bundles GPL-3.0-only Rime Ice dictionaries.🤖 Generated with Claude Code