Commit Graph

13253 Commits

Author SHA1 Message Date
Vitor Pamplona 7ac70498ac fix(quic-interop): bump HANDSHAKE_TIMEOUT_SEC 10s → 30s for amplificationlimit
The amplificationlimit testcase scenario drops client→server packets
2–7. Recovering past 6 consecutive drops via RFC 9000 PTO doublings
(0.3+0.6+1.2+2.4+4.8+9.6 ≈ 19s) is more than the previous 10s
budget allowed — we declared handshake_failed mid-recovery. Bumping
to 30s matches the multiconnect handshake budget and gives clean
PTO headroom.

Fixes: amplificationlimit ✕→✓ vs picoquic. No regression vs quinn
(was already passing at ~14s). Normal handshakes complete in <1s
so the bump is invisible outside lossy paths.

Still fails: amplificationlimit vs quic-go and msquic. Their
server-side handshake-progress watchdog gives up at ~10s of silence
regardless of our budget. The proper fix is RFC 9002 §6.2.4 — send
2 ack-eliciting packets per PTO probe instead of 1, halving the
recovery time for consecutive drops. That's a writer-side change,
deferred.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 17:06:28 -04:00
David Kaspar 17a040d7ce Merge pull request #2794 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-08 22:57:28 +02:00
Crowdin Bot a4981075c1 New Crowdin translations by GitHub Action 2026-05-08 20:39:42 +00:00
Vitor Pamplona 2b4b978d0e Merge pull request #2795 from vitorpamplona/claude/fix-deprecated-mutex-lock-bRzWb
Rename lock fields to reflect their specific purposes
2026-05-08 16:38:03 -04:00
Claude 9ad4dbc356 fix(quic): replace deprecated lock with split locks in QlogObserverTest
The post-handshake status check uses lifecycleLock (status is guarded
by lifecycleLock per the lock-split refactor); the malformed-datagram
test uses streamsLock since feedDatagram requires streamsLock.
2026-05-08 20:21:26 +00:00
Vitor Pamplona 6854a528d0 Merge pull request #2793 from vitorpamplona/claude/quic-path-validation-dcid-g4Bpj
Implement RFC 9000 §9 client-initiated path validation and DCID rotation
2026-05-08 16:00:26 -04:00
Claude 07ba23a71c fix(quic): second-pass audit fixes for path validation
Three correctness bugs surfaced by post-fix re-audit, plus minor
cleanups.

  - Bug A (validation hang): checkPathValidationTimeoutLocked was
    only called from handlePtoFired. PATH_CHALLENGE is ack-eliciting
    so the peer ACKs it; that ACK resets consecutivePtoCount, which
    means the PTO timer that used to host the budget check stops
    firing. Validation could hang indefinitely on a peer that ACKs
    but doesn't reply with PATH_RESPONSE. Fix: drive the budget
    check from drainOutbound (every send-loop wake).

  - Bug B (stale retire): under abrupt-migration semantics the
    prior CID is abandoned the moment we rotate. Queuing the retire
    only inside applyPathResponse meant two consecutive failed
    validations would leave the original seq=0 unretired forever.
    Fix: queue priorSeq in tryStartValidation; advance
    activeCidSequence at trigger time so it tracks the on-wire DCID.

  - Bug C (spec MUST violation): RFC 9000 §5.1.2 requires server-
    forced retirement of the active CID when the peer's
    retire_prior_to advances past it. Previously the parser silently
    accepted the offer and we kept stamping a now-retired CID.
    Fix: new PathValidator.forceRotateToHigherSequence; called from
    applyPeerNewConnectionIdLocked after a successful Stored result.
    No PATH_CHALLENGE needed (same path, just different CID).
    Closes connection with CONNECTION_ID_LIMIT_ERROR if the pool
    is empty when forced rotation is needed.

