Commit Graph
15259 Commits
Author SHA1 Message Date
Vitor PamplonaandGitHub 2258a0ee2d Merge pull request #3270 from vitorpamplona/claude/tender-mccarthy-lr70hd
Fix NostrClientRepeatSubTest to handle timing-dependent EOSE counts
2026-06-18 16:46:31 -04:00
davotoulaandClaude Opus 4.8 2a7493019b fix: remove literal backslash in DM relay-incomplete label
Compose Multiplatform string resources don't use Android res/values
escaping, so \' rendered literally as didn\'t on the DM history card.
Use a plain apostrophe to match the sibling new_key_continue_button string.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 22:25:34 +02:00
Claude c5c5ef706f fix(quartz): de-flake NostrClientRepeatSubTest re-subscription test
The test asserted exactly two EOSEs (`assertEquals(2, eoseCount)`) and
looped `while (eoseCount < 2)`. But a re-REQ on the same subscription id
silently replaces the previous subscription (NIP-01), and
RelaySession.handleReq cancels the in-flight query coroutine without
emitting an EOSE for the superseded filter. So when both mid-stream
re-subscriptions get collapsed before reaching EOSE, only the final,
never-superseded filter emits one — the consumer loop then blocks on
receive() until the 30s timeout and the assertion fails.

Drain until the relay goes quiet (idle-gap timeout) instead of counting
on a fixed number of EOSEs, and assert only invariants that hold for
every interleaving: at least one EOSE arrives, every non-EOSE entry is a
valid 64-char id, the final advertised-relay-list filter actually
streamed its events, and the total stays under a sane upper bound.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018e1bfPf4HZDCvwcfiEHECk
2026-06-18 20:16:02 +00:00
Vitor PamplonaandGitHub 3948c8dbd1 Merge pull request #3266 from vitorpamplona/claude/relay-connections-background-03x58e
Fix lifecycle-aware subscriptions and notification relay throttling
2026-06-18 15:42:28 -04:00
Vitor PamplonaandGitHub f31ab08e25 Merge pull request #3268 from vitorpamplona/claude/scroll-sensitivity-nav-statusbar-g2jo95
Add hysteresis to status bar hide/show and dampen bar reveal
2026-06-18 15:42:09 -04:00
Vitor PamplonaandGitHub 0d8816eda0 Merge pull request #3267 from vitorpamplona/claude/modernize-quote-repost-buttons-05r689
Refactor boost popup UI with reusable BoostActionChip component
2026-06-18 15:17:58 -04:00
Vitor PamplonaandGitHub dc31f04a92 Merge pull request #3265 from vitorpamplona/claude/beautiful-bell-145xan
Fix public chat relay selection for channel messages
2026-06-18 15:11:02 -04:00
Claude 400a06f62b revert(relay): leave RelayPool's connected set to onDisconnected
Drops the _connectedRelays changes (the removeRelayInner prune and the
derived-projection refresh) and restores RelayPool to match main. The
incremental onConnected/onDisconnected maintenance is sufficient; the
user-visible background relay-count issues are addressed by the lifecycle
teardown timing and the notification-update throttle, not here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ukw6FJPFh3JKGXL532p3ae
2026-06-18 19:04:22 +00:00
Claude 6e7d731979 feat: require at least one relay to create or edit a public chat
The channel metadata save/create button is now disabled until the channel
has a non-blank name AND at least one relay. A public chat with no relay
declares an empty relay list, which leaves messages with no reliable home
(the root cause behind the relay-targeting issue). Gating the button at the
screen level keeps the relay-list StateFlow reactive (a ViewModel
derivedStateOf would not recompose on relay add/remove).

Also reverts the temporary PublicChatRelayDebug diagnostic logging.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AYtHYEob2THu74inTxZxCh
2026-06-18 19:01:55 +00:00
Claude 77834cfd92 fix(relay): derive connected set from pool state instead of patching it
Replaces the incremental add/remove maintenance of _connectedRelays
(including the removeRelayInner prune) with a recompute from the source of
truth: a relay is connected iff it is in the pool AND its socket reports
ready (isConnected()). refreshConnectedRelays() runs on connect, disconnect
and pool-membership changes.

