Repository navigation
Merge open PRs #27–#41 with review follow-ups - #43
Merged
Merged
Conversation
While typing on the keypad, the bar gains a mic between the input mode and Done. It listens for one phrase with the platform speech recognizer and puts it at the cursor; tapping again stops. The microphone permission is asked on first use, and dictation ends when the field loses focus.
With the dictation mic, app search's bar (mode, mic, Done, clear) had no room for Done's label and wrapped it. When a screen adds its own typing button, Done now shows only its check icon.
The canvas kept its 480 x 640 Figma scale but was letterboxed on any other aspect ratio. It now fills the display: the shorter axis stays at the design size and the other grows, so the Sidephone is unchanged.
A located place was only ever called "Current location". Its town is
now looked up with the platform geocoder and added once known
("Current location · Begues"), including for a fix saved earlier. The
hourly and 5-day forecasts caption which place they are for.
In app search the bar is mode, mic, Done and the square clear button. The mic and Done were squares too, leaving the mode button whatever width was left. They now share the width beside clear evenly, so the bar reads as four even buttons like the dock, and clear stays square.
Overriding ComponentActivity.dispatchKeyEvent is the only way to see a keypad event before Compose or the system consumes it. Lint already accepts the override with a suppression; without it every build fails lintDebug with three RestrictedApi errors.
Intent.getParcelableExtra(Intent, Class) is API 33, but minSdk is 31, so lint rejected the call as NewApi and the build failed lintDebug. Branch on the SDK level and keep the deprecated overload for older devices, which is the only version that exists there.
Both files predate the formatting gate and were never run through ktlint, so touching them would fail spotlessCheck. This is what spotlessApply produces: import order, brace style on single-line if/else, and one blank line. No behaviour changes.
Run the checks that catch Sidekit's regressions on every pull request: - validate the Gradle wrapper - assemble debug and the R8 release build - run the JVM and Robolectric unit tests for both modules - run Android Lint and upload its SARIF to code scanning - check ktlint formatting of files changed since main - scan for committed secrets, review new dependencies, and run CodeQL Formatting uses Spotless with ktlint, ratcheted against origin/main so only touched files must be clean. .editorconfig holds the shared rules. The dependency-graph workflow keeps the graph that dependency review reads current.
Spotless does not load .editorconfig on its own, so ktlint was running with its defaults: a 140-column wrap and no Composable naming exemption. Every screen would have failed the formatting gate, and the rules in .editorconfig (code style, max_line_length off, wildcard imports) were only serving editors. Read the ktlint keys from .editorconfig through a config-cache aware provider and pass them as editorConfigOverride, keeping one source of truth. Also exempt @composable functions from function-naming, since screens are PascalCase by convention. Verified: spotlessCheck passes on this branch, and on a branch that touches screens (previously 7 function-naming errors).
`android-actions/setup-android` splits its `packages` input on a space, not on newlines. The YAML block scalar therefore reached sdkmanager as a single package name containing line breaks, and every job failed in setup with "Warning: Failed to find package 'platform-tools platforms;android-37.0 build-tools;37.0.0'". Observed on the fork's first run: build, unit tests, lint, formatting and CodeQL all stopped in the composite action before touching Gradle.
The version was two hand-edited literals in defaultConfig, so every dev build since 1.1 called itself 1.1-dev no matter what it contained; `git describe` on the same tree said v1.1-56. Releases are cut on release/* branches that bump the literals, and main never sees those commits, so main cannot stay in step by construction. versionName now comes from `git describe --tags --match 'v*' --dirty` (a tagged commit is exactly 1.2.3, anything later is 1.2.3-3-g<sha>, uncommitted work adds -dirty) and versionCode counts commits, so it only grows and installs over older builds. A checkout without git falls back to 1.2.3/6 and both can be overridden with -PsidekitVersionName / -PsidekitVersionCode.
The dev build installed alongside the stable Sidekit was only told apart
by its launcher name; inside the app it looked exactly the same. It now
carries a small DEV pill at the right of the launcher header, drawn with
the Sidekit tokens (tiny type, medium weight, control corner, surface
colour) and centred on the title's line box so it follows the clock in
compact mode. About names the build ("SideKit Dev" from app_name, plus
its package) instead of the hardcoded stable name.
Both come from resources: build_label is empty and is_dev_build false in
src/main, and only the dev build type overrides them. Stable frames are
therefore untouched, and the pill stays out of focus order and the D-pad
tests; a screen reader hears "Development build" rather than the letters.
ktlint's ratchet only checks files a branch changes, so these files were never run through the formatter. This is the output of ./gradlew spotlessApply with the repository's .editorconfig: no behaviour changes.
The apps list and Sideport's Add screen each had their own row and search field, so the two drifted, and only Add excluded the running package: the apps list offered Sidekit itself, and a sibling build (the dev app) could be added to Sideport, which is recursion. AppRow and AppSearchField are now the shared pieces. SidekitBuild decides from the package id what counts as a Sidekit build, PortedApps refuses such entries and drops ones saved by an earlier version, and sideportCandidates keeps the Add screen's filtering in one place, covered by SideportAddTest.
ktlint's ratchet only checks files a branch changes, so these files were never run through the formatter. This is the output of ./gradlew spotlessApply with the repository's .editorconfig: no behaviour changes.
The title swap used the button label spring, which is under-damped (0.68) so arrows overshoot. The new title rose its 28dp, shot about 1.5dp above its fixed position and fell back, which read as the title bar bumping. The title now uses a critically damped spring with the same stiffness, so it rises into place and stops there.
The launcher draws its own header, but the shared title only heard about the new screen a frame later and then faded out in place, so the previous screen's title showed over the clock. The title host is now told in the same frame, like the action bar, and drops the old title at once when the new screen has no shared title.
Screens run under the action bar, so focus search never saw them above it and Up from a bar button went nowhere. The bar now sends Up into the screen, which restores the item focus left from (lists restore their row too), or enters at the bottom item when the bar was reached by touch.
Clock, the calendar editor and Sideticks made the whole screen focusable so Done could move focus off a text field. The D-pad got stuck on that invisible full-screen target, and Up from the action bar returned to it. The scaffold now offers a focus parking spot instead: an empty point at the top of the body that requestFocus can use but the D-pad never lands on.
The map took every arrow key to pan, so focus could never leave it and Up from the action bar ended on a map that only scrolled. Arrows now move focus past the map like any row; OK on the map starts panning, OK or Back stops, and the map shows the focus ring and says which mode it is in.
The contacts list took focus as one block so keypad digits would reach it, which left the D-pad unable to move to a contact or down to Calls and Dial, and Up from the bar went back to that invisible block. Digits are now caught by the screen, with focus starting on the scaffold's parking spot, so typing a number still works straight away and from any contact.
The header reserved 123 px of the 640 px display before any content: 10 dp canvas padding, 30 dp title inset, 12 dp of Inter's ascent above the cap and a 103 dp header slot. The action bar added another 132. Settings -> Compact layout installs SidekitSpacing.Compact, which trims the chrome without touching the type scale: the header title sits 14 dp above its line box (header 75 instead of 103, so the body starts 28 dp higher) and the action bar is 100 instead of 112. The spacing is built in MainActivity from a preference observer, so toggling the row re-lays out every screen immediately; headerPaddingTop becomes its own token (default 30) so the standard layout is byte-for-byte unchanged. (cherry picked from commit 4731755)
The baselines were last recorded on 5 November, before #8 (action labels wrap), #16 (keypad navigation) and #17 (system settings page) changed what those screens draw, so a plain `./gradlew test` on main re-recorded 23 files into the working tree and left them looking like local edits. Re-recorded; the new bytes are reproducible on a second machine and a second run. The calendar screen highlights today's date, so its screenshot went stale a day after it was recorded: it now renders a fixed 5 November 2026 grid, matching the committed image exactly, instead of re-dirtying the tree every midnight (and marking every dev build `-dirty`). (cherry picked from commit 133d812)
`keypad_mapping_settings` and `settings_system_home` are captured after `scrollTo`, and the compact work adds a "Compact layout" row to that screen. The refresh commit was recorded before that row existed, so both images were re-recorded here against the merged tree; the other 23 refreshed images are unchanged by the compact layout. (cherry picked from commit 63171a3)
Add a GitHub Actions CI pipeline, and fix the lint errors it exposes
Derive versionName from the git tag and versionCode from the commit count, and label SideKit Dev builds
Share one app row between the Apps screen and Sideport's Add screen, and keep every Sidekit build out of Sideport
Add a compact layout, and make the screenshot baselines clean and stable
Fill the whole screen instead of a fixed 480 x 640 canvas
Let the D-pad leave the action bar, and stop screens trapping focus
Stop the screen title bouncing and drawing over the launcher clock
Show the town for Sideweather's current location
Dictate into text fields from the keypad typing bar
- github/codeql-action v3 is deprecated in December 2026; move to v4. - Only cancel superseded PR runs, so every push to main keeps a status. - The build job fetches full history, since versionCode now counts commits. - Read the install confirmation with IntentCompat, which avoids the buggy API 33 typed overload. - Let -PspotlessRatchet point the format ratchet at another ref (forks). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A shallow clone has no reachable release tag, so describe printed nothing and the build claimed to be 1.2.3. With --always it now reads 0.0.0-g<sha>. A machine without a git binary makes the exec throw rather than exit non-zero, so fall back to the release values in that case too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shared search field dropped onDone, which switched the soft keyboard's action from Done to Default. AppRow's clickable sat outside SidekitTile's clip, so the press highlight lost its rounded corners; clip first and mark it a button, as SidekitItem does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fit density pins whichever axis limits it, so a window wider than 3:4 (landscape, or a large screen that ignores the portrait lock on Android 16) stretched rows and the action bar sideways to ~1024 wide. The canvas is now always 480 wide and grows only in height, with dark side bars when the window is wider. Comments that still described a fixed 480 x 640 canvas are updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Now that focus can reach the action bar, keypad digits typed there were lost: the bar is drawn by the canvas, outside the scaffold that catches them. KeysFromActionBar lets a screen hear keys while the bar is focused, and keypadDigits registers there when it uses a parking spot. - The map takes focus as it opens, even by touch; only draw its focus ring in keyboard input mode. - Hide the focus parking spot from accessibility services. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The backstop SideEffect synced to the 'top' captured during composition. When HeaderHost recomposed in the frame a new screen attached, it ran after that screen's onHeaderChange and synced back to the old screen, so the title trailed the body again. Read state.top when the effect runs instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Key the forecast loop on the place's key, not the Place itself: adding the town renamed it, which restarted the loop and blanked the card. - On API 31-32 the blocking geocoder ran in withContext(IO), which waits out the call even after withTimeoutOrNull cancels it, so the 5 s cap never applied. Run it detached and resume a cancellable continuation instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- SidekitTextField(dictation = false) drops the mic. Used for number and fixed-format fields (minutes, HH:mm, dates, steps, mail ports), the Vault note (private text shouldn't go to a speech service), and Translate, which has its own mic in the chosen source language. - Hide the mic when no recogniser is installed and in numeric input mode. - Finish a T9 word typed while listening before inserting the phrase, and don't start dictating if focus left the field during the permission prompt. - Distinct messages for start failures, a busy recogniser and unsupported languages. - :sidekit declares RECORD_AUDIO and the RecognitionService query itself. Co-Authored-By: Claude Opus 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.
Merges nine open contributor PRs into
main, followed by one review follow-up commit per PR.Merged
IntentCompatfor the install intent.-PspotlessRatchetoverride.--always, so a tagless or shallow build reads0.0.0-g<sha>instead of claiming 1.2.3. Falls back when there is nogitbinary.onDone. Clip before the clickable so the press highlight stays rounded.KeysFromActionBar). The map's focus ring shows only in keyboard mode. The parking spot is hidden from TalkBack.SideEffectreadsstate.topat apply time, so it no longer undoes the same-frame sync.place.key: the rename used to blank the card. Real 5 s geocode timeout on API 31–32.dictation = falsefor number/format fields, the Vault note and Translate. The mic is hidden without a recogniser or in numeric mode. The T9 word is finished before inserting. Better error messages.:sidekitdeclaresRECORD_AUDIOand the recogniser query.Not merged
SettingsScreenchanges.Verification (merged tree)
./gradlew :sidekit:testDebugUnitTest :app:testDebugUnitTest: 34 + 391 tests, 0 failures../gradlew :app:assembleDebug :app:assembleRelease: success../gradlew lintDebug: 0 errors (app 86 warnings, sidekit 3).spotlessCheckfails on this branch because the merged PRs touch files that predate the ktlint gate. Onmainthe ratchet compares against itself, so CI passes. Future PRs format the files they touch.🤖 Generated with Claude Code