Concurrency:
  - Add @Volatile to consecutivePtoCount. The driver kdoc claimed
    it already was; it wasn't. The send-loop reads it lockless for
    backoff calculation while three writers mutate it (driver PTO
    fire, parser ACK reset, applyPeerPathResponseLocked reset).

Cleanup:
  - Drop redundant destinationConnectionId re-stamp in
    applyPeerPathResponseLocked (already rotated at challenge time).
  - Fix PathMigrationResult kdoc to acknowledge that NotConnected
    is produced only by the connection-level wrapper.
  - Update applyPeerNewConnectionIdLocked kdoc with the §5.1.2
    forced-rotation contract.

Tests:
  - PathValidatorTest:
    + triggerRetiresPriorSequenceImmediately (Bug B)
    + twoConsecutiveFailedValidationsRetireAllAbandonedSequences
      (Bug B regression — would have caught the original miss)
    + forceRotateRunsWhenWatermarkPassesActiveCid (Bug C)
    + forceRotateNoOpWhenWatermarkBelowActive (Bug C edge)
    + forceRotateRotatesAgainWhenNewerOfferAdvancesWatermark (Bug C cascading)
  - ClientPathMigrationTest:
    + newConnectionIdWithRetirePriorToPastActiveForcesRotationOnSamePath
      (Bug C wire-level)
    + Updated fullMigrationRoundTrip to assert RETIRE rides in the
      same packet as PATH_CHALLENGE under abrupt-migration semantics.
    + Updated pathResponseWithMismatchingPayloadKeepsValidatingAndDcid
      to reflect activeCidSequence advances at trigger time.

All :quic:jvmTest (39 tests in path-validation suite) and
:nestsClient:jvmTest pass.

https://claude.ai/code/session_01PVVhSQXvw4K4oQ46FzpgaT
2026-05-08 19:57:55 +00:00
Claude 9b9ede2e1e fix(quic): audit fixes for client path validation + DCID rotation
Addresses seven bugs surfaced by post-landing audit of the path
validation feature.

Spec fixes (RFC 9000 §9):
  - Bug 1: PATH_CHALLENGE was going out on the OLD DCID because the
    writer reads conn.destinationConnectionId per packet and the
    rotation only happened on PATH_RESPONSE arrival. Now rotate the
    DCID inside triggerPathMigrationLocked (abrupt-migration model
    appropriate for the "old path looks dead" trigger condition).
    Fixes the headline feature — without this the challenge cannot
    actually exercise the new path.
  - Bug 2: 3 * PTO timeout dropped the failed CID without queuing a
    RETIRE_CONNECTION_ID. The peer kept the routing entry forever.
    checkValidationTimeout now queues the failed sequence per §5.1.2.
  - Bug 3: RETIRE_CONNECTION_ID for seq 0 was silently honored. We
    have no replacement SCID to give the peer (we don't issue our
    own NEW_CONNECTION_ID frames), so the connection is unusable.
    Close with INTERNAL_ERROR instead.
  - Bug 4: triggerPathMigration had no handshake-confirmed gate;
    §9.1 forbids migration before handshake confirmation. Returns
    new PathMigrationResult.NotConnected when status != CONNECTED.

Implementation fixes:
  - Bug 5: driver was calling Clock.System.now() directly instead
    of conn.nowMillis(), breaking virtual-clock tests.
  - Bug 6: PTO threshold check ran BEFORE the consecutive-PTO
    counter increment, so threshold=2 actually required 3 PTOs.
    Increment first; threshold semantics now match the constant.
  - Bug 7: applyPeerPathResponseLocked didn't reset
    consecutivePtoCount on successful validation; the next sleep
    inherited a stale exponential-backoff multiplier even though
    the peer just proved liveness.

