Commit Graph

13436 Commits

Author SHA1 Message Date
Claude cfa6899c65 docs: cross-link CONTRIBUTING.md from README and add PR template 2026-05-11 13:42:22 +00:00
Claude ee20ab6ca4 docs(contributing): document interop suites and issue-template practice 2026-05-11 13:25:13 +00:00
Claude 9c39af718b docs: add CONTRIBUTING.md with proof-of-testing rule for new contributors 2026-05-11 13:06:35 +00:00
Vitor Pamplona 89c711ca9f lint 2026-05-11 08:50:05 -04:00
Vitor Pamplona 1ff4f98fff docs(quic-interop): add quinn to the default peer set
quinn is now treated as a flawless-required interop target alongside
aioquic, picoquic, and quic-go. Most Rust-based Nostr/MoQ relays our
users run their servers on are built on quinn, so an interop regression
there is a user-visible regression.

Validated by a 3-round flakiness sweep (1 full matrix + 2 audio-critical
subsets) — zero result flakiness across 528 test executions across all
four peers, with two environmental docker-compose stall classes documented
separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 08:44:15 -04:00
Vitor Pamplona 2ddf59fc5f Merge pull request #2838 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-11 08:39:59 -04:00
Crowdin Bot a8aaf48c8b New Crowdin translations by GitHub Action 2026-05-11 12:32:31 +00:00
Vitor Pamplona 850345a1f2 Merge pull request #2831 from skotchdopolepet/codex/two-stage-zap-progress
Show two-stage zap progress
2026-05-11 08:30:21 -04:00
Vitor Pamplona 94125d6001 Merge pull request #2830 from skotchdopolepet/codex/report-warning-threshold
Configure report warning threshold
2026-05-11 08:27:13 -04:00
Vitor Pamplona 57807e393b Merge pull request #2828 from skotchdopolepet/codex/fix-hidden-dm-unread-dot
Fix hidden DM unread badge after blocking users
2026-05-11 08:23:07 -04:00
Vitor Pamplona f184f715b1 Merge pull request #2834 from coreymull/gif-keyboard-chat-upload
Add GIF keyboard uploads to chat composers
2026-05-11 08:19:54 -04:00
Vitor Pamplona d6b7cd456a Merge pull request #2841 from nrobi144/feat/desktop-embedded-local-relay
feat(desktop): embedded local relay with SQLite event persistence
2026-05-11 08:16:17 -04:00
nrobi144 2b96e83264 feat(desktop): add embedded local relay with SQLite event persistence
Add an in-process local relay to Amethyst Desktop that persists all
received events to a per-account SQLite database using quartz's existing
EventStore infrastructure. On startup, the local store hydrates
DesktopLocalCache for instant feed rendering before remote relays connect.

- LocalRelayStore: manages per-account EventStore lifecycle, batched
  write-through via BasicBundledInsert (250ms window), startup hydration
  (contact list -> metadata -> recent content events)
- LocalRelayMaintenance: periodic cleanup (NIP-40 expiration, 30-day
  prune, weekly VACUUM), disk space monitoring
- Settings UI: integrated into RelaySettingsScreen with statistics,
  storage management (prune/vacuum/clear), JSONL export/import, and
  error display
- OfflineBanner: animated banner in both SinglePaneLayout and
  DeckColumnContainer showing offline status with local cache indicator
- Thread-safe store access via @Volatile + synchronized lock
- Skips re-enqueue during hydration (checks LOCAL_RELAY_URL)
- DB stored at ~/.amethyst/accounts/<pubkey8>/events.db
- Corrupt DB auto-detected and recreated on open

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-11 11:35:39 +03:00
David Kaspar 17b6bba9fb Merge pull request #2824 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-11 08:10:02 +02:00
Corey Mull e38902fb66 Wire keyboard image input into chat uploads 2026-05-11 00:26:05 -04:00
skotchdopolepet 6b9b5cfe9b feat: show two-stage zap progress 2026-05-11 04:38:26 +02:00
skotchdopolepet ece6a4acf6 feat: configure report warning threshold 2026-05-11 04:32:28 +02:00
skotchdopolepet 2a4ecade39 fix: ignore hidden DMs for unread message badge 2026-05-11 04:18:49 +02:00
Claude 7e2e3304e1 refactor(RichTextViewer): move isMarkdown onto RichTextViewerState
Cleaner conceptual model: the parsed state object is the place where
all derived facts about the content live, so `isMarkdown` becomes a
field on `RichTextViewerState` (populated by `RichTextParser.parseText`
using the same cheap heuristic).

