Commit Graph
14580 Commits
Author SHA1 Message Date
davotoula f2df23cfa6 Code review:
- fix(player): harden fullscreen swipe controls and brightness lifecycle
2026-06-04 23:19:28 +02:00
davotoula 5edfe90321 feat(player): enable brightness/volume swipe in fullscreen video 2026-06-04 23:19:08 +02:00
Vitor PamplonaandGitHub 56240c10d2 Merge pull request #3134 from vitorpamplona/claude/compassionate-cannon-3mX7Y
Fix cache-pruning invariant for onchain zaps and nutzaps
2026-06-04 15:08:23 -04:00
Vitor PamplonaandGitHub c6be3ee3a3 Merge pull request #3118 from mstrofnone/docs/namecoin-design-refresh
docs(namecoin): refresh NIP-05 design doc to match current main
2026-06-04 15:03:37 -04:00
Vitor PamplonaandGitHub 8a156261cd Merge pull request #3132 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-04 15:03:24 -04:00
Crowdin Bot 336695dd55 New Crowdin translations by GitHub Action 2026-06-04 19:03:17 +00:00
Vitor PamplonaandGitHub 38724cc3f5 Merge pull request #3133 from vitorpamplona/claude/wizardly-fermat-y7NBf
Move authenticated identity from policy to connection scope
2026-06-04 15:00:58 -04:00
Claude 7c95ba1ffd refactor: rename removal methods to match what they do
Three names didn't describe their behavior:

- removeFromCache → unlinkAndRemove: the method's main job is unlinking the
  note from every referrer (parents, channels, the report/card/status/poll
  indexes), not just evicting it from the map; the old name only captured
  the last step.
- removeAllChildNotes → clearChildLinks: it clears only THIS note's forward
  child collections and returns them — it does not touch the children's
  replyTo and does not remove anything from the cache. The old name sounded
  more aggressive than detachFromChildren(), which is actually the
  both-directions op.
- Note.removeOnchainZap(source) → removeOnchainZapBySource(source): too easy
  to confuse with removeOnchainZapForSource(txid, pubkey), which is the
  verification-verdict removal with anti-spoof guards. The new name matches
  its inner helper (innerRemoveOnchainZapBySource) and disambiguates the two.

Pure rename: no behavior change. Test names/comments updated to match.

https://claude.ai/code/session_01RqJPYzmjb1pR3NBeH2yY3s
2026-06-04 18:59:54 +00:00
Claude 9497739c7e docs(quartz/relay): remove auth-scope component diagram 2026-06-04 18:48:26 +00:00
Claude a3bb6fbc10 refactor: unify Note removal into one shared unlink path
deleteNote() (NIP-09) and removeFromCache() (prune) had drifted into two
near-duplicate "detach a note from the cache" routines. Removal really has
two halves — (1) unlink the note from everything that points AT it, and
(2) handle the note's OWN children — and only the second half differs
between the paths. removeFromCache() already implemented half (1)
completely, so deleteNote() now delegates to it and keeps only its two
delete-specific responsibilities: tearing down gift-wrap hosts and
severing (but keeping) its children via detachFromChildren().

This also fixes a real leak the duplication was hiding. computeReplyTo()
has no ReportEvent branch, so a report note's replyTo is empty and the
report→target link lives only in the explicit reported* index handling.
The old deleteNote() only undid reportedAuthor(), so deleting an
event-level report (reportedPost / reportedAddresses) left the reported
note's `.reports` map holding the removed report note — a partial deletion
that leaked the shell and risked a duplicate Note for the same id.
Delegating to removeFromCache() (author + post + addresses, all
idempotent) closes that gap.

Net behavior change is the report-leak fix only; the redundant
TorrentCommentEvent case is dropped because the torrent target is already
in replyTo (and removed via removeNote), and its @Suppress("DEPRECATION")
goes with it. Adds KDoc to both methods documenting the two-halves model.

