Skip to content

Release 1.9.1 - #115

Closed
projectdelta6 wants to merge 15 commits into
mainfrom
release/1.9.1
Closed

projectdelta6 wants to merge 15 commits into
mainfrom
release/1.9.1

Conversation

@projectdelta6

Copy link
Copy Markdown
Collaborator

Release branch for 1.9.1, the first Central release since 1.9.0.

./scripts/publish.sh --dry-run is green on c4a7e5c — clean, test koverVerify, :app:verifyConsumerKeepRules, publishToMavenLocal, and verifyPublishedVariantResolution --refresh-dependencies all pass.

Dependency bumps

  • AGP 9.3.2 → 9.4.0
  • Kotlin 2.4.10 → 2.4.20
  • androidx Navigation 3 1.2.0-alpha07 → 1.2.0-rc01 (two hops)
  • Compose BOM 2026.08.00 → 2026.09.00
  • Room 2.8.4 → 2.8.5

Local-publish tooling

Testing a change before a release had one path — publish.sh --local — which signs, so it waited on a 1Password unlock for a signature nothing local ever verifies (Gradle does not check signatures on resolve). And there was no way to undo an install short of deleting from ~/.m2 by hand.

  • scripts/publish-local.sh — unsigned install, no credentials, takes module names to publish a subset while iterating. --signed delegates to publish.sh --local.
  • scripts/clear-local-publish.sh — removes the install, optionally one version. Lists what it will delete and confirms first; only ever touches the group directory derived from PUBLISH_GROUP, so a fork clears its own coordinates.
  • Shared Android Studio run configurations under .run/ for both.
  • CONTRIBUTING.md gains the consuming-project half that was missing entirely: where mavenLocal() goes, why the first resolve needs --refresh-dependencies in both directions, and the escape from version shadowing — a TOOLBOX_VERSION that cannot exist on Central.

Nav3Navigation docs

  • Stop recommending BackHandler for ordinary system back: it duplicates the built-in path and intercepts ahead of NavDisplay, so predictivePopTransitionSpec never scrubs. Points at NavigationBackHandler for genuine interception, and documents the API 33–35 enableOnBackInvokedCallback opt-in that nothing in the repo mentioned.
  • New: how to deliver a pop result on system back. popWithResult is child-initiated, so system back routes to a plain pop() and the result is silently dropped — the back arrow keeps working while the gesture stops signalling. Documents the host-onBack dispatch, and records why this stays app-side rather than becoming a host default. Raised by a consumer hitting it during the 1.9.1 adoption.

Release notes

  • Central publishing limits and what they mean for cadence (batch patch releases; do not split modules).
  • CI runs tests when a draft PR is marked ready for review.
  • Version catalog regrouped by publication blast radius.

Before merging

Releases are immutable — once 1.9.1 uploads it can never be corrected, only superseded. And one toolbox release is ~508 files against a 1,000/month Central cap, so a second 1.9.x this calendar month would fit and a third would not.

Publishing is manual: merging this does not release anything. ./scripts/publish.sh from main uploads and then tags.

🤖 Generated with Claude Code

projectdelta6 and others added 15 commits September 9, 2026 12:08
Central enforces per-month file count, release size and release count from
1 October 2026. One toolbox release is 508 files — about half the monthly file
allowance — because 26 modules each carry a full jar/sources/javadoc/pom/module
set with signatures and checksums. That, not release count, is our binding
constraint: a second release in the same calendar month barely fits and a third
cannot.

Two things worth writing down before someone reaches the wrong conclusion under
time pressure. Same-month point releases need batching, since an August-style
1.8.0 -> 1.8.3 flurry would be over twice the allowance. And splitting the
toolbox into separately-published repositories to shrink our footprint would do
the opposite: Central scores a multi-module bundle as one release event, so 26
repositories would be 26 events per version, past the limit of 7 immediately.

Cross-referenced from "Why one version for all modules", which recommended
splitting a module out without noting that cost.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Draft PRs cannot be merged, so the tests-and-coverage job now skips them rather
than spending runner minutes. On its own that guard would silently strand any PR
opened as a draft: `ready_for_review` is not in the default event type set, so
marking such a PR ready fired no event at all and CI would never report on it.
Adding the type alongside the guard is what makes the pairing safe.

