880 Commits

Author SHA1 Message Date
Moe Hamade
7fa5bfe4dc
Merge 3bdaf2ae5904a5dcd2058dfb85dd5659c07330a5 into c127eb83ab94c069c32d37530d2faecd381cd2a8 2026-09-15 03:01:22 +09:00
GitHub Action
c127eb83ab Automated update of relay data - Sun Sep 13 06:20:34 UTC 2026 2026-09-13 06:20:34 +00:00
Moe Hamade
3bdaf2ae59 fix(ui): forward Back when the handler's enabled state is stale
BackHandler's enabled flag trails the state it is derived from by a
dispatch and a recomposition, so a second quick press after the last
overlay closes still reaches this handler. handleBackPressed then finds
nothing to unwind and returns false, and dropping that result swallowed
the press instead of leaving the chat screen.

The result is now forwarded: pop a route if one is beneath, and otherwise
finish, which is what the press would have done had the handler already
been disabled. This mirrors the guard the callback carried before the
Navigation 3 host replaced it.

Not covered by a test. The condition is a race between input dispatch and
recomposition inside a composed hierarchy, and this project cannot host a
Compose unit test yet: isIncludeAndroidResources fails 24 existing tests
with "targetSdkVersion=37 > maxSdkVersion=35", and pinning Robolectric to
sdk 35 fails 109.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LgmKvwg6yjo1cEmagppK9m
2026-09-08 21:54:48 +03:00
Moe Hamade
664c2dac46 refactor(nav): seed the back stack from the host, not from onCreate
Three changes that all come from the back stack currently being one entry
deep, which hides distinctions this PR is what makes matter.

Seeding moved out of onCreate. It was there because NavDisplay rejects an
empty back stack and effects do not run until after the first composition.
Gating the host on the stack instead lets the effect that re-roots on every
later crossing do the first one too, so there is one mechanism rather than
two. It also stops the start route having to be readable synchronously:
onCreate could only seed at all because onboardingState carries an eager
CHECKING, and anything read from disk later would have had nothing to offer
it. The gate has to test the stack rather than the route, since the route is
non-null from the first composition and would compose the host with nothing
to show.

The crossing guard compares the first entry rather than the last. They are
the same value at depth one, but the effect re-runs on every configuration
change while the retained scope keeps the stack, so once a destination is
pushed onto chat, the top would stop matching the root and every rotation
would silently reset the stack.

The Bluetooth receiver moves to onCreate and onDestroy. Composition and the
Activity cover the same span here, so this is not a lifetime change; it is
that a composition-scoped registration can be narrowed by moving the code,
which is exactly how it came to be scoped to a destination and stopped
firing on the chat route.

setRootIfEmpty goes with the onCreate call it existed for.

Verified on a Pixel 9a: launch without the empty-backstack crash, rotation
both ways with the stack intact, and Bluetooth off then on still logged on
the chat route. 591 unit tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdKgoKZbL5K1wsn1WsES26
2026-09-08 16:50:05 +03:00
Moe Hamade
1835281fc9 fix(nav): host the bluetooth receiver above the destinations
The ACTION_STATE_CHANGED receiver was registered from a DisposableEffect
inside OnboardingFlowScreen. That was harmless while the when branch kept
that composable on screen for chat too, but chat is its own destination
now, so reaching COMPLETE took the onboarding entry out of composition and
unregistered the only receiver watching the adapter. Turning Bluetooth off
from Quick Settings while sitting in chat then went unnoticed, and the
onResume fallback does not fire without a lifecycle transition.

Reproduced on a Pixel 9a. Disabling Bluetooth on the chat route logged
nothing on this branch, against main:

  D BluetoothStatusManager: Bluetooth turned OFF
  W MainActivity: Bluetooth disabled or failed: User has turned off their Blue