https://claude.ai/code/session_01RqJPYzmjb1pR3NBeH2yY3s
2026-06-04 18:35:56 +00:00
Claude d2e64a0339 docs(quartz/relay): update component diagram to the merged design
Reframe from a forward-looking plan diff to the current architecture, and fix
the onAuthenticated signature to (event): Boolean (pubKey was dropped). All
structural elements already matched the merged code.
2026-06-04 18:29:52 +00:00
Claude 0791ae9d40 test(quartz/relay): assert authenticatedUsers is scoped per connection
The auth set is already a per-RelaySession instance field (one session per
connect(), fresh policy per connection), so two connections never share auth
state. Add a regression test: authenticate different pubkeys on two connections
of the same server and assert each scope holds only its own — no union leak.
2026-06-04 17:42:48 +00:00
Claude 23ddeba8ec fix: sever child back-references when deleting a Note (NIP-09)
deleteNote() removed the target from its parents, gatherers, and the cache
map, but never cleared its own child collections nor dropped itself from
its children's replyTo. That left a partial deletion: every child kept the
removed shell alive through replyTo (a leak), and a reply resolved later
via computeReplyTo would getOrCreateNote a *second* Note for the same id —
breaking the one-Note-per-id invariant.

Adds Note.detachFromChildren(), which clears the note's forward child
collections (via removeAllChildNotes) and severs this note from each
child's replyTo (keeping any other parents). deleteNote() now calls it
before notes.remove(), so once the note leaves the map nothing points at
the dead shell. Orphaned replies become roots, which is correct once their
parent is hard-deleted from the cache.

Adds detachFromChildren coverage to NotePruningReferenceTest.

https://claude.ai/code/session_01RqJPYzmjb1pR3NBeH2yY3s
2026-06-04 17:42:29 +00:00
Claude 6fb735018c refactor(quartz/relay): drop redundant pubKey param from onAuthenticated/authorize
pubKey was always event.pubKey (the NIP-42 signer is the authenticated
identity), so the parameter was pure redundancy. onAuthenticated(event) and
authorize(event) now read event.pubKey directly; the engine still commits
cmd.event.pubKey. Removes the now-unused HexKey imports from IRelayPolicy and
PolicyStack.
2026-06-04 17:32:24 +00:00
Claude a54d9c92a7 refactor(quartz/relay): move auth identity from policy into connection scope
Authentication state (who is logged in on a connection) is connection scope,
not a policy decision. It was stored on FullAuthPolicy, which forced the
AuthScopedPolicy marker, the PolicyStack union, and a downcast in
RequestContext just to route it back out as scope.

Now the engine owns it: RelaySession holds the authenticatedUsers set behind
the (now public) requestContext; the data plane and policies read it through
RequestContext. The policy stays pure decision —
- onConnect(scope, send): a per-connection policy captures the read-only scope
  to gate on; shared singletons ignore it.
- onAuthenticated(): Boolean: the policy's vote on whether to record the
  verified pubkey (default false, so blind-accept policies never record an
  unverified identity). FullAuthPolicy runs authorize() then votes true.
- RelaySession.handleAuth performs the single, engine-side commit after the
  whole chain approves and a verifying policy votes to record.

FullAuthPolicy keeps all auth logic (challenge, accept(AuthCmd), gating,
authorize) and gains a protected authenticatedUsers accessor over the scope for
subclasses (restricted content / filter rewrite). Deletes AuthScopedPolicy,
PolicyStack.authenticatedUsers, and the RequestContext downcast.
2026-06-04 16:45:03 +00:00
Claude c4bfdc72dc fix: detach channel in removeFromCache to mirror deleteNote
removeFromCache() relied solely on note.inGatherers to detach a pruned
note from its channels, while deleteNote() additionally resolved the
channel via getAnyChannel() and removed the note there too. inGatherers
is normally authoritative (Channel.addNote always calls addGatherer), so
this is defensive rather than a confirmed live leak — but it closes the
divergence so both removal paths detach channels identically. Guards
against any future consume path that adds a note to a getAnyChannel-
resolvable channel without the gatherer link: otherwise the note would
linger in the channel's notes map after leaving the cache, leaking it and
letting a relay echo mint a duplicate Note with the same id.

https://claude.ai/code/session_01RqJPYzmjb1pR3NBeH2yY3s
2026-06-04 16:17:14 +00:00
davotoula 21f722767d refactor: use lambda Log overload for interpolated log calls 2026-06-04 18:15:17 +02:00
Claude 6943c1a056 docs(quartz/relay): component & data-flow diagram for the auth-scope plan 2026-06-04 16:03:00 +00:00
Claude 0008f676b6 docs(quartz/relay): plan to move auth identity from policy to connection scope 2026-06-04 15:56:36 +00:00
Claude 11591826f0 fix: detach onchain-zap and nutzap sources when pruning Notes from LocalCache
LocalCache must hold a single Note per event id/address and must never
remove a Note from the cache map while another Note still strongly
references it — a dangling reference both leaks the shell and lets a
relay echo mint a second Note with the same id.