The `event_name` check keeps pushes to main running — on push there is no
`event.pull_request`, and a null never equals false in GitHub expressions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sonatype granted uk.co.appoly.droid an OSS exemption, so the commercial-nature
classification — which applies regardless of publishing volume and would
otherwise require Publisher Pro — does not apply to us.

The same response declined to raise the file-count ceiling in substance. It was
framed as "enhanced monthly publishing limits" of 7 releases / 80 MB / 1000
files, which is exactly what the Usage Center already showed before the request,
sized to a publishing history of a single release. So the earlier note's numbers
came from the published defaults rather than our real limits: the file ceiling is
1,000, not ~1,167, and the one-release-per-calendar-month conclusion stands.

Recorded because that reply reads as a win on both counts and is easy to
mistake for headroom we do not have.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A patch-level move inside the same 2.4 language version, so the metadata version
is unchanged and no consumer that resolves 1.9.0 today is affected. The
consumer-visible kotlin-stdlib floor moves to 2.4.20, which the dependency graph
was already forcing up from 2.1.21 and 2.2.21 transitives.

Nothing else needed pinning: the Compose compiler and serialization plugins are
version.ref'd to `kotlin` and moved in lockstep, and KSP2's versioning is
decoupled from Kotlin, so the 2.3.11 pin still applies across the ten modules
that use it plus Room in :app.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Version-only for production code. Every breaking change in beta01 and alpha07 is
in the deep-link API — DeepLinkRequest.extras becoming RequestExtras, the factory
function removals, DeepLinkMatcher gaining a type parameter — and this module
imports no DeepLink* symbol.

Two beta01 items did reach us. The new lint requiring Scene implementations to be
data classes or implement equals/hashCode is already satisfied: TabsScene
implements both explicitly. The contentKey change is the reason for the test edit
below.

NavEntry.contentKey now defaults to a composite of `key.toString()` and
`key::class.toString()`, so the assertion pinning it to DetailScreen(5).toString()
failed. Dropped rather than updated to the new format: the preceding assertion
already compares contentKey to contentKey, and the backStack assertion above pins
that the entry is DetailScreen(5), so identity stays covered without re-arming the
same trap on the next release. Asserting on NavEntry.key instead is not an option
— it is private in beta01.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Navigator API examples told consumers to wrap the host in a BackHandler that
pops when `canPop` and otherwise switches tab. That duplicates
`TabsNav3Navigator.pop()`, which `Nav3TabsHost` already wires into
`NavDisplay.onBack`, and it intercepts the gesture before NavDisplay sees it, so
`predictivePopTransitionSpec` never scrubs — losing the native predictive back
this module exists to provide. It also hardcoded HomeTab where `startTab` is
configurable and may sit mid-strip.

Replaced with a "System back" section documenting the built-in path. Exiting the
app needs no handler either: at the start-tab root `canPop` is false and Nav3
disables its back callback, so back falls through to the Activity even though
retained tabs keep `backStack.size > 1` — asserted by
Nav3PredictiveBackDeviceTest. Genuine per-screen interception now points at
NavigationBackHandler from androidx.navigationevent, which arrives transitively
via navigation3-ui, shares NavDisplay's dispatcher, and unlike BackHandler
exposes gesture progress and cancellation.

Also documents the API 33-35 `enableOnBackInvokedCallback` opt-in. It defaults
true only on API 36+, and this module's minSdk is 23, so consumers below 36 were
silently getting commit-only pops from a module whose headline feature is
predictive back. Nothing in the repo mentioned it.

The tabs docs are reframed so the per-tab stacks read as the source of truth and
`backStack` as the derived projection NavDisplay renders from — which is what the
code does, `tabStacks` being what every push/pop mutates. Leading with "flattened
into a single backStack" invited the reading that the flat list is the model. A
new "Why one NavDisplay" section records why one display rather than one per tab:
Nav3 ties all per-entry state to back-stack membership via
NavEntryDecorator.onPop and has no retained-but-off-stack concept, so a single
display is what makes cross-tab retention possible at all, and it keeps
predictive back working across a tab boundary (predictive back being
per-NavDisplay) while avoiding a dispatcher per tab.