`RichTextViewer` now calls `CachedRichTextParser.parseText` once at the
top and dispatches on `state.isMarkdown` instead of running a separate
scan. `CachedRichTextParser.isMarkdown(content)` and its dedicated
`isMarkdownCache` go away — the single `richTextCache` carries the
decision alongside the parsed segments.

Inner callers (`DisplaySecretEmoji`, `MultiSetCompose` reaction
preview, `DisplayUncitedHashtags`) are unaffected: they continue to
receive a fully-parsed state with all segments populated.
2026-05-11 00:25:36 +00:00
Claude 680e5bc0be perf(RichTextViewer): share isMarkdown decision via CachedRichTextParser
The markdown check is purely a function of `content`, but it was a
top-level function in RichTextViewer.kt — every distinct instance of
the same content (e.g. a quoted note rendered inside its quoter,
or the same viral note shown multiple places in a feed) re-scanned
the string.

Move the decision into `CachedRichTextParser` with its own small
LRU keyed on content.hashCode(). Composables still wrap the call in
`remember(content)` so recompositions skip even the LRU lookup.
2026-05-10 22:34:48 +00:00
Claude c66acfa131 perf(RelayBadges): one sampled flow per note instead of three
`RelayBadges` runs once per note in complete-UI mode. The closed view
was opening three separate `relays.stateFlow` subscriptions (one per
icon slot, each with its own `mapNotNull`), and both the closed and
expanded views collected the relay list raw — every relay arrival on
a fanned-out note triggered an immediate recomposition.

Collapse to a single subscription that emits `relays.take(3)`,
throttled with `sample(500)` and `distinctUntilChanged`. Slot widgets
now pull from the resulting list instead of each owning a flow.

Same treatment for the expanded `RenderAllRelayList` (now sampled)
and `ShouldShowExpandButton` (wraps the cached
`createMustShowExpandButtonFlows` lookup in `remember(note)` so the
LRU is hit once per note instead of per recomposition).
2026-05-10 21:45:54 +00:00
Claude 18c1ab2ece perf(NoteCompose): cut per-item allocations during feed scroll
Three targeted fixes for the jitter that shows up as soon as a feed
contains nested NoteCompose (reposts, quotes, BechLink previews):

* `calculateBackgroundColor`: only schedule the 5s "new item" fade
  LaunchedEffect when the item is actually new and tracks read state.
  Inner notes pass `routeForLastRead = null` and were parking a
  coroutine for 5s per item, every scroll. Also drop a per-call
  `Color.copy(alpha = 0f)` allocation in favor of `Color.Transparent`.

* `EventObservers` + `WatchBlockAndReport`: wrap the `StateFlow`
  lookups in `remember(note)` so each recomposition of the same note
  doesn't re-resolve `note.flow().…stateFlow` (and, in
  `WatchBlockAndReport`, hit the synchronized LRU lookup) on every
  pass. Affects observeNote / Replies / Reactions / Zaps / Reposts /
  Ots / Edits and the per-item hidden-flow check.

* `produceCachedState` / `produceCachedStateAsync`: short-circuit on
  cache hits. The previous `produceState` body always launched a
  coroutine even when the LRU already had the value; this is the
  common path for BechLink previews and draft notes during scroll.
  Now we read the cache synchronously inside `remember`, and only
  launch a `LaunchedEffect` for the actual miss.