Code quality:
  - Rename ValidationOutcome.Validated.newConnectionIdBytes →
    connectionId; PathValidationState.Validating.newCidBytes →
    newConnectionId. The "Bytes" suffix was redundant.
  - Drop unused PathValidator(initialActiveCidSequence) parameter.
  - Drop dead coerceAtLeast(2) in pool size calculation.
  - Make pendingChallenges and pendingRetireSequences internal.
  - Fix stale KDoc references (activatePendingValidatedCid,
    forceRetireActiveIfNeeded, "retirePriorTo decreased" — none
    survived the §19.15 clamp fix).
  - PathValidator.RecordResult: drop RetirePriorToRegressed
    enum value (clamped, never returned).
  - Surface qlogObserver.onConnectionIdRetired in both the
    success and timeout paths.

Tests:
  - ClientPathMigrationTest: existing fullMigrationRoundTrip
    test now asserts DCID rotates AT challenge time, not on
    PATH_RESPONSE.
  - New retireConnectionIdForSequenceZeroClosesConnection.
  - New pathResponseSuccessResetsConsecutivePtoCount.
  - New triggerPathMigrationBeforeHandshakeReturnsNotConnected.
  - PathValidatorTest:
    validationTimeoutAfter3PtoTransitionsToFailedAndRetiresFailedCid
    now asserts the failed sequence is queued for retire.
  - retirePriorToRegressionIsRejected → renamed to
    retirePriorToRegressionIsClampedNotRejected.

All :quic:jvmTest and :nestsClient:jvmTest pass.

https://claude.ai/code/session_01PVVhSQXvw4K4oQ46FzpgaT
2026-05-08 19:37:33 +00:00
Claude 435c49bae9 feat(quic): client-initiated path validation + DCID rotation (RFC 9000 §9)
Implements the client side of connection migration so a path that
stops receiving ACKs (NAT rebind, route flap, dead peer) can be
recovered without a fresh handshake:

  1. NEW_CONNECTION_ID frames from the server are stored in a
     PathValidator pool (was: parsed and dropped).
  2. After PATH_PROBE_PTO_THRESHOLD consecutive PTOs, the driver
     calls triggerPathMigrationLocked(); the validator picks an
     unused CID and queues a PATH_CHALLENGE with a CSPRNG payload.
  3. The writer drains the challenge into the next outbound 1-RTT
     packet using the new DCID; a RecoveryToken.PathChallenge is
     attached so loss recovery can re-queue on packet drop.
  4. Inbound PATH_RESPONSE that byte-equals the outstanding payload
     promotes destinationConnectionId to the new bytes and queues
     RETIRE_CONNECTION_ID for the prior sequence.
  5. RFC 9000 §8.2.4: validation is abandoned after 3 * PTO;
     timeout transitions to PathValidationState.Failed for retry.

Spec coverage:
  - §5.1.1 initial DCID is sequence 0
  - §5.1.2 retire_prior_to enforcement (clamping per §19.15
    reordering rule, force-retire of cached entries below
    watermark)
  - §8.2.2 byte-equal payload match
  - §8.2.4 3 * PTO abandonment
  - §19.15 frame-encoding error checks (retire_prior_to >
    sequence_number, invalid CID/token length)
  - §19.16 RETIRE_CONNECTION_ID frame codec + protocol-violation
    close on retire of an unissued sequence

Observability: QlogObserver gains onPathValidationStarted /
Succeeded / Failed and onConnectionIdActivated / Retired hooks
for qvis sequence diagrams.

Tests: PathValidatorTest (state-machine unit) +
ClientPathMigrationTest (full round-trip through InMemoryQuicPipe:
NEW_CONNECTION_ID -> trigger -> PATH_CHALLENGE -> PATH_RESPONSE ->
DCID rotated + RETIRE_CONNECTION_ID emitted). Existing
PathValidationTest (peer-initiated PATH_CHALLENGE echo) continues
to pass unchanged.