The earlier prune patched the *readout* on the assumption that "removed
from pool ⟹ disconnected", which is only incidentally true. A set that is
hand-maintained per event drifts from reality whenever an event is missed —
OkHttp's async cancel() callback being dropped under mass teardown, or a
socket dying without an onDisconnected. Projecting the set from each pooled
relay's actual isConnected() can't drift: removed relays are already
disconnected so they fall out, and a silently-dead socket stops being
counted on the next refresh.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ukw6FJPFh3JKGXL532p3ae
2026-06-18 18:54:06 +00:00
Claude 85e6abd17b chore(relay): drop speculative removeAllRelays connected-set clear
removeAllRelays() has no call sites — it's dead code — so clearing
_connectedRelays there was never exercised. The live fix for the stale
connected count is the prune in removeRelayInner (driven by updatePool),
which keeps removeAllRelays untouched relative to main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ukw6FJPFh3JKGXL532p3ae
2026-06-18 18:32:45 +00:00
Vitor PamplonaandGitHub d6fcc7abbf Merge pull request #3264 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-18 14:16:08 -04:00
Claude 738589fe2f chore(relay): remove diagnostic logging, restore 30s unsubscribe grace
Strips the BgRelayTrace instrumentation added while diagnosing the
background relay-count issues and restores the production grace period.

- LifecycleAwareKeyDataSourceSubscription: UNSUBSCRIBE_GRACE_MILLIS back to
  30s, drop the per-subscription label + logs, refresh the doc to describe
  the LifecycleEventObserver detection.
- RelayPool: drop updatePool trace logs and the now-unused Log import; keep
  the _connectedRelays prune (with a trimmed comment).
- BaseEoseManager: drop the per-assembler relay-count log + Log import.
- SubscriptionController: drop activeRelays(), which only fed that log.

The actual fixes stay: lifecycle-observer teardown detection, the
connected-set prune, and the notification-count throttle + fg/bg wording.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ukw6FJPFh3JKGXL532p3ae
2026-06-18 18:15:32 +00:00
Claude dbfd2c4c43 fix(notif): throttle relay-count updates to dodge Android's rate limit
A device log showed the persistent notification stuck on a stale count
(e.g. "44 inbox relays") while the pool had actually settled lower
(flowConnected=8). Cause: the count collector posted the notification on
every connectedRelaysFlow delta — ~90 updates during feed load, then ~22
in ~250ms during background teardown. Android rate-limits notification
updates (~10/s) and silently drops the excess, so the last value the
framework rendered (a mid-cascade 44) stuck instead of the final 8.

Sample connectedRelaysFlow at 1s before updating the notification. That
caps updates to ~1/s — comfortably under the limit — and the settled
count always lands. Also drops the now-confirmed notif-collector/
notif-popup debug logging.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ukw6FJPFh3JKGXL532p3ae
2026-06-18 17:53:39 +00:00
Crowdin Bot e066bd8a5b New Crowdin translations by GitHub Action 2026-06-18 17:37:54 +00:00
Claude bbd84664d9 Merge remote-tracking branch 'origin/main' into claude/relay-connections-background-03x58e 2026-06-18 17:37:26 +00:00
Vitor PamplonaandClaude Opus 4.8 2f602a29bc fix(tor): seed hasEverBootstrapped from on-disk guard sample
A fresh process reset TorManager.hasEverBootstrapped to false, so the
stuck-Connecting self-heal watchdog used the gentle reset() (drop client,
keep state) instead of resetWithCleanState() (wipe state). When guards.json
carried guards poisoned by TooManyIndeterminateFailures from a prior
session, every retry reloaded the same poisoned guards and Tor stayed stuck
in Connecting forever — never wiping the one thing blocking it.

Seed hasEverBootstrapped at startup from durable on-disk evidence: Arti only
writes confirmed_at on a guard after it has built real circuits, so a
confirmed guard proves Tor bootstrapped successfully on this install before,
even across the restarts that clear the in-memory flag. With it set, a stuck
bootstrap correctly wipes the stale/poisoned state and rebuilds a fresh
guard sample.

