The local Docker interop stack builds moq-relay from a `./moq` checkout
that NostrNestsHarness cloned at unpinned `main` (~0.10.25+), while
nostrnests's moq-auth sidecar mints tokens for moq-relay 0.10.10 — the
version nostrnests pins in their own nests-relay/Dockerfile.relay. The
JWKS/JWT-signature handling drifted across those releases, so the relay
rejected every connection with `failed to decode the token` even though
the QUIC/TLS/WebTransport handshake completed cleanly.
Pin DEFAULT_MOQ_REVISION to the moq-relay-v0.10.10 release tag so the
harness builds the relay moq-auth was written against, and fetch tags so
the tag is checkout-able on a pre-existing clone.
https://claude.ai/code/session_01PoQupfdoKU3ryQwLwTeXeM
A stalled/failed QUIC handshake gave no hint whether the socket connected
over IPv4 or fell into the ::1 blackhole. The HandshakeFailed messages
now carry `socket=<addr>:<port> sni=<host>` so the failure itself shows
which address resolvePreferIpv4 picked.
https://claude.ai/code/session_01PoQupfdoKU3ryQwLwTeXeM
After switching the dev harness to `localhost`, the QUIC handshake still
stalled and the relay logged nothing at all — packets weren't reaching
it. `localhost` resolves to ::1 first on most systems, and IPv6 loopback
isn't reliably routable (Docker's IPv6 UDP port-forwarding silently
blackholes), so the UDP socket was connecting into a hole.
QuicWebTransportFactory.connect now resolves the authority host to a
concrete address preferring IPv4 for the UDP socket target, while still
passing the original hostname as the TLS SNI server name — so SNI keeps
matching the server certificate and stays a legal RFC 6066 host_name.
https://claude.ai/code/session_01PoQupfdoKU3ryQwLwTeXeM
Three findings from the local nests interop hang (relay logged "Illegal
SNI extension: ignoring IP address presented as hostname"):
- TlsClientHello sent the connect host verbatim as TLS SNI, including
IP literals. RFC 6066 §3 forbids IP literals in server_name; a strict
relay (rustls) discards the extension, ends up with no SNI to select
its certificate on, and the handshake stalls. Both ClientHello
builders now omit server_name when the host is an IP literal
(new isIpLiteralHostname predicate).
- QuicWebTransportFactory.connect never bounded awaitHandshake() — only
readConnectResponse had a timeout — so a stalled handshake hung
connect() indefinitely. It now fails fast after connectTimeoutMillis
with a message naming the likely cause.
- NostrNestsHarness pointed moqEndpoint at 127.0.0.1, which forced the
IP-literal SNI path against the dev relay. Switched to localhost: a
valid hostname that matches the relay's generated dev cert and still
resolves to loopback.
https://claude.ai/code/session_01PoQupfdoKU3ryQwLwTeXeM
QuicWebTransportFactory threw `:status=0` for every distinct CONNECT
failure mode — relay never answered, relay FIN'd the request bidi with
no H3 response, relay sent a HEADERS frame lacking :status, or the whole
connection was torn down by a CONNECTION_CLOSE. The four cases need
different fixes but produced identical, undiagnosable errors.
readConnectResponse now records bytes/H3-frames seen on the request
stream and reports which of those cases occurred; the thrown exception
also carries a connection-state snapshot (conn.status, closeReason,
closeErrorCode, requestStreamClosed) captured before driver.close(), so
a relay CONNECTION_CLOSE (e.g. H3_SETTINGS_ERROR from a rejected SETTINGS
frame) is distinguishable from a request-stream-only close.
https://claude.ai/code/session_01PoQupfdoKU3ryQwLwTeXeM
SendTraceScenario already swept framesPerGroup and recorded per-frame
send durations + arrival wall-times, but never correlated them — so it
could verify frame delivery but not the send-side latency a listener
perceives. Record each frame's send wall-clock timestamp and derive the
true send→arrival latency (time-to-first-frame + p50/p99/min/max) per
subscriber. With framesPerGroup > 1 this surfaces the batching window
(~framesPerGroup * cadence) as the latency floor.
Add framesPerGroup=10 and =50 (the production default) sweep entries to
both the prod and local-harness suites so the batching-vs-latency
tradeoff curve is visible in one run.
https://claude.ai/code/session_01PoQupfdoKU3ryQwLwTeXeM
An addressable event whose `a` tags reach itself — directly (self-mention)
or via any cycle of other in-cache events — would recurse unbounded
through `AddressHintProvider` / `EventHintProvider` branches and crash
with StackOverflowError on publish (`signAndComputeBroadcast` →
`computeRelayListToBroadcast`). The crash was reproducible on the device
when publishing a kind-1 note that referenced an article whose own event
carries a self `a` tag (e.g. YakiHonne-authored long-form posts include
their own naddr in their `a` tags).
Adds Czech, German, Brazilian Portuguese, and Swedish translations
for 19 new keys covering auto-create-drafts compose settings,
profile-UI section toggles, security-screen empty states and
section headers, external-resource comment scopes, and nest
host-action failure toasts.
On every re-subscribe (listener reconnect or publisher-cycle), the
moq-lite relay re-serves its cached latest group from the first frame.
The wrapper forwarded it straight into the decoder, so in a fully-muted
room the same old clip looped once per reconnect. Track the highest
group sequence delivered by prior subscriptions and drop any group not
strictly newer, mirroring kixelated/hang's Container.Consumer.#run.
https://claude.ai/code/session_01G9h2dzkEj6Y2F1Yr2kCojp
Move the detectDragGestures pointerInput from the whole button card to
the six-dot drag handle icon so drag-and-drop reordering activates only
when the user grabs the handle.
https://claude.ai/code/session_01Jyu8dLYN7MXVNaVZE3u2M9
Members were sorted to float the current speaker to the top, so a
member jumped to the front when they started talking and slid back
~250ms after they stopped — a constant shuffle in any active room.
The speaking state is already shown in-place via the mic badge and
audio-level ring, so keep members in their stable arrival order.
https://claude.ai/code/session_01GvvNS1FRn6gGvHRC7dgEtP
Convert the per-account `disableClientTag` toggle into a positive
`addClientTag` setting that defaults to enabled, and relocate it from the
Security Filters screen to the Compose Settings page.
https://claude.ai/code/session_01TkmBzzEz3kKnrjpbrKCpsf
The AppImage build inputs (AppRun, .desktop entry, icon, and the
CI-fetched linuxdeploy binary) are consumed only by desktopApp's
createReleaseAppImage task. Co-locating them under
desktopApp/packaging/appimage/ removes the `../` path escape from the
build script and keeps all desktop packaging assets inside the module.
https://claude.ai/code/session_0137ULcfJkASmfmffFBdW8ac
The bottom add-word text field sat behind the phone's navigation
and gesture bars. Apply the union of navigation bar and IME insets
to the bottom bar so it clears the system bars and rises with the
keyboard without a double-counted gap.
https://claude.ai/code/session_01KPkQbS9SYTigkkzYGU97Sz
A Comment event (kind 1111) scoped to an external identifier (`I` tag)
previously rendered as a bare text post and landed in the Home "New
Threads" feed. Treat it as a reply to that external scope: it now shows
in the conversations feed and renders a typed chip (hashtag, geohash,
url, or generic) above the comment text.
https://claude.ai/code/session_01ArHkNXu1ANrVGZAyMWg4Xu
ScheduledPostsScreen used a plain Scaffold with no bottom bar and an
unconditional back arrow, so a user who pinned it to the bottom nav got
a back arrow and no bottom bar instead of the standard tab-root shell.
Add AppBottomBar(Route.ScheduledPosts) and gate the back arrow on
nav.canPop(), matching every other bottom-nav-eligible screen: back
arrow + no bottom bar when entered from the drawer, no back arrow +
bottom bar when entered from the bottom nav.
https://claude.ai/code/session_01WZXxqCGYT4JEQBwwBNiUjm
NestZapButton had no ObserveZapIconState fallback (unlike NoteCompose's
ZapReaction), so after onPayViaIntent handed the invoice to an external
wallet the progress spinner stayed up indefinitely in the long-lived
NestActivity. Reset zappingProgress to 0 on every onPayViaIntent path.
The floating zap overlay was anchored TopEnd, colliding with the
hand-raise badge — and since the merge made zaps float from the
zapper's avatar (a likely hand-raiser), that overlap would be common.
Moved it to TopCenter, the only badge-free anchor.
Also dropped dead state (zapStartingTime / the non-animated
animatedProgress alias) and corrected stale comments left over from
the sender-grouping merge.
https://claude.ai/code/session_01EW11kUdiEYPuPti7vtD2AR