Note.onchainZaps (NIP-BC) and Note.nutzaps (NIP-61) were added after the
removal/migration routines were written and were never wired into them,
so a pruned zap-source Note leaked through its target's maps:

- removeNote() only detached reply/boost/reaction/zap/zapPayment/report/
  label, leaving the target's onchainZaps/nutzaps entry dangling when the
  source note was pruned. Now also calls removeNutzap + a new
  source-keyed removeOnchainZap (unconditional cache removal, distinct
  from the verdict-respecting removeOnchainZapForSource).
- removeAllChildNotes() cleared onchainZaps but never returned the source
  notes for removal from the cache map (asymmetric with nutzaps), so they
  lingered orphaned. Now included.
- moveAllReferencesTo() dropped labels, zapPayments, and onchainZaps when
  a replaceable's old version was superseded — silent data loss plus
  orphaned onchain sources. Now migrated and cleared like the rest.

Adds NotePruningReferenceTest covering all three paths.

https://claude.ai/code/session_01RqJPYzmjb1pR3NBeH2yY3s
2026-06-04 15:37:44 +00:00
Claude a98f5fddd2 refactor(quartz/relay): move authenticatedUsers off IRelayPolicy to a marker
Keep the universal policy interface free of NIP-42: instead of a default
authenticatedUsers on IRelayPolicy, add an opt-in AuthScopedPolicy mixin that
only FullAuthPolicy (and PolicyStack, which unions its auth-tracking members)
implements. RequestContext.authenticatedUsers resolves it via an `as?
AuthScopedPolicy` downcast, defaulting to empty — so non-auth relays carry no
auth concept, and the accessor now earns its keep by encapsulating that cast
(ctx.policy is IRelayPolicy and no longer exposes the set directly).
2026-06-04 15:36:40 +00:00
Claude 3d6acde472 feat(quartz/relay): thread a per-connection RequestContext into EventSource
Non-storage relays answer a REQ purely from filters: EventSource.events()
got no session/auth context, even though the connection's policy already
knows the authenticated pubkey(s). That walled the auth state off from the
code that produces events, forcing a shared mutable holder + hand-wired
RelaySession to build any caller-aware relay (NIP-50 search scored from the
viewer, DM-style restricted content, paid/allow-listed sets, per-connection
tenancy).

Introduce RequestContext (connectionId, authenticatedUsers, policy) and pass
it through the read path: RelaySession -> SessionBackend.query/count/
countResult -> EventSourceBackend -> EventSource. authenticatedUsers is now
exposed on IRelayPolicy (default empty, overridden by FullAuthPolicy and
unioned by PolicyStack) and read live, so a REQ after AUTH sees the freshly
authenticated pubkey(s). For richer per-connection state, downcast ctx.policy.

EventSourceServer.serve { } is now usable for auth-scoped relays without a
side channel. Adds a test proving ctx.authenticatedUsers reaches the source
after a NIP-42 handshake; updates RELAY.md.
2026-06-04 15:25:39 +00:00
Vitor Pamplona 3db3037f92 seeing if this works for jitpack 2026-06-03 19:22:29 -04:00
Vitor Pamplona b0a6baaddc Tries to fix the compilation issue with jitpack 2026-06-03 18:58:19 -04:00
Vitor PamplonaandGitHub 99ab2cb720 Merge pull request #3131 from vitorpamplona/claude/quartz-relay-ergonomics-7sTEN
Add ReqResponderServer for non-storage relays + NIP-50 search support
2026-06-03 17:45:49 -04:00
Claude 1c54a877e2 refactor(quartz): split relay server package into engine / backend / policies
The relay server package had 14 top-level files. Group by concern:

- relay/server/          engine + entry points: NostrServer, EventSourceServer,
                         RelayServerBase, RelaySession, ConnectionRegistry,
                         RelayServerListener, NegSessionRegistry
- relay/server/backend/  data plane: SessionBackend, EventSource,
                         EventSourceBackend, LiveEventStore, IngestQueue
- relay/server/policies/ policy model + impls: IRelayPolicy(+PolicyResult) and
                         RelayLimits move in alongside the existing policies
- relay/server/inprocess/ unchanged

Layering is acyclic: engine -> {backend, policies} -> nip01Core.

