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.
`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>
`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>
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>
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.
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.
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.
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/).
Adapt cherry-picked PR commits to current main where:
- ElectrumxServer.trustAllCerts was renamed to usePinnedTrustStore
- IRequestListener was renamed to SubscriptionListener
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.
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
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
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.
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
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.
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.
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.
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.
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.
Aligns the toggle with its Security Filters siblings (warnAboutPostsWithReports,
filterSpamFromStrangers, etc.): stored in AccountSecurityPreferences{Internal},
synced across devices via the app-specific data event, and updated through
account.updateDisableClientTag -> sendNewAppSpecificData.
The signer wrapper now reads the live value from synced settings, so changes
still apply immediately to the next signed event.
https://claude.ai/code/session_01KhNVTm8LLdVphqyxVkZtDS
- `as=<authorPubKey>`: when converting plain http(s) imeta URLs into
`blossom:` URIs we now include the note author's pubkey, letting the
local cache consult their kind:10063 BUD-03 server list on miss.
Plumbed via RichTextParser/CachedRichTextParser/RichTextViewer/
ExpandableRichTextViewer/TranslatableRichTextViewer.
- Profile-pictures-only mode: a new per-account toggle that restricts
the bridge to profile pictures (RobohashFallbackAsyncImage). When on,
feed images and videos skip the bridge entirely. Profile pictures
recover their sha256 from the URL path when present (covers
Blossom-hosted avatars like https://nostr.build/i/<sha>.jpg).
Adds a "Don't add client tag to my events" switch under Security Filters.
NostrSignerWithClientTag now consults a runtime predicate at sign-time, so
the toggle takes effect immediately on the live session without rewrapping
the signer or rebuilding Account.
https://claude.ai/code/session_01KhNVTm8LLdVphqyxVkZtDS