Hoisted next to NavDisplay, which lives for as long as the Activity's
composition, so it no longer depends on which destination is showing. The
callback also reads onboardingState from the ViewModel rather than the
value captured when the effect ran, which the effect's keys never
invalidated.

Same two lines now log on the chat route, and 591 unit tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdKgoKZbL5K1wsn1WsES26
2026-09-08 16:49:19 +03:00
Moe Hamade
c0a8a3c6df fix(nav): apply the scaffold insets at one layer, and trust the coroutines boms
contentModifier already carried the Scaffold's system-bar padding and was
passed to both NavDisplay and the onboarding entry, so onboarding padded
itself twice while ChatScreen, which applies its own status, navigation and
IME insets, gained an extra outer band. Only onboarding takes the scaffold
padding now, matching what the when branch did before this PR, where
ChatScreen was called with no modifier at all.

The verification metadata refresh also adds three kotlinx-coroutines-bom
poms. They resolve through detached configurations that KSP introduces, and
a warm local cache never re-fetches them, so they failed only on a cold CI
runner. Regenerated with --refresh-dependencies so the whole set was
observed at once rather than one CI cycle at a time.

Three components added, none removed, no version drift.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdKgoKZbL5K1wsn1WsES26
2026-09-08 16:49:19 +03:00
Moe Hamade
c56dfa826c build: lock Navigation 3 for release and trust guava's parent pom
The nav3 entries were written only for the debug configurations, because
the locks were refreshed with debug tasks. The root build sets
LockMode.STRICT for every configuration, so resolving releaseCompileClasspath
or releaseRuntimeClasspath rejected them and no release could be built.

Refreshed following docs/reproducible-builds.md: the debug, lint and IDE
copy-lock pass, then bundleRelease and assembleRelease in separate
invocations because the split APK and AAB intermediates cannot coexist.

Also adds com.google.guava:guava-parent:33.6.0-jre. Its pom is resolved
through the KSP classpath that Hilt introduces, and a warm local cache
never re-fetches it, so it only surfaced on CI. The checksum agrees across
the local cache, a fresh Maven Central download, and the .sha256 Central
publishes alongside it.

One component added, none removed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdKgoKZbL5K1wsn1WsES26
2026-09-08 16:49:02 +03:00
Moe Hamade
eb8c2e7446 style: drop the duplicated and speculative navigation commentary
The reason interceptBack exists was written twice, at the call site and
where the parameter is defined. Kept the one next to the definition.

AppRoutes named :feature:<name>:api as the eventual home for the route
types. Those modules do not exist and may not be called that; a comment
that describes a plan rather than the code ages into a wrong one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdKgoKZbL5K1wsn1WsES26
2026-09-08 16:49:02 +03:00
Moe Hamade
c7d551e2af feat(nav): put onboarding and chat behind a Navigation 3 back stack
Replaces the when (onboardingState) in MainActivity and the ad-hoc back
handling with a Navigation 3 back stack. Navigator, AppNavigator, the
EntryProviderInstaller multibinding and the NavDisplay host live in :app
for now; they move to a :core:navigation module once there is a second
module to justify one.

Onboarding is one destination, not eight. Its steps are driven by
permission results and adapter state changes rather than by the user
navigating, and going back a step has never been supported. Per-step
routes would invent a history that does not exist, so the state machine
stays and only the crossing into chat is a navigation event. That crossing
uses resetTo, so Back cannot re-enter onboarding.

Removes the OnBackPressedCallback that was constructed and registered
inside a composable body, adding a callback on every recomposition of that
branch. NavDisplay owns back now; it drives predictive back through
androidx.navigationevent, and a second handler over the same gesture can
detach the navigationevent input mid-gesture, so there must not be one.

Chat still manages its overlays with booleans, so back consults
ChatViewModel first through interceptBack. That parameter exists only until
those overlays become routes.

The back stack is seeded before setContent rather than from a
LaunchedEffect: NavDisplay rejects an empty back stack and an effect does
not run until after the first composition.