This moves three already-published classes (IRelayPolicy, LiveEventStore,
IngestQueue), so external quartz consumers re-import — a source-only change,
no behavior change. In-repo callers (geode) updated. Full quartz + geode
suites green; RELAY.md source map updated.

https://claude.ai/code/session_016YBS2pWCBSDgAMthHzfCTr
2026-06-03 21:44:12 +00:00
Claude 66481dac05 refactor(quartz): rename ReqResponder -> EventSource; drop JwtAuthPolicy doc
The SPI name leaned on "Req" (the NIP-01 command), which isn't self-explanatory.
Rename to read as what it is — a source of events for a query:

- ReqResponder        -> EventSource          (method respond() -> events())
- ReqResponderBackend -> EventSourceBackend   (param responder -> source)
- ReqResponderServer  -> EventSourceServer
- *Test + RELAY.md + KDoc references updated to match.

Also drop the JwtAuthPolicy snippet from RELAY.md (it was only an illustrative
doc example, never a real class); the surrounding prose still documents the
`authorize` bridge hook.

Pure rename + doc edit, no behavior change; full suite green.

https://claude.ai/code/session_016YBS2pWCBSDgAMthHzfCTr
2026-06-03 21:09:24 +00:00
Claude c13dbad99d refactor(quartz): simplify relay server internals (no behavior change)
Cleanup pass (4 review angles), behaviour-preserving, full suite green:

- Extract RelayServerBase: NostrServer and ReqResponderServer duplicated
  connect/serve/buildPolicy/activeConnections and the connection scope verbatim
  (ConnectionRegistry only unified the bookkeeping). They now share one engine
  base and contribute only their backend + teardown; ReqResponderServer is ~10
  lines, NostrServer just its ingest/store wiring.
- HyperLogLog.leadingZeroBits: replace the hand-rolled per-byte bit loop with
  stdlib Int.countLeadingZeroBits() (- 24 for the 0..255 byte).
- LimitsPolicy.clampLimits: drop the `var changed` + throwaway-list map for a
  `none{} -> map{}` that's simpler and allocates nothing when no filter is
  clamped (the common case once maxLimit is set).
- PolicyStack: collapse the two first-non-null hook loops to firstNotNullOfOrNull.
- RelaySession.handleAuth: use OkMessage.rejected(MachineReadablePrefix.ERROR, …)
  instead of hand-writing the "error:" prefix, matching the COUNT path.

Skipped: rewriting FullAuthPolicy's pre-existing auth-required:/invalid: reason
strings to MachineReadablePrefix — unchanged context lines, out of this diff's
scope.

https://claude.ai/code/session_016YBS2pWCBSDgAMthHzfCTr
2026-06-03 20:43:52 +00:00
Claude 9a19a0f346 refactor(quartz): remove pendingNewlyAdded — commit auth once, not commit-then-rollback
The pendingNewlyAdded field was a rollback token faked through instance state:
FullAuthPolicy.accept(AuthCmd) committed the pubkey eagerly, so a later
rejection (composed policy or a throwing hook) had to be undone, and the field
existed only to avoid dropping a pubkey that was already authenticated.

Fix the root cause — the eager commit. accept(AuthCmd) now only validates; the
pubkey is recorded in FullAuthPolicy.onAuthenticated (made final), which the
engine calls only after accept AND the whole policy chain approve the AUTH.
External-auth bridges override a new open `authorize` hook that runs before the
commit; throwing rejects the login with nothing committed to undo.

This deletes pendingNewlyAdded, IRelayPolicy.onAuthenticationFailed, its
PolicyStack override, and both rollback call-sites in RelaySession.handleAuth —
and makes the prior compose-after-reject / failed-re-AUTH cases correct by
construction (no rollback to get wrong). Tests updated to override `authorize`;
the two regression tests pass unchanged.

https://claude.ai/code/session_016YBS2pWCBSDgAMthHzfCTr
2026-06-03 20:25:12 +00:00
Claude a21f811e87 refactor(quartz): clarity pass on relay server internals
Behaviour-preserving readability improvements (full suite green):

- LimitsPolicy: drop the <T : Command> generic reject helpers (which forced
  rejectSubId<ReqCmd>(...) call-site type args). Each check is now a
  "rejection reason or null" function (eventRejection / subscriptionRejection)
  and the accept overloads just wrap a non-null reason — the three overloads
  read almost identically.
