Skip to content

Merge open PRs #27–#41 with review follow-ups - #43

Merged
HarlenPostill merged 42 commits into
mainfrom
integrate/open-prs
Nov 9, 2026
Merged

HarlenPostill merged 42 commits into
mainfrom
integrate/open-prs

Conversation

@HarlenPostill

Copy link
Copy Markdown
Owner

Merges nine open contributor PRs into main, followed by one review follow-up commit per PR.

Merged

PR What Follow-up
#27 CI pipeline, Spotless/ktlint, lint error fixes CodeQL action v3 → v4 (v3 is deprecated in Dec 2026). Only PR runs are cancelled when superseded. Full history in the build job. IntentCompat for the install intent. -PspotlessRatchet override.
#28 Version from git tag / commit count, DEV pill --always, so a tagless or shallow build reads 0.0.0-g<sha> instead of claiming 1.2.3. Falls back when there is no git binary.
#29 Shared app row, Sidekit kept out of Sideport Keep the search field's onDone. Clip before the clickable so the press highlight stays rounded.
#30 Compact layout + clean screenshot baselines — (conflict with #28's formatting resolved in the merge)
#33 Fill the screen instead of a fixed 480 × 640 The canvas stays 480 wide and only grows in height. Wide windows get dark side bars instead of stretched rows.
#41 D-pad can leave the action bar; no focus traps Phonebook digits typed while the bar is focused now reach the search (KeysFromActionBar). The map's focus ring shows only in keyboard mode. The parking spot is hidden from TalkBack.
#39 Title doesn't bounce or draw over the clock The backstop SideEffect reads state.top at apply time, so it no longer undoes the same-frame sync.
#34 Town name for Sideweather's current location The forecast loop is keyed on place.key: the rename used to blank the card. Real 5 s geocode timeout on API 31–32.
#35 Dictation from the keypad typing bar dictation = false for 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. :sidekit declares RECORD_AUDIO and the recogniser query.

Not merged

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).
  • The test run re-recorded 34 screenshots. All are pixel-identical to the committed ones (byte-only re-encoding), so none are committed.
  • spotlessCheck fails on this branch because the merged PRs touch files that predate the ktlint gate. On main the ratchet compares against itself, so CI passes. Future PRs format the files they touch.
  • Not run on a physical device.

🤖 Generated with Claude Code

yeradis and others added 30 commits November 8, 2026 15:49
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
HarlenPostill and others added 12 commits November 9, 2026 11:59
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>
@HarlenPostill
HarlenPostill merged commit 3debe06 into main Nov 9, 2026
@HarlenPostill
HarlenPostill deleted the integrate/open-prs branch November 9, 2026 01:10
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.

3 participants