Verified on a Pixel 9a: launch renders without the empty-backstack crash,
chat is reached, Back exits to the launcher instead of re-entering
onboarding, and relaunch is clean. Plus :app:assembleDebug and 591 unit
tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdKgoKZbL5K1wsn1WsES26
2026-09-08 16:48:41 +03:00
Moe Hamade
480dc38333 style: group the new dagger imports with their neighbours
The Hilt annotations landed mid-block in two files, splitting the androidx and
kotlinx groups those files otherwise keep. No behaviour change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NaZivRfLu7FdKjzVwwRjcc
(cherry picked from commit 12b4b2aba288ec8875188d81cd60831764ce51f5)
2026-09-08 16:47:04 +03:00
Moe Hamade
bf782f9072 feat(di): inject the phone client's ViewModels with Hilt
Replaces the hand-rolled ViewModelProvider.Factory in MainActivity with
constructor injection. No singleton is converted and no behaviour changes;
this only moves ViewModel construction into the graph so feature modules
can obtain their own ViewModels later.

MeshModule bridges the existing process-wide mesh instances into the graph.
Its bindings are deliberately unscoped: MeshServiceHolder is the source of
truth, it is shared with the foreground service, and it supports replacing
the mesh service after a panic clear. A @Singleton binding would pin the
first instance for the process lifetime and hand out a stale service after
a replacement.

ChatViewModel still resolves to a single activity-scoped instance, so the
unifiedMeshService.delegate assignment continues to reference the same
object it did before.

ApkDownloadViewModel is injected through its secondary constructor. Its
collaborators are internal types, and routing those through Dagger's
generated Java buys nothing; the primary constructor stays available so
tests can keep substituting fakes.

Verified: :app:assembleDebug, :wear:assembleDebug, and 591 unit tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NaZivRfLu7FdKjzVwwRjcc
(cherry picked from commit 33d6b36e220810c87595851ece7e9a63535c61b0)
2026-09-08 16:47:04 +03:00
Moe Hamade
8b7af243b3 build: raise Gradle metaspace to 1g
KSP and the Dagger/Hilt annotation processors load enough additional classes
into the daemon that a combined testDebugUnitTest + lintDebug run exhausts the
previous 512m ceiling.

The failure is worth describing because it does not name its cause: it surfaces
as an OutOfMemoryError inside ClassLoader.defineClass in whichever task happens
to be loading classes at the time, including lint on an unrelated module. When
it lands during a --write-verification-metadata run it can also truncate
gradle/verification-metadata.xml to zero bytes, which then fails the next build
with "Unable to read dependency verification metadata: Premature end of file".

JVM arguments are not build inputs, so this does not affect reproducibility.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NaZivRfLu7FdKjzVwwRjcc
2026-09-08 16:47:04 +03:00
Moe Hamade
e6db3ec7e4 build: add KSP and Hilt to the phone client
Adds the KSP and Hilt Gradle plugins and the Hilt runtime/compiler
dependencies to :app, with the lock state and verification metadata
regenerated per docs/reproducible-builds.md. No source changes; nothing
is injected yet.

KSP versions independently of Kotlin as of 2.3.0. Earlier releases used a
<kotlin>-<ksp> scheme requiring an exact match to the Kotlin version, so
the catalog records the reason the two versions no longer track.

Verification metadata gains 36 components and removes none. Reviewed:

- androidx.compose.runtime:runtime:1.8.2 and
  androidx.annotation:annotation-experimental:1.3.1 are .module metadata
  only, with no jar or aar. They are resolution metadata, not artifacts,
  and do not downgrade what the Compose BOM selects.
- kotlin-stdlib:2.3.20 carries a jar but the lock state confines it to
  kspPluginClasspath, so it never reaches a runtime classpath.
- com.google.dagger:dagger was already on the runtime classpath at 2.59
  transitively; it moves to 2.60.1.