- ArtiGuardState: pure, file/JNI-free parsers over guards.json
  (hasConfirmedGuard + hasNoUsableGuards extracted from TorService).
- TorService.hasBootstrappedBefore() reads the file off-thread.
- TorBackend gains the suspend method; TorManager seeds in init.
- Tests cover the parser against a real captured poisoned-but-confirmed
  guards.json fixture, plus a watchdog test for the wipe-on-first-stuck path.

Verified on an emulator stuck in Connecting from real poisoned guards:
self-heal wiped state and Tor reached Active in ~5s (clean 0-disabled
guard sample).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 13:33:51 -04:00
Claude c4889c9ae2 Merge remote-tracking branch 'origin/main' into claude/relay-connections-background-03x58e 2026-06-18 17:33:09 +00:00
Claude edae6d0520 feat(notif): distinguish foreground vs background relay count in popup
The always-on notification now reads "Connected to N relays" while the
app is foreground (the pool also holds feed/finder outbox relays) and
"Connected to N inbox relays" once backgrounded (feeds torn down, only
inbox + DM relays remain). The label is chosen from MainActivity.isResumed
at each notification refresh; since foreground/background transitions
always change the connected count, the existing count-driven re-post
picks up the new wording.

Both messages are now <plurals> (relay/relays declines in many locales),
converting the existing always_on_notif_connected across all 11 locales
that had it (other-only; Crowdin fans out the remaining CLDR categories).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ukw6FJPFh3JKGXL532p3ae
2026-06-18 16:30:06 +00:00
Claude b732cbaf59 chore: temporary diagnostic logging for public chat relay targeting
Logs the relay set targeted by the public-chat send path and the broadcast
path under tag "PublicChatRelayDebug", so an on-device repro can show whether
the channel-declared relay is actually in the target set. To be reverted once
the declared-relay case is diagnosed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AYtHYEob2THu74inTxZxCh
2026-06-18 15:56:57 +00:00
Claude 17073e7cfa debug(relay): trace the notification popup's connected-count updates
The persistent notification's relay count is rendered by a collector on
the service's Dispatchers.IO scope. If that collector is throttled while
backgrounded — the same throttling that delayed the lifecycle teardown by
60s — the popup would show a stale count while the real pool (logged as
flowConnected in updatePool) has already shrunk. Log every value the
collector receives and every count it actually posts, so we can tell a
stale popup from real connections.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ukw6FJPFh3JKGXL532p3ae
2026-06-18 15:53:59 +00:00
Claude 840868d315 fix: fall back to observed relays when public chat declares an empty relay list
PublicChatChannel.relays() was `info.relays?.toSet() ?: super.relays()`.
An empty (non-null) declared-relay list — `emptyList()?.toSet()` — yields an
empty set and short-circuits the elvis, so the channel reported zero relays
instead of falling back to the relays it was actually observed on. Both the
message-send path and the broadcast path (computeRelaysForChannels /
wantsBroadcastRelays) read relays(), so the message was published to nowhere
while a manual broadcast still reached the user's personal relays — matching
the reported symptom.

Treat an empty declared list like "no declared relays" via ifEmpty, so it
falls back to observed relays. Adds PublicChatChannelRelayTest covering the
declared-relay round-trip, message->channel resolution, and the empty-list
fallback regression.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AYtHYEob2THu74inTxZxCh
2026-06-18 15:42:52 +00:00
Claude 5d2bbfe661 style(relay): spotless import ordering in BaseEoseManager
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ukw6FJPFh3JKGXL532p3ae
2026-06-18 15:35:11 +00:00
Claude 87c4077f94 fix(relay): detect background via LifecycleEventObserver, not bg-dispatched flow
A device log showed the foreground feeds (and ~150 relays) staying
connected for a full ~60s after the app was paused, then collapsing to
the 11-relay floor all at once:

    11:26:02  HomeOutboxEventsEoseManager — keys=2, relays=344   (paused here)
              … 60s of silence …
    11:27:02  grace-start(HomeFilterAssembler) — lifecycle=CREATED
    11:27:02  updatePool done — flowConnected=9, inPool=11