https://claude.ai/code/session_01PVVhSQXvw4K4oQ46FzpgaT
2026-05-08 18:51:34 +00:00
Vitor Pamplona d950be6d73 Merge pull request #2792 from davotoula/fix/imeta-dangling-space
fix(quartz): drop empty/whitespace-only imeta values
2026-05-08 14:05:53 -04:00
davotoula c7ff30a5d5 fix(quartz): drop empty/whitespace-only imeta values
NIP-92 imeta tag entries are encoded as "key SPACE value" strings.
When IMetaTagBuilder.add() received an empty (or whitespace-only)
value the encoder produced "key " with a trailing space, which
schema-validating relays reject as a malformed tag value.
2026-05-08 19:28:11 +02:00
Vitor Pamplona 48b48a8481 fix(quic-interop): summarize-matrix picks newest run dir, not sibling .stdout.log
`ls -1dt run-*` returns both the per-run directories AND the
`.stdout.log` files run-matrix.sh tees alongside them, interleaved by
mtime. `head -n 1` could land on a `.stdout.log` regular file, after
which the rest of the script trying to walk subdirectories under it
silently produced empty output ("no <pair> dir under …"). Walk the
listing instead and pick the first entry that is actually a directory.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 11:04:28 -04:00
Vitor Pamplona 5e3e5cc5a0 chore(quic-interop): auto-patch upstream certs.sh for macOS BSD tr
`LC_CTYPE=C tr -dc '[:alnum:]' </dev/urandom` in the upstream runner's
certs.sh trips "tr: Illegal byte sequence" on macOS — LC_CTYPE alone
doesn't override the runtime locale chain. Only the amplificationlimit
testcase exercises this loop (chain length > 1 → fakedns SAN inflation),
but if it aborts the runner stops the whole matrix BEFORE any later
test in TESTCASES_QUIC runs: handshakeloss, transferloss,
handshakecorruption, transfercorruption, ipv6, v2, rebind-port,
rebind-addr, connectionmigration, and the goodput/crosstraffic
measurements all silently never execute. The post-mortem summary
shows the 12 testcases that ran before the abort and looks deceptively
complete.