- Extract ConnectionRegistry: NostrServer and ReqResponderServer duplicated
  the connection bookkeeping (stable-id keying, active gauge, once-only
  teardown accounting). That subtle logic now lives in one named class both
  servers delegate to; their connect()/close() shrink to the parts that
  actually differ (the backend and what else teardown closes).
- HyperLogLog.addPubKey: rename `ri` -> `registerIndex`.

https://claude.ai/code/session_016YBS2pWCBSDgAMthHzfCTr
2026-06-03 20:15:06 +00:00
Claude 366d9435d7 fix(quartz): pre-merge review fixes — auth rollback, listener rename
Final-review findings on the relay tooling:

- Auth bypass (security): FullAuthPolicy.accept(AuthCmd) commits the pubkey,
  but handleAuth only rolled back on the hook-throw path — a policy composed
  AFTER FullAuthPolicy that rejects the AuthCmd left the connection
  authenticated behind an OK false. handleAuth now also rolls back on the
  reject path, preserving OK-true-iff-authenticated for any composition order.
- Failed re-AUTH no longer drops a prior valid auth: onAuthenticationFailed
  removes only the pubkey THIS AUTH newly added (tracked via Set.add's return),
  not one already authenticated earlier on the connection.
- Rename the server-side RelayConnectionListener -> RelayServerListener to
  avoid colliding with the existing client-side
  relay.client.listeners.RelayConnectionListener (published-API clarity).
- RelayLimits.toNip11Limitation clamps created_at bounds to Int range so a
  post-2038 epoch second can't wrap negative in the NIP-11 document.
- Add regression tests for both auth cases (reject-after-FullAuth; failed
  re-AUTH keeps prior auth).

https://claude.ai/code/session_016YBS2pWCBSDgAMthHzfCTr
2026-06-03 19:41:55 +00:00
Claude 648a00ef63 feat(quartz): add missing NIP-11 banner field to relay info document
Nip11RelayInformation modeled `icon` but not `banner` (NIP-11's wide
promotional image, distinct from the square icon), so relays advertising a
banner couldn't round-trip it. Add the field + a round-trip test.

https://claude.ai/code/session_016YBS2pWCBSDgAMthHzfCTr
2026-06-03 19:22:37 +00:00
David KasparandGitHub 5901f2e453 Merge pull request #3130 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-03 21:06:42 +02:00
Claude 37b07057b2 refactor(quartz): enforce session-level limits through policy hooks
Previously max_message_length and max_subscriptions were hard-coded in
RelaySession behind a parallel `limits` param, while the per-command limits
went through LimitsPolicy — two mechanisms, and a custom policy couldn't
influence the session-level ones.

Unify them: add two default-noop hooks to IRelayPolicy —
acceptMessage(raw) (pre-parse) and acceptSubscription(subId, openCount) —
chained through PolicyStack so they compose across multiple policies.
LimitsPolicy now implements all limit checks; RelaySession just invokes the
hooks and no longer takes a `limits` param. Servers compose LimitsPolicy
whenever `limits` is set and keep `limits` only to advertise via NIP-11.

Behaviour is unchanged (oversized -> NOTICE invalid:, sub cap -> CLOSED
rate-limited:); the enforcement now lives in the policy layer. Adds direct
hook unit tests; existing end-to-end limit tests still pass.

https://claude.ai/code/session_016YBS2pWCBSDgAMthHzfCTr
2026-06-03 18:58:20 +00:00
Claude 70dcaa1b10 feat(quartz): relay limits as single source of truth + NIP-11 serving
A relay author no longer hand-rolls limit policies or wires NIP-11 twice.

- RelayLimits: one config that is both enforced and advertised. Pass it to
  NostrServer/ReqResponderServer and every limit is applied; toNip11Limitation()
  renders the same numbers into the NIP-11 limitation block so they can't drift.
- LimitsPolicy: per-command enforcement (max_content_length, max_event_tags,
  created_at bounds reject EVENT; max_filters / max_subid_length reject
  REQ/COUNT; max_limit clamps, default_limit fills) with invalid: prefixes.
  Servers prepend it automatically when limits declares command caps.
- RelaySession: session-level caps that a policy can't see — max_message_length
  (NOTICE before parse) and max_subscriptions (rate-limited: CLOSED on new sub).
- NIP-11 serving: Nip11RelayInformation.toJson() + CONTENT_TYPE
  (application/nostr+json); the existing model was parse-only.
- Tests for the policy, the session-level caps end-to-end, and the
  limits->NIP-11 round trip; RELAY.md Limits + Serving NIP-11 sections.

https://claude.ai/code/session_016YBS2pWCBSDgAMthHzfCTr
2026-06-03 18:41:06 +00:00
Claude 952da685d8 feat(quartz): NIP-45 approximate COUNT — HyperLogLog construction + wire support
The HLL aggregation side (estimate/merge/encode) existed but the construction
side did not, and the Jackson wire (de)serializer silently dropped the `hll`
field — so a JVM/Android relay could not actually answer COUNT with HLL.

- HyperLogLog.addPubKey(): the NIP-45 construction (register index = pubkey
  byte at the filter offset; value = leading-zero-bits from offset+1, +1),
  KMP-safe. Plus HyperLogLog.builderFor(filter) and an HllBuilder that streams
  event pubkeys into registers and yields an approximate CountResult.
- Plumb CountResult through the count path: SessionBackend.countResult /
  ReqResponder.countResult (default = exact count(); override for approximate/
  hll); RelaySession sends the returned CountResult.
- Fix CountResultSerializer/Deserializer (jvmAndroid) to write/read the `hll`
  hex field, matching the kotlinx serializer the native targets already use.
- Tests for construction (index/value/merge-idempotence) and the wire path;
  RELAY.md Approximate COUNT section.

https://claude.ai/code/session_016YBS2pWCBSDgAMthHzfCTr
2026-06-03 18:29:01 +00:00
Crowdin Bot bd9f927041 New Crowdin translations by GitHub Action 2026-06-03 18:21:24 +00:00
Claude 319ffb729a feat(quartz): relay connection observability + stable connection ids
Gives relay operators metrics/logging hooks without patching the engine, and
removes the hashCode()-keyed connection registry the audit flagged.

- RelaySession gains a stable, process-unique `id` (monotonic counter).
- RelayConnectionListener (onConnect/onDisconnect, no-op default) is accepted
  by NostrServer and ReqResponderServer; both now key their connection
  registry by `id` instead of hashCode() (no more identity-collision hole).
- Both servers expose a live `activeConnections` gauge. Teardown accounting is
  idempotent (double close counted once) and onDisconnect fires for any
  connections still open at server close.
- Tests + RELAY.md Observability section.

https://claude.ai/code/session_016YBS2pWCBSDgAMthHzfCTr
2026-06-03 18:21:05 +00:00
davotoulaandClaude Opus 4.8 54f746edc3 feat: translate settings-search and share-to-DM strings (cs, de, sv, pt-BR)
Add cs-rCZ, de-rDE, sv-rSE and pt-rBR translations for the new settings
search field (placeholder + no-results) and the share-to-DM flow strings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 20:16:36 +02:00
Claude bcb4b2b964 fix(quartz): roll back authentication when the post-auth hook rejects
Audit finding: FullAuthPolicy.accept(AuthCmd) added the pubkey to the
authenticated set before onAuthenticated ran, so a bridge that threw from
onAuthenticated (its whole point — reject when e.g. a JWT exchange fails)
produced an OK false while the connection stayed authenticated server-side.
Subsequent REQ/EVENT/COUNT were then allowed despite the failed login — an
auth bypass.

- Add IRelayPolicy.onAuthenticationFailed(pubKey) (default no-op), forwarded
  by PolicyStack and overridden by FullAuthPolicy to drop the pubkey.
- RelaySession.handleAuth calls it when onAuthenticated throws, restoring the
  invariant that a client treated as authenticated is exactly one that got
  OK true. The rollback is itself guarded so a misbehaving policy can't also
  swallow the failing OK.
- Tests: failed-hook now asserts the connection is NOT authenticated and that
  a follow-up REQ is rejected with auth-required.
- RELAY.md: note that throwing from onAuthenticated rolls auth back.

https://claude.ai/code/session_016YBS2pWCBSDgAMthHzfCTr
2026-06-03 17:45:52 +00:00
Claude 68e49a97ba feat(quartz): Flow<Event> REQ-responder SPI + storage-free dispatch engine
Lets non-storage relays (search, redirector, computed/projected data) answer
REQs without implementing the heavy IEventStore or hand-writing the
readFrame -> parse -> policy -> EVENT/EOSE loop.

- ReqResponder: the public Flow<Event> SPI — respond(filters): Flow<Event>
  (+ a count() default). EOSE is sent when the flow completes.
- SessionBackend: the seam RelaySession now depends on (query/count/submit/
  negentropy-snapshot). submit + snapshot default to reject / empty so a
  responder only implements the read path. LiveEventStore implements it
  (storage path unchanged); ReqResponderBackend adapts a ReqResponder.
- ReqResponderServer: storage-free dispatch engine mirroring NostrServer's
  connect/serve/close, reusing RelaySession for the full wire protocol.
- RelaySession now frames backend failures as CLOSED error: <msg> (REQ) and
  count failures likewise, instead of dropping the coroutine — useful for
  responders doing network I/O.
- RELAY.md: Non-Storage Relays section + engine/source-map updates.

Storage path (NostrServer + IEventStore, live tail, negentropy) is unchanged;
existing server/auth/negentropy tests pass alongside the new responder tests.

https://claude.ai/code/session_016YBS2pWCBSDgAMthHzfCTr
2026-06-03 17:31:53 +00:00
Claude 69aea29954 feat(quartz): server-side relay ergonomics — NIP-50 parser, suspend auth hook, wire helpers
Addresses the self-contained, low-risk items from the relay-ergonomics
request:

- NIP-50: add SearchQuery to parse Filter.search into free-text terms and
  the typed key:value extensions (domain/language/sentiment/nsfw/include),
  preserving unknown extensions and offering a canonical toSearchString().
- NIP-42: add a suspend IRelayPolicy.onAuthenticated(pubKey, event) hook
  (chained through PolicyStack) so external-auth bridges (e.g. JWT exchange)
  can live inside FullAuthPolicy instead of leaking into transport code.
  RelaySession invokes it after the AUTH passes; a throw becomes OK false.
- Ergonomics: Command.fromJson/toJson and Message.fromJson/toJson mirroring
  Event, plus MachineReadablePrefix + OkMessage/ClosedMessage factories for
  standardized OK/CLOSED reason prefixes.
- Docs: RELAY.md sections for the external-auth bridge, NIP-50 search, and
  the wire helpers.

https://claude.ai/code/session_016YBS2pWCBSDgAMthHzfCTr
2026-06-03 17:15:13 +00:00
Vitor PamplonaandGitHub 9423463f17 Merge pull request #3129 from vitorpamplona/claude/sleepy-sagan-Q2p3t
Register CommunityRulesEvent in EventFactory for kind 34551
2026-06-03 09:35:28 -04:00
Claude 3a43c4f7bb fix: register CommunityRulesEvent in EventFactory
Kind 34551 (CommunityRulesEvent) was missing from EventFactory.create, so
signing a community-rules template produced a generic Event. Returning it
as CommunityRulesEvent in Account.sendCommunityRules threw a
ClassCastException when publishing community rules.

Register the kind in the factory and add a regression test.
2026-06-03 13:30:52 +00:00
David KasparandGitHub 479c694e6e Merge pull request #3128 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-03 14:35:31 +02:00
Crowdin Bot 6f645511f0 New Crowdin translations by GitHub Action 2026-06-03 12:02:06 +00:00
Vitor PamplonaandGitHub 67d14fcc03 Merge pull request #3125 from nrobi144/fix/desktop-log-noise
fix: address root causes of 6 runtime log noise issues
2026-06-03 07:59:09 -04:00
Vitor PamplonaandGitHub 74af87ae37 Merge pull request #3127 from davotoula/feat/settings-search
Searchable, data-driven settings screen
2026-06-03 07:58:50 -04:00
davotoulaandClaude Opus 4.8 72d538bb62 refactor(settings): address review — non-translatable keywords, symEntry helper, legal keywords
- Mark all *_search_keywords translatable="false" (English concept/protocol
  index; stops Crowdin translating protocol terms and breaking locale search) [#1]
- Collapse ~23 symbol+nav rows via a local symEntry() helper [#3]
- Reword keywordsRes KDoc to match the actual word-prefix tokenization [#6]
- Add search keywords to the Legal rows (privacy_policy, child_safety) [#7]

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:08:28 +02:00
davotoula d75e80bea6 feat(settings): curated search keywords for all rows + word-prefix search matching
Add keyword blobs so settings resolve by concept/protocol name, not just
title — e.g. "blossom" -> Media Servers, "audio rooms" -> Nests Servers,
"negentropy" -> Event Sync, "nsec" -> Backup Keys. NIP numbers omitted by
preference.
2026-06-03 09:45:20 +02:00