No behavior change.
2026-05-10 20:47:03 +00:00
Crowdin Bot b3424a01ad New Crowdin translations by GitHub Action 2026-05-10 20:14:59 +00:00
davotoula ac08c27531 i18n: translate favorite DVMs empty state into cs-rCZ, pt-rBR, sv-rSE, de-rDE
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:12:31 +02:00
Vitor Pamplona 40dcfa2004 Merge pull request #2820 from davotoula/fix/bottom-nav-favourite-feeds-icon
Align favourites icon, add to default bar, redesign empty state
2026-05-10 15:56:14 -04:00
Vitor Pamplona 70188c1c22 Merge pull request #2822 from davotoula/feat/update-translations-and-find-missing-translations-skill
Update translations and the find missing translations skill
2026-05-10 15:55:59 -04:00
davotoula 41faf0525e Swap feed icon and notification icon in bottom bar: restore Notification icon is last 2026-05-10 21:30:19 +02:00
David Kaspar 67529e37ca Merge pull request #2823 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-10 21:14:11 +02:00
Crowdin Bot a1b7d8543c New Crowdin translations by GitHub Action 2026-05-10 18:23:40 +00:00
Vitor Pamplona ace021202e Merge pull request #2821 from davotoula/fix/hls-imeta-blurhash-thumbhash
Rich imeta on every published HLS event + auto-published kind:1 sibling
2026-05-10 14:22:01 -04:00
davotoula 85d768d395 i18n: drop stale %1$s placeholder from translations to fix StringFormatCount 2026-05-10 15:02:06 +02:00
davotoula 9a93b3e789 i18n: translate NIP-9A community rules and Blossom cache strings 2026-05-10 12:17:53 +02:00
davotoula 7f54a2645f docs(skills): update skill to warn on plural-shaped strings in find-missing-translations 2026-05-10 12:17:31 +02:00
davotoula 0a58ca3320 fix(l10n): drop orphaned favorite_dvms_empty key from 10 locale files 2026-05-10 10:46:42 +02:00
davotoula 99e0f2403d chore(translations): remove orphan hls_draft_note_* keys across locales 2026-05-10 10:15:21 +02:00
davotoula 7a77e3f5ee fix(richtext): recognize HLS MIME types as video in createMediaContent + propagate imeta image field to MediaUrlVideo.artworkUri
Code review:
- simplify result types and shared helpers
- propagate CancellationException from poster path + cover sibling
2026-05-10 09:40:00 +02:00
davotoula 48d08f5895 feat(hls): auto-publish kind:1 sibling note with rich imeta 2026-05-10 09:37:50 +02:00
davotoula 7550e82283 feat(hls): emit blurhash and thumbhash on every NIP-71 video imeta 2026-05-10 09:37:37 +02:00
davotoula f594589736 Code review:
refactor(discover): restore exhaustive when in DiscoverTab.toTabIndex
refactor(discover): collapse DiscoverTab.toTabIndex to ordinal and drop redundant pager guards
2026-05-10 09:24:18 +02:00
davotoula f20971348c use AutoAwesome icon for favourite feeds and add to default bottom bar
redesign empty state with CTA that deep-links to Discover algos tab
2026-05-10 09:24:18 +02:00
David Kaspar ddff04ec88 Merge pull request #2818 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-09 21:53:29 +02:00
Crowdin Bot 328cf0a8dd New Crowdin translations by GitHub Action 2026-05-09 16:44:28 +00:00
Vitor Pamplona b0ce0c1770 Merge pull request #2817 from davotoula/fix/gallery-forward-thumbhash
fix(gallery): forward imeta hashes through gallery
2026-05-09 12:42:57 -04:00
davotoula 61d52cd9db fix(uploads): surface blurhash/thumbhash generation failures
processBitmap silently swallowed any exception from bitmap.toBlurhash()
or bitmap.toThumbhash() via runCatching{...}.getOrNull(), making it
impossible to diagnose why a video upload's published imeta had blurhash
but no thumbhash (or neither). Add an .onFailure { Log.w(...) } to each
runCatching so the actual exception class + stack reach logcat.
2026-05-09 18:30:33 +02:00
davotoula fb7a30fd70 fix(gallery): forward imeta visual fields through video player to gallery
When the user long-pressed a playing video and tapped "Add Media to
Gallery", the share-action overlay (RenderTopButtons.kt:302) hard-coded
blurhash/dim/hash to null and built an inline MediaUrlVideo without
those fields. Result: the published ProfileGalleryEntryEvent (kind 1163)
carried only the URL, alt, e, m, and client tags — no x/dim/blurhash/
thumbhash — even when the source kind:1 imeta carried them.
2026-05-09 18:29:55 +02:00
davotoula 3cbc4fcec6 fix(gallery): forward thumbhash when adding media to gallery 2026-05-09 18:29:32 +02:00
Vitor Pamplona 86c91a8ddc Merge pull request #2814 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-09 11:55:08 -04:00
Crowdin Bot fbd1b69051 New Crowdin translations by GitHub Action 2026-05-09 15:49:52 +00:00
Vitor Pamplona 0bf0077aa5 fix(quic): emit ACK_ECN with all-zero counts (RFC 9000 §13.4.2)
Upstream commit df6103ffdd ("truthful ECN reporting") replaced
`AckEcnCounts(0, 0, 0)` on 1-RTT ACK frames with
`ecnCounts = null`, on the rationale that hardcoded zero counts
were "lying" while we marked outbound ECT(0) but didn't read
inbound TOS.