Add an idempotent `sed` step to run-matrix.sh that rewrites the line
to `LC_ALL=C tr` on every invocation, plus a plan-file note so the
next person hitting the deceptive summary doesn't repeat the
diagnosis. The patch is a working-tree edit to ../quic-interop-runner/,
not a fork; re-applied on every clone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 11:04:28 -04:00
Vitor Pamplona 5b8bd021b0 fix(quic): exempt retired stream ids from client-initiated squatting guard
The audit-4 #5 guard ran before the existing phantom-stream check, so an
msquic-style aggressive STREAM retransmit on a stream we'd opened and
retired (peer's loss-detector refire racing our FIN-ACK) closed the
connection with STREAM_STATE_ERROR. Observed in the parallel `transfer`
interop test where retransmits on retired streams 0/4 truncated whichever
URL was still mid-receive (5 MB → 2.2 MB).

Add `!isStreamIdRetiredLocked` to the guard so legitimate retransmits
fall through to the existing silent-drop branch. Genuine squatting on
never-opened CLIENT_* ids still closes — the existing FrameRoutingTest
case stays green because id 0 is never put into the retired ring.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 11:04:28 -04:00
Vitor Pamplona abd565a460 Merge pull request #1914 from mstrofnone/desktop-namecoin-port
Port Namecoin NIP-05 resolution to Desktop app
2026-05-08 08:55:09 -04:00
m 9d84d01569 style: spotlessApply formatting + fix icon imports for upstream MaterialSymbols 2026-05-08 22:38:39 +10:00
m 23a7ac4770 fix(namecoin): accept single-identity {nostr:{pubkey,relays}} on d/ names
ifa-0001 doesn't mandate that domain records use the nostr.names
sub-dictionary. Operators who own a name outright commonly publish:

    {"nostr": {"pubkey": "<hex>", "relays": ["wss://..."]}}

(the same shape id/ records use). Before this fix d/-namespace branch
only accepted {"nostr":"<hex>"} or {"nostr":{"names":{...}}},
so a record like d/mstrofnone with the single-identity object form
silently failed with NoNostrField even though id/mstrofnone resolved
fine.

Resolution rules:
  1. nostr.names wins for any sub-identity.
  2. Root lookups fall back to bare pubkey when names["_"] is absent.
  3. Non-root lookups against names-only or single-identity records
     do NOT silently use the bare pubkey.
2026-05-08 22:33:06 +10:00
m 5efead42fb fix(quartz/electrumx): parse NAME_FIRSTUPDATE outputs alongside NAME_UPDATE
Names whose latest on-chain transaction is still the initial registration
(OP_NAME_FIRSTUPDATE = OP_2 = 0x52) were silently dropped because the
parser only matched OP_NAME_UPDATE (OP_3 = 0x53). The scripthash index
returns the FIRSTUPDATE tx in that case, so resolution looked
'unreachable' even though every server answered.

Accept both opcodes when scanning vouts and when parsing the script.
FIRSTUPDATE pushes <name> <rand> <value>, so skip the extra <rand> push
before reading the value.
2026-05-08 22:33:06 +10:00
m 4ab9cae213 fix(desktop): provide LocalNamecoin{Preferences,Service} so search can resolve
The lazy-init declarations and CompositionLocalProvider entries were lost
during the rebase, so SearchScreen always saw null services and skipped
Namecoin resolution. Add them back inside the LoggedIn branch alongside
LocalTorState.
2026-05-08 22:33:06 +10:00
m 50d0e15fe2 fix(desktop): show Namecoin lookup results in search
Re-add the Namecoin results UI block that was lost during the rebase.
Renders Loading/Resolved/NotFound/Error states above bech32/people/note
results when the query is a Namecoin identifier (.bit, d/, id/).
2026-05-08 22:31:46 +10:00
M dca55ebcc4 fix: update renamed APIs (trustAllCerts→usePinnedTrustStore, IRequestListener→SubscriptionListener)
Adapt cherry-picked PR commits to current main where:
- ElectrumxServer.trustAllCerts was renamed to usePinnedTrustStore
- IRequestListener was renamed to SubscriptionListener
2026-05-08 22:31:33 +10:00
M 645bcf8f44 refactor: lazy-load Namecoin services on Desktop (not kept in memory from start)
Move Namecoin service initialization from App-level (eager, on every startup)
to inside the LoggedIn branch (only created when user logs in and screens
that use Namecoin are reachable). Matches the Android lazy pattern in AppModules.

Also deduplicate NamecoinSettings: Android module now uses a typealias to the
commons module version, matching the original PR intent.
2026-05-08 22:31:33 +10:00
M 5369694b4d refactor: move NamecoinSettings and NamecoinResolveState to commons module
Eliminates code duplication between Android and Desktop:

- Move NamecoinSettings to commons/model/nip05DnsIdentifiers/namecoin/
  (with @Serializable and @Stable annotations)
- Extract NamecoinResolveState sealed class to its own file in commons
- Move NamecoinSettingsTest to commons (shared by both platforms)
- Replace Android NamecoinSettings.kt with a typealias to commons
- Update all imports in Desktop and Android modules
- Remove debug println statements from ImportFollowListDialog and Main
2026-05-08 22:31:33 +10:00
M 0b636d62aa Fix follow list import: validate pubkey length + add crash monitoring
Root cause: decodePublicKeyAsHexOrNull() returns truncated hex for
non-bech32 input (its else branch runs Hex.decode on arbitrary strings).
The relay then rejects the filter with 'Invalid author length'.

Fixes:
- Check raw 64-char hex FIRST (before bech32 parsing)
- Only attempt bech32 decode for strings starting with npub1/nprofile1/nsec1
- Validate all resolved pubkeys are exactly 64 hex chars
- Show specific error messages per identifier type instead of generic fallback
- Improved debug logging with pubkey prefix for each resolution path

Crash monitoring:
- Added Thread.setDefaultUncaughtExceptionHandler in Main.kt
- Logs crashes to ~/.amethyst-desktop-crash.log with timestamp and full stack trace
- Also prints to stderr for Gradle console visibility
2026-05-08 22:31:33 +10:00
M 0bc1c2838d desktop: full relay-integrated Import Follow List dialog
Rewrites ImportFollowListDialog with complete relay integration:

- Resolves identifiers: npub, hex, NIP-05 HTTP (via OkHttp), Namecoin
- Fetches kind 3 (ContactListEvent) from relays via subscription
- Parses follow list (p-tags) into selectable entries
- Fetches kind 0 metadata for display names (best-effort)
- Shows preview with select all/deselect all + individual toggles
- Publishes new kind 3 via broadcastToAll with selected follows
- State machine: Idle → Resolving → Fetching → Loaded → Publishing → Done
- Proper cleanup: DisposableEffect unsubscribes on dismiss
- 15s timeout for kind 3, 20s timeout for metadata enrichment
- Updates Main.kt call site to pass relayManager, account, localCache
2026-05-08 22:31:33 +10:00
M 21133c34c0 Fix search: use resolveDetailed() for proper timeout handling
The search bar was using resolve() which lets
NamecoinLookupException.ServersUnreachable propagate as an exception,
causing 'servers unreachable' to appear immediately instead of waiting
for the actual lookup to complete.

Switch to resolveDetailed() which catches exceptions internally and
returns typed outcomes (Success/NameNotFound/NoNostrField/
ServersUnreachable/InvalidIdentifier/Timeout). The search screen now
shows Loading for the full duration of the attempt (up to 20s) and
only shows the error after all servers have actually been tried.
2026-05-08 22:31:12 +10:00
M a7c4842d60 Wire ImportFollowListDialog into File menu (⇧⌘I)
The dialog was created but never accessible from the UI. Now:
- Added 'Import Follow List…' menu item in File menu (Shift+Cmd+I / Shift+Ctrl+I)
- showImportFollowListDialog state threaded through App composable
- Dialog rendered alongside ComposeNoteDialog and AddColumnDialog
2026-05-08 22:30:55 +10:00
Vitor Pamplona ab09c520ce Merge pull request #2758 from mstrofnone/feat/nip9a-community-rules
feat(quartz): NIP-9A community rules parser + validator (kind:34551)
2026-05-08 08:30:50 -04:00
Vitor Pamplona ce42a36882 Merge pull request #2780 from mstrofnone/fix/desktop-sidebar-scrollable
fix(desktop): make the single-pane navigation rail scrollable
2026-05-08 08:30:01 -04:00
M 5602429f9b Port Namecoin NIP-05 resolution to Desktop app
Add censorship-resistant NIP-05 verification using the Namecoin blockchain
to the Desktop (JVM) app, porting functionality from PRs #1734, #1771,

New files:
- DesktopNamecoinNameService: app-level service wrapping the Quartz
  NamecoinNameResolver with caching, custom server support, and live
  state flows. Uses plain JVM sockets (no Tor support on Desktop yet).
- DesktopNamecoinPreferences: Java Preferences API-backed persistence
  for Namecoin settings (enabled toggle + custom ElectrumX servers).
- NamecoinSettings: Desktop copy of the settings data class (no Android
  dependencies).
- LocalNamecoin: CompositionLocals for threading Namecoin service/prefs
  through the compose tree.
- NamecoinSettingsSection: Compose Desktop UI for configuring ElectrumX
  servers — toggle, active server display with DEFAULT/CUSTOM badge,
  add/remove custom servers, reset to defaults. Uses onPreviewKeyEvent
  for Enter-to-submit instead of Android KeyboardActions.
- ImportFollowListDialog: Dialog for importing follow lists via npub,
  hex, NIP-05, or Namecoin identifiers. Resolves identifiers to pubkeys
  with Namecoin blockchain support.

Modified:
- Main.kt: Instantiate DesktopNamecoinPreferences and
  DesktopNamecoinNameService in App composable, provide via
  CompositionLocals, wire into RelaySettingsScreen.
- SearchScreen.kt: Detect Namecoin identifiers (.bit, d/, id/) in the
  search bar, resolve via DesktopNamecoinNameService with loading/error
  states, display resolved user above standard results. Uses
  LaunchedEffect with key-based cancellation for stale lookups.
- DeckColumnContainer.kt: Pass namecoinPreferences to RelaySettingsScreen
  from CompositionLocal.

Tests:
- DesktopNamecoinPreferencesTest: round-trip persistence, add/remove
  servers, enable/disable, reset, duplicate handling.
- NamecoinSettingsTest: server string parsing/formatting, round-trips,
  edge cases, toElectrumxServers conversion.

The Quartz KMP library (commonMain + jvmAndroid) already contains the
core Namecoin resolution code (ElectrumXClient, NamecoinNameResolver,
NamecoinLookupCache) shared across Android and Desktop.
2026-05-08 22:29:23 +10:00
m 01f6c1c24b fix(desktop): make the single-pane navigation rail scrollable
The left navigation rail in single-pane mode renders a fixed list of
pinned screens (Home, Reads, Notifications, ...) plus a 'More' launcher
and a stack of bottom controls (relay health, bunker heartbeat, tor
status, account switcher).

Material3 `NavigationRail` lays its children out in a non-scrollable
`Column`. When the window is short — either because the OS window is
small or the user pinned several screens — the bottom items in the
list (and the 'More' button) get clipped and become unreachable.

Replace the `NavigationRail` with a `Column` that mirrors the rail's
container styling and splits content into two regions:

- A scrollable region (weight(1f) + verticalScroll) holding the pinned
  screens and the 'More' launcher. Overflow now scrolls instead of
  clipping.
- A fixed bottom region holding the relay health indicator, bunker
  heartbeat, tor status indicator, and the account switcher. These
  remain anchored at the bottom of the rail.

Item visuals are preserved by keeping `NavigationRailItem` for the
items themselves, with `NavigationRailItemDefaults.colors()`.

No behavior change when the rail content already fits the window.
2026-05-08 22:28:17 +10:00
Vitor Pamplona 36112c8b11 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-05-08 08:25:49 -04:00
Vitor Pamplona 2097089d3d Merge pull request #2788 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-08 08:20:44 -04:00
Vitor Pamplona 15e93c8838 spotless apply 2026-05-08 08:18:28 -04:00
Crowdin Bot 7dc9adb856 New Crowdin translations by GitHub Action 2026-05-08 12:17:53 +00:00
Vitor Pamplona f601836bc3 Merge pull request #2787 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-08 08:16:48 -04:00
Vitor Pamplona 753f259959 Merge pull request #2786 from greenart7c3/claude/add-blossom-cache-support-ts8mK
Add local Blossom cache bridge for transparent media proxying
2026-05-08 08:15:51 -04:00
Vitor Pamplona 06d6425369 Merge branch 'main' into claude/add-blossom-cache-support-ts8mK 2026-05-08 08:15:44 -04:00
Crowdin Bot 4ae51ca8bb New Crowdin translations by GitHub Action 2026-05-08 12:15:30 +00:00
Vitor Pamplona 8b1b49007c Merge pull request #2785 from greenart7c3/claude/disable-client-tags-option-Nb3b4
Add option to disable NIP-89 client tag in published events
2026-05-08 08:13:49 -04:00
Vitor Pamplona e8e1e44807 Merge pull request #2783 from greenart7c3/claude/add-copy-address-button-LgZSZ
Add copy-to-clipboard functionality for payment targets
2026-05-08 08:13:26 -04:00
Vitor Pamplona 90b98fa5f4 Merge pull request #2782 from davotoula/fix/scheduled-posts-lint-fixes
Lint and sonar cleanup on scheduled-posts
2026-05-08 08:13:17 -04:00
Vitor Pamplona 317b205858 Merge pull request #2781 from mstrofnone/fix/selectable-error-messages
fix(desktop): make error messages selectable so users can copy them
2026-05-08 08:12:49 -04:00
Vitor Pamplona fcb5b8cc55 Merge pull request #2779 from davotoula/fix/plurals-use-format-arg
Various language fixes to scheduled posts
2026-05-08 08:12:04 -04:00
Vitor Pamplona 0127ed4412 Merge pull request #2778 from nrobi144/fix/bunker-timeout-and-decrypt
feat(desktop): custom feeds system with creation, discovery, and author search
2026-05-08 08:11:55 -04:00
David Kaspar 3fb04bd6f2 Merge branch 'main' into fix/plurals-use-format-arg 2026-05-08 13:40:34 +02:00
David Kaspar ce9e83b791 Merge pull request #2784 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-05-08 13:40:07 +02:00
Claude bb871f6c71 fix(blossom): preserve upstream path prefix in xs= hint
The bridge was emitting `xs=https://cdn.nostr.build` for URLs like
`https://cdn.nostr.build/i/<sha>.jpg`, dropping the `/i/` prefix. The
local cache appends `/<sha>` to the xs server hint per spec, so it would
try to fetch `https://cdn.nostr.build/<sha>` — a 404 on nostr.build's
non-Blossom CDN scheme.

Anchor the server-base extraction on the slash immediately preceding the
sha-bearing path segment so we emit `xs=https://cdn.nostr.build/i`.
The cache then appends `/<sha>` and reaches the original blob.

Affects all three bridge entry points:
- MediaUrlContent.toCoilModel (note media)
- bridgeProfilePictureUrl (profile pictures)
- LocalBlossomCacheRedirectInterceptor (everything else via OkHttp)

Flat Blossom paths (`<host>/<sha>`) keep emitting `xs=<host>` unchanged.
2026-05-08 11:35:53 +00:00
Claude aa598b9949 fix(blossom): profile pictures now route through local cache
bridgeProfilePictureUrl previously returned a `blossom:` URI, but
profile pictures go through ProfilePictureFetcher (Coil routes by type
on `ProfilePictureUrl`), which feeds the URL straight to NetworkFetcher
and never sees BlossomFetcher. NetworkFetcher then tries to issue an
HTTP request against the `blossom:` scheme and fails — silently — so
profile pictures from Blossom servers stopped loading.

Return a direct `http://127.0.0.1:24242/<sha>.<ext>?xs=<host>&as=<pub>`
URL instead. The OkHttp request goes to the local cache normally; the
new redirect interceptor sees it's already on localhost and passes
through unchanged.
2026-05-08 10:25:30 +00:00
Claude 9451f7ca9a feat(blossom): catch all sha256-keyed URLs via OkHttp interceptor
Pictures hosted on Blossom servers (URLs with a 64-char sha256 hex in
the path) bypassed the bridge whenever they were rendered through a
composable that wasn't explicitly updated to call toCoilModel — link
previews, video thumbnails, badge images, audio room covers, etc.

Add LocalBlossomCacheRedirectInterceptor as an app-level OkHttp
interceptor that catches every HTTP request transparently:

- Detects a 64-char hex segment in any path segment
- Rewrites the request URL to http://127.0.0.1:24242/<sha>.<ext>?xs=<host>
- Coil's disk cache continues to key by the original URL so cached
  blobs survive toggling the bridge off

Activates only when the master toggle is on, the profile-pictures-only
restriction is off, and the probe sees localhost up. Profile-only mode
still routes profile pics via the existing composable bridge.
2026-05-08 10:02:08 +00:00