- The remainder are the Dagger compiler's own build-time dependencies
  (guava, kotlinpoet, javapoet, google-java-format, error_prone).

wear/gradle.lockfile and settings-gradle.lockfile are unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NaZivRfLu7FdKjzVwwRjcc
(cherry picked from commit a97675b46527f98f5493150b53c926caa5115058)
2026-09-08 16:47:04 +03:00
callebtc
945c9e025b
Merge pull request #866 from Chessing234/fix-dedup-total-checks-race
fix: synchronize totalChecks increment in NostrEventDeduplicator
2026-09-07 23:44:31 +03:00
callebtc
010789adde
Merge pull request #882: strengthen packet deduplication
Use full-payload packet IDs for duplicate detection and simplify the explanatory comment.
2026-09-07 23:33:41 +03:00
callebtc
9444acd33e
Merge pull request #869 from qutad/fix/nostr-ios-dm-lookback
fix(nostr): cap DM envelope backdating at 24 hours
2026-09-07 23:32:40 +03:00
callebtc
9e5a07b782
Merge pull request #907 from Chessing234/fix/relay-reconnect-recovery
fix(nostr): keep retrying relays instead of giving up on them forever
2026-09-07 23:31:15 +03:00
callebtc
fa1727de2f docs: simplify packet deduplication comment 2026-09-07 23:29:23 +03:00
callebtc
936a4cdf6d
Merge pull request #932 from permissionlesstech/codex/release-wear-0.1.3
Bump Wear OS version to 0.1.3
v2.0.2-wear.0.1.3
2026-09-07 15:08:23 +03:00
callebtc
0fce9faa1f Bump Wear OS version to 0.1.3 2026-09-07 14:55:50 +03:00
callebtc
f352a3e4bc
Merge pull request #931 from permissionlesstech/codex/wear-ui-shape-accessibility
Improve Wear round-screen layouts and chat scrolling
2026-09-07 14:43:41 +03:00
callebtc
eb9ce6555f Merge remote-tracking branch 'origin/main' into codex/wear-ui-shape-accessibility 2026-09-07 14:20:35 +03:00
callebtc
08974107dc Compact Wear chat headers and stabilize scroll controls 2026-09-07 14:20:35 +03:00
GitHub Action
9a399cc333 Automated update of relay data - Sun Sep 6 06:16:30 UTC 2026 2026-09-06 06:16:30 +00:00
callebtc
afad3c5513 Fix Wear layouts on small round displays 2026-09-05 19:42:43 +03:00
callebtc
81ba775063
Merge pull request #923 from permissionlesstech/codex/bump-version-2.0.2
Bump Android version to 2.0.2
v2.0.2
2026-08-31 00:04:04 +03:00
callebtc
06cdb0f2f6 Bump Android version to 2.0.2 2026-08-30 23:40:15 +03:00
GitHub Action
1c33df7407 Automated update of relay data - Sun Aug 30 06:16:55 UTC 2026 2026-08-30 06:16:56 +00:00
Taksh
e50cf9c8cc chore: retrigger ci 2026-08-30 09:49:50 +05:30
Taksh
5800583cd2 chore: retrigger ci 2026-08-30 09:39:09 +05:30
Taksh
e65039fe76 chore: retrigger ci 2026-08-30 09:38:30 +05:30
callebtc
4f828b5643
Merge pull request #916 from permissionlesstech/codex/fix-wear-shape-and-font-scaling
Improve Wear layouts for round screens
2026-08-26 18:19:15 +02:00
callebtc
7cf5286bfb Fix slow Wear scroll intent detection 2026-08-26 17:28:17 +02:00
callebtc
a125da48aa Polish Wear shape-aware layout implementation 2026-08-26 16:46:13 +02:00
callebtc
3ed46222ba Fade Wear chat at screen contour 2026-08-26 11:56:00 +02:00
callebtc
898cfde066 Restore Wear chat edge fading 2026-08-26 11:39:16 +02:00
Moe Hamade
11097a629a style: cut the commentary around the back handler down to what the code cannot say
The block carried seven comment lines over four of code, against 0.17 for
the file as a whole. Most of it restated the identifiers: enabled =
canHandleBack does not need a sentence explaining that Back is intercepted
while there is state to unwind.