The lifecycle-aware subscription detected ON_STOP by collecting
lifecycle.currentStateFlow on Dispatchers.Default. Backgrounded, that
collector wasn't resumed until the next NostrClient keep-alive tick
(KEEP_ALIVE_INTERVAL_MS = 60s), so teardown — and the relay disconnects
it drives — lagged a minute behind the actual pause.

Switch detection to a main-thread LifecycleEventObserver, which fires
synchronously during onStop. Only the grace delay still runs on the
background scope (so it isn't gated by the stopped UI frame clock).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ukw6FJPFh3JKGXL532p3ae
2026-06-18 15:34:56 +00:00
Claude 6e579e469f fix: broadcast public chat messages when channel declares no relays
Public chat (NIP-28) messages were sent only to the channel's own
declared relay set via signAndSendPrivately, which has no fallback.
When channel.relays() resolved to an empty set (channel created
without relay hints, or sparse observation data), the message was
published to zero relays and never left the device — yet hitting the
Broadcast button later delivered it fine, since broadcast computes a
full relay list (author outbox + channel + hints + broadcast relays).

Route every channel through signAndSendPrivatelyOrBroadcast, which
already falls back to computeRelayListToBroadcast when the supplied
relay list is empty. LiveActivities chat already used this path; this
extends the same fallback to public chats.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AYtHYEob2THu74inTxZxCh
2026-06-18 14:53:12 +00:00
Claude 63da2e5b71 debug(relay): log per-assembler relay count on invalidation
After fixing the stale connected-count, the background footprint settles
at ~25 relays (desired=22) — higher than the inbox+DM target. Add a
per-EoseManager log (assembler name -> key count + distinct relay count)
so we can attribute the 25 to specific always-on loaders (metadata/drafts
on homeRelays, gift-wrap history, marmot groups, notifications) and trim
precisely instead of guessing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ukw6FJPFh3JKGXL532p3ae
2026-06-18 14:31:07 +00:00
Claude 43b28a5cf4 fix(relay): prune connected set when a relay leaves the pool
Backgrounding the app correctly collapsed the desired relay set (e.g.
desired=22, toRemove=343) and the pool cache shrank accordingly, yet the
always-on notification kept reporting ~110 connected relays. Measured:

    updatePool done — cacheConnected=18, flowConnected=87, inPool=22

_connectedRelays (exposed via connectedRelaysFlow() and read by the
notification) was only ever pruned from the async onClosed/onFailure
websocket callback. disconnect() uses OkHttp cancel(), which kills the
socket immediately but whose callback is unreliable when hundreds of
sockets are cancelled at once in the background — so the connected set
stayed stale long after the real connections were gone.

Prune _connectedRelays directly in removeRelayInner (and clear it in
removeAllRelays) so the connected set tracks the pool's membership
immediately. The async callback remains as an idempotent backstop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ukw6FJPFh3JKGXL532p3ae
2026-06-18 13:55:52 +00:00
Claude a15fe94e2f debug(relay): log connected-set vs pool cache after updatePool
The background teardown works (desired collapses to 11, toRemove=349) but
the persistent notification still reports ~110 connected relays. That
points at the _connectedRelays StateFlow (what the notification reads)
being decoupled from the pool's desired set: it is only decremented from
the async onFailure/onClosed websocket callback, while disconnect() uses
OkHttp cancel() (immediate/violent). Add a post-reconcile log comparing
cacheConnected (relays still in the pool reporting isConnected) against
flowConnected (_connectedRelays.size) to confirm whether the 110 are live
sockets or a stale count.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ukw6FJPFh3JKGXL532p3ae
2026-06-18 13:37:15 +00:00
Claude 5b996d62e8 feat(ui): show both fork states in boost popup preview
Stack the with-fork and without-fork variants of the quote/repost popup
in the @Preview so both the text-note (fork available) and long-form/wiki
(fork hidden) layouts render side by side in dark and light themes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016a27kWKUcpADUC1KhXHRpV
2026-06-18 13:24:13 +00:00
Vitor PamplonaandGitHub 24e9cf2aaa Merge pull request #3263 from vitorpamplona/claude/gracious-archimedes-wfvcri
Add macOS code signing + notarization for DMG and amy CLI
2026-06-18 09:14:25 -04:00
Claude 87c16afe1d refactor: move amy.rb into cli/packaging/homebrew (was root packaging/)
The Homebrew formula is the cli module's product (amy), and the CLI already
owns its packaging artifacts under cli/packaging/ (cli/packaging/macos/
amy.entitlements). The root packaging/ dir was new in this branch and held
nothing else, so co-locate the formula with the module that owns it and drop
the stray root dir. Updates the two BUILDING.md references.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sso31DfSF9B6EFCVkEqWD
2026-06-18 13:13:43 +00:00
davotoula c9e8c84524 fix(l10n): list base sw in locales_config to match Swahili consolidation
The Swahili translation now lives at the base values-sw resource (the
sw-KE/sw-TZ dirs were consolidated away)
2026-06-18 11:44:00 +02:00
David KasparandGitHub 468966f57b Merge pull request #3262 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-18 11:42:15 +02:00
David KasparandGitHub 1dfa24390d Merge branch 'main' into l10n_crowdin_translations 2026-06-18 11:42:06 +02:00
Crowdin Bot 97e1a9bebf New Crowdin translations by GitHub Action 2026-06-18 09:40:57 +00:00
David KasparandGitHub b73890ac96 Merge pull request #3261 from nrobi144/feat/desktop-launch-optimization
feat(desktop): launch optimization foundation + icon-decode/relay-bootstrap fixes
2026-06-18 11:38:59 +02:00
nrobi144 96752bd21b docs(desktop): mark all in-scope launch-opt phases complete in the plan
Phase 1.4 (App() smoke test), Phase 2.4 (fixture-relay wire-up), and
Phase 5.2 (bootstrap-gate fix + regression tests) all land in commit
48a8178c9. The progress-log table, acceptance-criteria checkboxes, and
pending-work section are updated to reflect the new state. 278/278
desktopApp tests pass.

Only follow-up enhancements remain — the cold-fork shell driver, a
Compose-driving benchmark variant, and Phase 5.3 (sequential remember
chain in MainContent). None are required for the in-scope set.
2026-06-18 12:10:58 +03:00
nrobi144 48a8178c98 feat(desktop): App() Compose UI smoke tests + Phase 5.2 regression tests
Adds a `LaunchTestOverrides` bundle (default null in production) so
`App()` can be driven from `createComposeRule()` against the in-process
fixture relay instead of the OkHttp + kmp-tor + DesktopHttpClient stack
it normally constructs via `remember { … }`. `DesktopRelayConnectionManager`
gains a secondary constructor taking a `WebsocketBuilder` so the
`LocalRelayManager` composition local (typed as
`DesktopRelayConnectionManager?` and consumed by ~20 screens) does not
have to be relaxed.

`AppStateMachineTest` exercises four scenarios:

1. `appShowsLoginScreenWhenNoSavedAccountExists` — App() with no
   `accounts.json.enc` reaches LoggedOut and renders LoginScreen.
2. `appWithViewOnlyAccountReachesLoggedInWithoutCrashing` — App() with
   a pre-seeded ViewOnly account reaches LoggedIn end-to-end through
   `MainContent`, the deck columns, NWC wiring, etc.
3. `bootstrapSubscriptionFiresEagerlyEvenWhenRelayNeverConnects` —
   wires a `NeverConnectsWebsocketBuilder` so no connection ever opens,
   yet App() still reaches LoggedIn within 5s instead of the previous
   30s gate timeout. Direct regression test for the Phase 5.2
   bootstrap-gate removal.
4. `bootstrapSubscriptionFiresAtMostOncePerAccountLoad` — wraps the
   fixture builder with a `RecordingWebsocketBuilder` and asserts the
   bootstrap REQ does not loop or double-fire.

The `LaunchScenario` benchmark drops its private
`BenchmarkRelayConnectionManager` subclass in favor of the new
secondary `DesktopRelayConnectionManager(WebsocketBuilder)` constructor.

278/278 desktopApp tests pass.
2026-06-18 12:09:41 +03:00
David KasparandGitHub 24655a8a89 Merge pull request #3259 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-18 11:01:28 +02:00
Crowdin Bot a81b2b9994 New Crowdin translations by GitHub Action 2026-06-18 08:41:19 +00:00
David KasparandGitHub 4d8e3063c4 Merge pull request #3258 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-18 10:39:27 +02:00
nrobi144 d10c875d88 docs(desktop): refresh launch-opt plan pending list with App() blocker
Phase 1.4 / 2.4 / the four Phase 5.2 regression tests are all blocked on
the same broader App() dependency-injection refactor — relayManager,
localCache, localRelayStore, and subscriptionsCoordinator are still
constructed inside App() via remember { … }. The torManager slot has
been loosened to ITorManager in preparation, but the rest is wider work
than this session can absorb.
2026-06-18 11:14:28 +03:00
nrobi144 d2d044a634 refactor(desktop): relax App() torManager param to ITorManager interface
App() only consumes torManager.status, which is on ITorManager.
Loosening the parameter type lets future tests substitute a fake without
having to construct the concrete DesktopTorManager (which eagerly builds
a kmp-tor TorRuntime on first status access). No production behavior
change: DesktopTorManager already implements ITorManager and the existing
call site at Main.kt:633 upcasts naturally.

This is a small intermediate step on the road to the still-pending Phase
1.4 App() Compose smoke test, which is the last item blocked on broader
App() dependency injection (relayManager / localCache / localRelayStore
are still remember'd internally).
2026-06-18 11:13:54 +03:00
nrobi144 b14ee5ec5c feat(desktop): in-process relay seam, launch benchmark, bootstrap-gate removal
Phases 2.1/2.2/2.3, 3.1/3.2, 4, 5.2, and 6 of the launch-optimization plan
land together because they share a single set of seams and a single
benchmark report.

* InProcessWebsocketBuilder + LaunchFixtureRelay wrap quartz's existing
  InProcessWebSocket + NostrServer (with EmptyPolicy) so any test can
  drive a NostrClient against an in-memory relay seeded with arbitrary
  events. Roundtrip verified by LaunchFixtureRelayTest.

* LaunchFixture builds a deterministic 50-note synthetic home-feed
  snapshot from a fixed RNG seed (kind:1 + author kind:0 + kind:3 +
  kind:10002). A real-world JSONL artifact is a drop-in replacement.

* NoteCard gets a stable testTag + a CompositionLocal-backed
  onPlaced hook. Production overhead is one composition-local read
  plus one null check per placement (default
  LocalNoteCardInstrumentation = null).

* LaunchMarkers records named markers against TimeSource.Monotonic.
  LaunchScenario.coldBoot drives the AccountManager (ViewOnly path)
  + DesktopLocalCache + RelayConnectionManager + LocalRelayStore
  stack against the fixture relay and reports t_account_logged_in,
  t_first_event, t_n_events.

* LaunchBenchmark runs 2 warmup + 5 measured iterations, computes
  min/q1/median/q3/max, atomically writes the report file, and is
  skipped by default — opt in via AMETHYST_BENCH=true. Baseline +
  post-fix snapshots committed under desktopApp/benchmarks/.

* SubscribeBeforeConnectTest proves NostrClient / RelayPool queue REQs
  issued before connect() and flush them when the connection comes up.
  The bootstrap-config subscription in Main.kt drops its
  `connectedRelays.first { isNotEmpty() }` + 30s withTimeoutOrNull gate
  on the strength of that invariant — the subscription now fires
  eagerly and recovers when no relay ever connects instead of silently
  giving up after 30s.

All 274 desktopApp tests pass. No flaky tests introduced.
2026-06-18 11:10:14 +03:00
Claude 0c3711ea15 feat(ui): modernize the quote/repost choice popup
Replace the bare row of three flat primary-coloured buttons in the
boost/quote/fork popup with the same card-and-pill treatment used by the
reaction and zap popups: an elevated surfaceVariant card holding pill
chips, each with a coloured leading icon (repost, quote, fork) next to
its label and the soft outline the zap rails use, so the three action
popups read as one family.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016a27kWKUcpADUC1KhXHRpV
2026-06-18 03:54:14 +00:00
Claude a349ebbbc6 feat: reduce sensitivity for bringing back nav and status bar on scroll
When immersive scrolling hides the bottom navigation and the OS status bar,
the small reverse drag a finger makes while catching/stopping a fast scroll
was enough to immediately bring the chrome back.

Two changes make the reveal a more deliberate gesture:

- Damp the reveal direction in DisappearingBarNestedScroll: hiding still
  tracks the finger 1:1, while revealing applies REVEAL_SENSITIVITY (0.5),
  so bringing the in-app bars back needs twice the scroll distance.
- Add hysteresis to the OS status bar toggle: it hides once the chrome is
  fully settled (>= 0.999) but only reappears after the chrome is pulled
  back below STATUS_BAR_SHOW_THRESHOLD (0.7), so a stray reverse reveal no
  longer flips the binary status bar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SN3JhbbvjE7BVKuSRMZmsU
2026-06-18 03:03:29 +00:00
Claude a32b349b04 debug(relay): trace background sub teardown + drop unsubscribe grace to 0
Investigating why backgrounding the app on the all-follows feed leaves
~172 outbox relays connected when only inbox + DM relays (~8) should
remain. The static teardown chain (lifecycle ON_STOP -> unsubscribe ->
client.unsubscribe -> PoolRequests.remove -> RelayPool.updatePool
disconnect) is correct, so this adds runtime tracing at the two decisive
hops to find where it stalls on-device:

- LifecycleAwareKeyDataSourceSubscription: log subscribe/grace-start/
  unsubscribe/dispose with the assembler name (tag BgRelayTrace).
- RelayPool.updatePool: log desired/inPool/toRemove/connected counts.

Also drops UNSUBSCRIBE_GRACE_MILLIS 30s -> 0 as an experiment: if the
grace delay() was being starved on Dispatchers.Default once backgrounded
(Doze/app-standby suspends timers), unsubscribing immediately on ON_STOP
both proves and fixes the leak. To be reverted to a wakelock-safe grace
once confirmed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ukw6FJPFh3JKGXL532p3ae
2026-06-18 02:43:48 +00:00
Claude cb5498ae4c perf(cli): drop Compose UI render stack from the amy runtime image
amy is headless and compiles against zero Compose UI (the Compose deps are
`implementation` in :commons, so they never hit the CLI compile classpath),
but they still rode the runtime classpath into the shipped image — ~29 MB of
Compose desktop render stack, including skiko's native .dylibs that enlarged
the macOS notarization surface.

Exclude skiko + the org.jetbrains.compose UI groups (ui/foundation/material/
material3/animation) from :cli runtimeClasspath. Keep androidx.compose.runtime
(snapshot state + @Stable/@Immutable) — that IS CLI-safe and used by commons
models/state. This avoids the commons → commons/commons-ui module split: the
single-module, feature-cohesive design (commons/ARCHITECTURE.md §1/§3) is
preserved; only the runtime artifact is trimmed.

Result: amy image lib 77 MB -> 48 MB (-38%), and all 4 Compose/skiko notary
dylibs gone (only secp256k1/jna/sqlite natives remain — the ones actually
loaded). A create-release.yml assertion fails the build if the UI stack ever
leaks back.

Verified with the SDK hidden + an offline amy command battery (init/whoami/
--json/relay/marmot/login, plus a real 6-relay key-package round-trip): zero
NoClassDefFoundError/linkage errors; init derives a secp256k1 key cleanly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sso31DfSF9B6EFCVkEqWD
2026-06-18 01:39:08 +00:00
Crowdin Bot 468216ae1b New Crowdin translations by GitHub Action 2026-06-18 00:56:31 +00:00
Vitor Pamplona e1ec0b6823 Adds Bitcoin Sikho as a Designer to the contributors. 2026-06-17 20:51:08 -04:00