Stale navigation3 version in Requirements corrected alpha07 -> beta01;
UpdateReadmeVersions has no nav3 pattern, and that line is prose rather than a
dependency block, so it does not self-heal.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bug fixes only. A full source diff of both artifacts between beta01 and rc01
turns up exactly two changed files, and no public API change at all — expected,
since rc freezes the API.

NavEntry's `defaultContentKey` moved from `Pair("$key", "${key::class}")` to
`"$key:${key::class}"`. It is @PublishedApi internal but used as a default
constructor argument, so it compiles into NavEntry's own synthetic rather than
inlining into consumers, and there is no binary-compatibility exposure for a
library that ships against one version while its consumers compile against
another. TabsSceneStrategyTest is unaffected: 9ab405b already rewrote those
assertions to compare contentKey to contentKey precisely because beta01 churned
this field once before, so the trap was disarmed ahead of time.

UriDeepLinkMatcher gained duplicate-placeholder validation and a ParsedPattern
refactor. Unreachable here — this module imports no DeepLink* symbol, deep links
being a seeded start stack rather than a URI-pattern framework.

Verified on rc01: the module's 120 JVM tests, koverVerify, and
:app:verifyConsumerKeepRules, plus the full 14-test on-device suite, which is the
part that matters — it covers real predictive-back gestures and Activity
recreation, and `aFreshLaunchDoesNotInheritThePreviousActivitysTabViewModels`
exercises exactly the contentKey identity behaviour rc01 changed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A Compose patch release, not a feature one. Diffing the two BOM POMs
artifact-by-artifact (248 entries each), every version that moves goes 1.12.0 ->
1.12.1 across animation, foundation, material, runtime and ui. Nothing else
changes, and material3 does not move at all.

No API changes to absorb, so this is version-only across the ten modules that
apply the platform. The fixes do land where this repo lives, though —
runtime-saveable and foundation back ComposeExtensions' serialization-safe
MutableState holders, SegmentedControl's drag gestures, and the lazy-list paging
extensions.

Verified with the full test task across all modules, koverVerify, and
:app:verifyConsumerKeepRules. Also re-ran Nav3Navigation's on-device suite:
animation and foundation both moved, and predictive-back scrubbing plus
rememberSaveable restore across recreation are exactly what they drive. 14/14.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A patch bump with no API change, affecting DateHelperUtil-Room's type converters
and S3Uploader-Multipart's upload-state database.

The thing worth checking was the exported schemas, since S3Uploader-Multipart
writes them to a checked-in schemas/ directory and a codegen change there would
mean a migration problem rather than a build problem. Room 2.8.5 regenerates them
byte-identically: both v1 and v2 keep the same formatVersion and the same
identityHash (9fad5f76... and 2b09eace...), so nothing needed re-checking in and
no migration is implied.

The DateHelperUtil-Room README change is UpdateReadmeVersions syncing the Room
coordinates in its install block during the build, not a hand edit.

Verified with the full test task across all modules, koverVerify, and
:app:verifyConsumerKeepRules. Worth noting the gap this exposed rather than
leaving it implicit: S3Uploader-Multipart has no androidTest source set at all,
despite androidTestImplementation(room.testing) and schemas wired into androidTest
assets, so a @database(version = 2) with two exported schemas carries no automated
migration coverage. Harmless here because the schemas are unchanged, but the next
entity change walks into it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Split [versions], [libraries] and [plugins] into three tiers so it is obvious
which bumps consumers can see:

- PUBLISHED: on a consumer's classpath (api/implementation in a library module,
  or baked into the AAR by a code generator). Annotated the `api`-exposed ones
  (FlexiLogger, sandwich, Navigation 3) since a bump there is a breaking-change
  candidate rather than a routine build tweak.
- BUILD/TEST: test and androidTest configurations of the library modules, plus
  the toolchain. Only CI can break.
- DEMO APP: referenced solely by :app.

No entries added, removed or re-versioned in the regroup - activityCompose moves
to BUILD/TEST, where it belongs: it is the demo app plus one androidTest
dependency in Nav3Navigation, not a published dependency.