That broke the interop runner's `ecn` testcase against quinn:
the runner's `_check_ack_ecn` requires at least one ACK_ECN
frame in the trace (`hasattr(p["quic"], "ack.ect0_count")`),
and explicitly says "we only check whether the trace contains
any ACK-ECN information, not whether it is valid". Without
the field present the runner reports "Client did not send any
ACK-ECN frames" and fails. 0/3 against quinn (was 19/22).

Re-emit `AckEcnCounts(0, 0, 0)` with corrected reasoning:

  - We mark outbound packets ECT(0) (peers' tracking benefits).
  - We don't read inbound TOS (JDK DatagramChannel needs JNI for
    `IP_RECVTOS`, deferred). So we observe ZERO ECT-marked
    inbound packets.
  - Reporting 0 counts IS accurate to that observation. RFC 9000
    §13.4.2 doesn't penalize a path that never sees marks; it
    only requires the count be a "cumulative count of received
    QUIC packets that were marked with the corresponding ECN
    codepoint" — which is exactly 0 under our limitation.

The Initial / Handshake-space ACKs stay plain (RFC 9000 §13.4
forbids ECN on long headers); only application-space gets the
ECN counts.

Verified end-to-end:
  - quinn ecn: 3/3 (was 0/3 post-rebase, 1/1 pre-rebase).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 11:40:36 -04:00
Vitor Pamplona 0920aba2b8 fix(quic): issue spare source CIDs to peer post-handshake-confirmed
RFC 9000 §5.1.1 + §19.15: a client SHOULD advertise spare source
CIDs to the peer via NEW_CONNECTION_ID frames so the peer has
DCIDs available for path migration / NAT rebind. Strict server
stacks (quic-go, picoquic, msquic, mvfst) refuse to validate a new
client path until they have a spare DCID — server log shows
"skipping validation of new path … since no connection ID is
available". Quinn migrates on src-port-change alone, which is why
rebind-port / rebind-addr passed against quinn pre-fix and failed
against the others.

Adds:
  - IssuedSourceConnectionIdEntry (data class) — sequence + CID +
    stateless-reset token tuple.
  - QuicConnection.issuedSourceConnectionIds (LinkedHashMap) — pool
    of currently-active issued SCIDs, seeded with seq=0 (the
    initial CID, no token because the stateless_reset_token TP is
    server-only per RFC 9000 §18.2).
  - QuicConnection.issueOwnConnectionIdsLocked(count) — drives the
    initial issuance once handshake is confirmed; appends recovery
    tokens to pendingOwnNewConnectionIdEmits for the writer to emit.
  - QuicConnection.issueOwnReplacementSourceCidLocked() — top-up
    after each peer RETIRE_CONNECTION_ID so the pool stays at
    full capacity for repeated migrations.
  - QuicConnectionWriter — once handshakeConfirmed flips, calls
    issueOwnConnectionIdsLocked with `peer.activeConnectionIdLimit
    - 1` (cap 7); drains pendingOwnNewConnectionIdEmits as fresh
    NEW_CONNECTION_ID frames; the existing pendingNewConnectionId
    map continues to handle loss-recovery retransmits.
  - applyPeerRetireConnectionIdLocked — three cases: in-pool seq
    is freed and replaced; below-next-seq missing seq is benign
    retransmit; above-next-seq is PROTOCOL_VIOLATION (peer
    retiring something we never advertised).

Verified against the live interop runner:
  - quic-go rebind-port: 2/2 (was 0/N — Task 2). Now passes in ~64s.
  - quic-go rebind-addr: 2/2 (was 0/N). Now passes in ~125-138s.
  - picoquic rebind-port: 2/2.
  - picoquic rebind-addr: 1/2 (flaky — separate investigation;
    one run the connection migrates correctly, the other times
    out at 110s; not blocking the rebind-port / quic-go win).
  - picoquic connectionmigration: 0/2 still failing (the runner's
    "active migration" testcase exercises a more sophisticated
    migration shape — TBD; tracked separately).

Tests: IssuedSourceConnectionIdTest (5 cases pinning the
post-handshake emission, peer-RETIRE replacement, retransmit
tolerance, and protocol-violation closure for above-next-seq retires).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 11:40:36 -04:00