What is left is the part that is not derivable. That enabled is a variable
rather than true is a predictive-back decision, and finish() being reachable
at all only makes sense once you know the flag trails the state it mirrors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdKgoKZbL5K1wsn1WsES26
2026-08-26 00:28:55 +03:00
Moe Hamade
68361d949f fix(ui): forward Back when canHandleBack is momentarily stale
BackHandler's enabled flag trails the state it mirrors by a coroutine
dispatch and a recomposition. A second Back press inside that window finds
the handler still enabled while handleBackPressed() already has nothing to
unwind, and ignoring its result consumed the press instead of letting the
system act on it. The callback the handler replaced did forward it.

Falls back to finish() on an unhandled press, which is what the dispatcher
reached before once no enabled callback consumed it.

Reported by Codex review on #912.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdKgoKZbL5K1wsn1WsES26
2026-08-26 00:11:54 +03:00
Moe Hamade
78991a289e fix(ui): register the chat back handler once instead of per recomposition
The chat branch of OnboardingFlowScreen built an OnBackPressedCallback and
called addCallback(this, ...) from inside a composable body. Composable
bodies re-run on recomposition, and that function observes eight
MainViewModel StateFlows, so every Bluetooth, location, or loading change
registered another callback. They were bound to the activity, so none were
released until onDestroy.

Each accumulated callback also multiplied the work of an unhandled Back
press: the callback disabled itself, re-dispatched, and the next one down
consulted ChatViewModel again. Five recompositions meant one Back press
consulted it five times before reaching the system.

BackHandler keeps a single registration across recompositions and disposes
it when the branch leaves composition. Driving its enabled flag from
ChatState.canHandleBack also retires the disable/re-dispatch/re-enable
dance: with nothing to unwind the handler is simply disabled, so the
dispatcher falls through to the system.

canHandleBack mirrors the branches of ChatViewModel.handleBackPressed and
is covered by unit tests, since the handler being enabled and the press
being consumed have to agree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MdKgoKZbL5K1wsn1WsES26
2026-08-25 23:52:52 +03:00
callebtc
33c1ebfc60
Merge pull request #909 from Chessing234/fix/panic-wipe-destroy-db-on-failure
fix: destroy conversation database files when panic wipe throws
2026-08-25 14:37:58 +02:00
Taksh
1a9f6befd4 fix: destroy conversation database files when panic wipe throws
If SQLite clearAll fails mid-panic, in-memory state was already cleared
but a process restart could reload encrypted history from disk (#699).
Fall back to deleting the database files before reporting failure.
2026-08-25 07:40:04 +05:30
callebtc
ed617f0f1b Fix Wear chat autoscroll and focal scaling 2026-08-24 21:13:07 +02:00
callebtc
34f94882f5 Restore Wear chat content space 2026-08-24 20:57:24 +02:00
callebtc
45915fab8f Tune Wear header boundary by screen shape 2026-08-24 20:15:17 +02:00
callebtc
f744038318 Preserve width for Wear Material cards 2026-08-24 20:08:44 +02:00
callebtc
4a1de47ed6 Cover transformed Wear header boundary 2026-08-24 20:04:46 +02:00
callebtc
5d66b69129 Clip Wear messages below the chat header 2026-08-24 20:01:46 +02:00
callebtc
7788328917 Keep Wear chat rows clear of controls 2026-08-24 19:58:03 +02:00
callebtc
2e41dabd07 Add round-screen responsive Wear insets 2026-08-24 19:54:20 +02:00
callebtc
5852fca9b5 Fix Wear content clipping on watch shapes 2026-08-24 19:38:26 +02:00