Also drop the `kover` version and plugin alias. A settings plugins block is
resolved before the version catalog exists, so settings.gradle.kts could never
have read libs.plugins.kover - it hardcodes the version and always has. With no
Renovate or Dependabot on this repo, the entry was two lines that could silently
disagree with the real version. settings.gradle.kts is now the single source of
truth and its comment says why.

Remove the commented-out testImplementation(libs.paging.common) from the two
Lazy*PagingExtensions modules; the live usages in :app and BaseRepo-Paging are
unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
README version references are synced by the UpdateReadmeVersions task during
Gradle sync, so they move with the bump rather than being hand-edited.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Testing a change before a release had one path — publish.sh --local — which
signs, so it waits on a 1Password unlock for a signature nothing local ever
verifies: Gradle does not check signatures on resolve. That makes the everyday
iteration loop cost a vault unlock for no benefit, and there was no way to undo
an install short of deleting from ~/.m2 by hand.

publish-local.sh installs unsigned and needs no credentials, and takes module
names to publish a subset while iterating on one module. --signed delegates to
publish.sh --local for the cases where the artifact set itself is under test.

clear-local-publish.sh removes the install again, optionally for one version.
It lists what it will delete and confirms first, and only ever touches the
toolbox's own group directory — derived from PUBLISH_GROUP, so a fork clears
its own coordinates rather than ours.

Both are also shared Android Studio run configurations under .run/, running in
the Run window's terminal so the confirmation prompt works there.

CONTRIBUTING.md gains the consuming-project half, which was missing entirely:
where mavenLocal() goes, why the first resolve needs --refresh-dependencies in
both directions, and the escape from version shadowing — a TOOLBOX_VERSION that
cannot exist on Central, so no version string means two different things.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two sections each told half the story. "System back" says stop wrapping the host
in a BackHandler; "Results" says popWithResult hands a value to the screen
underneath. Neither says what happens to a screen doing both — and the answer is
that system back routes through NavDisplay's onBack to a plain pop(), so the
result is dropped. The back arrow keeps working, the gesture silently stops
signalling, and nothing errors. A consumer migrating off BackHandler hits this
immediately and has no documented landing place.

Documents the host-onBack dispatch: an app-side interface read via
navigator.lastItem, so NavDisplay still owns the gesture and the predictive pop
transition still scrubs. Notes the navigator must be hoisted with
rememberBackStackNav3Navigator, since onBack is built at the call site where
LocalNav3Navigator is still the outer navigator rather than the one the host
provides.

Also warns off the obvious wrong fix — an always-enabled NavigationBackHandler —
which intercepts ahead of NavDisplay and loses predictive back, the same
regression 2bf651e removed from the tabs examples. That API is for conditional
interception, not for carrying a payload out of an unconditional pop.

Records why this stays app-side rather than becoming a host default or a
Nav3Screen.onPopResult hook: an always-popWithResult(null) default would deliver
null to receivers that only wanted explicit results, and the hook is this
interface with the library guessing the contract instead of the app declaring
it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Drops the release-candidate suffix now the branch is the 1.9.1 release. README
version references follow via the UpdateReadmeVersions sync.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@projectdelta6 projectdelta6 self-assigned this Sep 15, 2026

@jakeeilbeck jakeeilbeck left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean release PR — no correctness or security issues. Checked the CI draft-guard expression (correct: push still runs, drafts skip), the version-catalog regroup (kover dropping out of the catalog strands nothing — it's pinned directly in settings.gradle.kts), and both new shell scripts. One minor papercut noted inline; nothing blocking.

Comment thread scripts/publish-local.sh
case "$arg" in
--signed|-s)
info "Delegating to scripts/publish.sh --local for the signed install."
exec ./scripts/publish.sh --local

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--signed silently discards any module args — publish-local.sh --signed BaseRepo does a full signed publish and ignores BaseRepo. Fine given publish.sh has no subset mode, but worth a one-line warn before the exec so the dropped args aren't a surprise.

@projectdelta6
projectdelta6 marked this pull request as draft September 16, 2026 09:30
@projectdelta6

Copy link
Copy Markdown
Collaborator Author

Holding for BarcodeScanner feature

@projectdelta6
projectdelta6 deleted the release/1.9.1 branch September 16, 2026 15:45
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