Commit Graph
16096 Commits
Author SHA1 Message Date
Claude c549bd22df perf: isolate QUIC blocking socket I/O onto dedicated threads
Part A of the dispatchers/thread-caps audit — the one genuine at-scale
starvation the audit found.

UdpSocket.receive() does a blocking DatagramChannel recvfrom that parks its
thread for the ENTIRE life of the connection. It ran via
withContext(Dispatchers.IO) from a read loop already on Dispatchers.IO, so
the blocking call pinned one shared IO-pool thread per connection. Past ~64
concurrent connections that starves ALL other Dispatchers.IO work in the
process — this module's and the host app's alike.

Give each socket two dedicated daemon threads: recvDispatcher for the
perpetually-parked receive and sendDispatcher for the send (they can't share
one thread — the receive would monopolise it). QUIC's blocking socket I/O now
never touches the shared pool. connect() keeps its one-shot DNS/bind on
Dispatchers.IO (setup cost, not a lifetime parker).

close() calls shutdownNow() on both executors: interrupting the recv worker
breaks the parked recvfrom immediately (ClosedByInterruptException, caught as
ClosedChannelException -> receive() returns null), so the threads exit
promptly instead of leaking per closed connection. The closed-check is hoisted
out of withContext so a post-close call fails fast without dispatching onto a
shut-down executor.

Verified: QuicConnectionDriverLifecycleTest (100 session open/close cycles,
asserts thread growth <=16 and no FD leak) passes, confirming the two new
threads per socket are reclaimed on teardown. New UdpSocketTest covers
round-trip, the dedicated-thread isolation, thread shutdown on close, and the
after-close contract. Full :quic:jvmTest suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ANuUziXKRafSTBxbh4SMoq
2026-07-02 21:35:57 +00:00
Claude 49e0fee162 perf: add expect/actual ConcurrentSet, use for EventDeduplicator
Part B of the dispatchers/thread-caps audit.

EventDeduplicator is fed from relay subscription callbacks
(AdvancedSearchBarState.trackRelayEvent), which arrive on multiple threads
concurrently. It backed a plain mutableSet with a single KmpLock, so every
delivery from every relay thread serialized on one monitor.

Add ConcurrentSet<E> as a KMP expect/actual util:
- jvmAndroid actual: ConcurrentHashMap.newKeySet() — lock-striped writes,
  lock-free reads, no single cross-thread monitor.
- iOS actual: a KmpLock-guarded set (no lock-free set in the K/N stdlib) —
  same behaviour as before, no regression. The win lands on JVM/Android,
  which is where the high-throughput event paths run.

Point EventDeduplicator at it. Covered by ConcurrentSetTest (commonTest,
behaviour) and ConcurrentSetConcurrencyTest (jvmTest, exactly-one-add-per-key
under 8 threads).

Scope note: the other two commonMain sites the audit flagged were left as-is
on purpose. The compose subscription managers' KmpLock is a deliberate,
documented KMP choice on a single (main-thread) writer where the lock is
effectively free; EOSECache is a bounded LRU with compound value mutation on
a per-subscription (not per-event) path. Neither is a clean fit for a
concurrent set, and converting them would fight a documented decision for
negligible gain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ANuUziXKRafSTBxbh4SMoq
2026-07-02 16:53:43 +00:00
Claude ca5ae978fb perf: add lock-free-read ConcurrentLruCache, use on two hot read paths
Part C of the dispatchers/thread-caps audit. Both LnurlEndpointCache and
DesktopCachedRichTextParser were bounded caches backed by a LinkedHashMap
behind a single monitor (@Synchronized / Collections.synchronizedMap with
accessOrder). An access-order map structurally mutates on get, so every
read took the lock — serializing all readers on paths that are hot
(kind-9735 zap-receipt validation; feed rich-text rendering).

Add ConcurrentLruCache<K, V> in quartz utils: storage is a
ConcurrentHashMap so get is lock-free; writes + eviction run under a small
write lock that is off the read path. Eviction is least-recently-put order
(get does not refresh recency) — exactly what LnurlEndpointCache already
did, and fine for the deterministic rich-text parse cache.

Point both caches at the shared helper. Covered by a new
ConcurrentLruCacheTest (round-trip, eviction order, re-put recency
refresh, get-does-not-refresh, clear, and a concurrent size-bound smoke
test); the existing LnurlEndpointCacheTest still passes unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ANuUziXKRafSTBxbh4SMoq
2026-07-02 16:21:27 +00:00
Claude 7e61d24962 perf: remove needless blocking on two per-packet/per-event hot paths
Part of the dispatchers/thread-caps audit. Two low-risk fixes that remove
thread-blocking work from paths hit on every event / every packet:

- LargeCache (iOS actual): drop the runBlocking wrapper around
  createIfAbsent. The block contained only synchronous CacheMap ops (the
  same get/put getOrCreate already calls without runBlocking), so it was
  pure dispatcher-blocking overhead on the per-event ingest path. Now
  mirrors the JVM actual's plain-function shape.

- QUIC JCA AEADs (AES-GCM + ChaCha20-Poly1305): split the single
  `synchronized(this)` monitor into disjoint encryptLock / decryptLock.
  seal-family touches only encryptCipher + recentEncryptNonces; open-family
  touches only decryptCipher, so a connection's send loop and read loop no
  longer serialize against each other through crypto on every packet. The
  documented defence-in-depth against cross-coroutine Cipher corruption is
  preserved per direction.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ANuUziXKRafSTBxbh4SMoq
2026-07-02 16:09:49 +00:00
Vitor PamplonaandGitHub 4727669fde Merge pull request #3453 from vitorpamplona/claude/quartz-logging-review-o1l51r
Make Log.sink pluggable for custom logging backends
2026-07-02 09:33:52 -04:00
Claude 167fe96345 refactor(quartz): route stray printStackTrace through the Log facade
printStackTrace() dumps straight to stderr, bypassing both Log.minLevel
and the consumer's Log.sink — the very thing the LogSink work exists to
control. Migrate the five production call sites:

- Lud06: drop two printStackTrace() calls that sat directly above an
  existing Log.w(..., t) carrying the same throwable (pure duplication).
- ElectrumXClient: the swallowed-lookup catch said "Log but don't crash"
  yet used printStackTrace(); route it through Log.w with context.
- OpenTimestamps: log the swallowed merge failure via Log.w; drop the
  print-then-rethrow (the rethrown exception already carries the trace).

Socket-protocol writer.println(...) and README/KDoc println examples are
left as-is — they are wire I/O and documentation, not logging.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EK3TrDkP1EXj1d62oKJMdc
2026-07-02 13:31:47 +00:00
Claude 292473ee26 feat(quartz): let consumers own logging via a swappable LogSink
Quartz already funnels every diagnostic through the `Log` facade, but the
sink was hardcoded per platform (android.util.Log / System.err / NSLog /
println), so a consuming app couldn't route Quartz logs into its own stack
(Timber, SLF4J, Crashlytics, a file, a test buffer, or /dev/null).

Add a `LogSink` fun interface and a replaceable `Log.sink`, defaulting to
`PlatformLogSink` which reproduces the historical per-platform behavior.
All ~225 call sites and the `Log.*` signatures are unchanged; the lazy
`() -> String` overloads still short-circuit on `minLevel` before the
lambda runs, preserving the allocation-free fast path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EK3TrDkP1EXj1d62oKJMdc
2026-07-02 13:25:59 +00:00
Vitor PamplonaandGitHub 016a5bd8b1 Merge pull request #3452 from vitorpamplona/claude/quartz-searchable-event-audit-fhvjws
Implement SearchableEvent interface for NIP-50 search support
2026-07-02 09:13:10 -04:00
Claude 35d0aa0ebc feat(quartz): index more event kinds for NIP-50 full-text search
Several event classes carried human-readable text (titles, names,
descriptions, prompts, free-text notes) but did not implement
SearchableEvent, so their content never made it into the SQLite FTS
index. Implement SearchableEvent on:

Tier 1 (titles/names/descriptions):
- NIP-15 marketplace: ProductEvent, StallEvent, AuctionEvent,
  MarketplaceEvent (name/description/about parsed from JSON content)
- Podcasting20TrailerEvent (title + content)
- TextNoteModificationEvent (proposed text + edit summary)
- GitStatusEvent base -> covers kinds 1630-1633 (status message)
- NIP-29 EditMetadataEvent (group name + about; added name()/about())

Tier 2 (free-text prose / labels):
- LiveActivitiesRaidEvent (raid message)
- CalendarRSVPEvent (RSVP note)
- MintRecommendationEvent (mint review)
- LabelEvent (content + label values)
- P2POrderEvent (maker name, currency, payment methods)
- NIP90 request events: text generation (prompt), image generation
  (prompt + negative prompt), text-to-speech (text)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P5MN4vF4JJG7xFCofJAHg8
2026-07-02 13:10:20 +00:00
Vitor PamplonaandGitHub d9c423eabf Merge pull request #3449 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-07-02 08:43:29 -04:00
Vitor PamplonaandGitHub 13f65dec26 Merge pull request #3450 from vitorpamplona/claude/compose-signature-field-u7rbx6
Add compose signature setting to auto-append custom text to posts
2026-07-02 08:42:20 -04:00
vitorpamplonaandgithub-actions[bot] 5c40187ba0 chore: sync Crowdin translations and seed translator npub placeholders 2026-07-02 11:05:32 +00:00
Vitor PamplonaandGitHub 8a618a92b0 Merge pull request #3432 from nrobi144/feat/desktop-privacy-lock
feat(desktop): Privacy lock for Messages column
2026-07-02 07:02:59 -04:00
nrobi144 cac54001da chore: retrigger CI after flaky macOS AppStateMachineTest
AppStateMachineTest.bootstrapSubscriptionFiresAtMostOncePerAccountLoad
hit a ConcurrentModificationException on macos-latest only. Test passes
locally 5/5 runs and every other CI check on this PR is green
(lint, Linux DEB, Windows MSI, Android, iOS, Compose smoke). Empty
commit to re-run the macOS DMG job.
2026-07-02 10:17:06 +03:00
nrobi144 33bb81dddb Merge remote-tracking branch 'upstream/main' into feat/desktop-privacy-lock
# Conflicts:
#	desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt
2026-07-02 07:20:44 +03:00
Claude b0834b8d8a feat: add compose signature pre-filled in text-based post screens
Adds a Signature field to Compose Settings (global UI settings, DataStore
persisted). When opening any text-based composer — new note, reply, quote,
poll, NIP-22 comment (reply/hashtag/geohash/url), or a new long-form
article — the signature is appended to the message with a blank line,
keeping the cursor at the start so the user types above it.

Drafts, forks, and version edits are skipped since their content already
carries (or deliberately omits) a signature, and an untouched
signature-only message is treated as blank so closing the composer never
auto-saves a junk draft.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vq4JQPB9m62nJ8Vdp7xVLN
2026-07-02 03:41:00 +00:00
Vitor PamplonaandGitHub e6728f8393 Merge pull request #3447 from vitorpamplona/claude/light-theme-pulldown-styling-2avguo
Remove elevation from bottom and top sheet surfaces
2026-07-01 20:24:17 -04:00
Vitor PamplonaandGitHub 05eb6db9cd Merge pull request #3446 from vitorpamplona/claude/relay-damus-shutdown-hlno35
Centralize default search relays into DefaultSearchRelayList
2026-07-01 20:23:35 -04:00
Claude 690bab0f0f fix: drive search-relay examples text from DefaultSearchRelayList
The "Good options are: …" hint in the search-relay setup dialog listed a
hardcoded, drifting subset of relays (and the zh locales had corrupted
hostnames like "reiny.nostr.band"). Make it dynamic instead:

- search_relays_not_found_examples now ends in a %1$s placeholder across
  all 57 locales.
- AddSearchRelayListDialog fills it from
  DefaultSearchRelayList.joinToString { " - ${it.displayUrl()}" }, so the
  hint always reflects the shared AmethystDefaults search set and stays in
  sync with the "reset to defaults" button right below it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Ttcqa3V78bugGraGhtehj
2026-07-02 00:11:38 +00:00
Claude 44edf3314a fix: remove Material 3 elevation tint from browser pull sheets on light theme
The embedded browser's top pull-down (TopControlSheet) and bottom pull-up
(BottomConsoleSheet) both drew with tonalElevation. Material 3 recolors a
Surface only when its color is EXACTLY colorScheme.surface, swapping in
surfaceColorAtElevation() which blends surfaceTint (= primary, the Amethyst
purple) over the surface. On the light theme that near-white + purple mix
reads as a bright pink/lilac cast instead of the plain background.

- TopControlSheet: drop tonalElevation to 0 so it renders the plain
  background color; keep shadowElevation to lift it off the page.
- BottomConsoleSheet: drop both elevations. It docks flush against the
  bottom navigation bar, so shadowElevation cast a shadow onto that bar (a
  seam breaking the flush look); the tonalElevation had no color effect
  anyway since its color isn't exactly colorScheme.surface. The grabber and
  divider still separate the panel from the page above it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJ2VbDz7C6hvHVsLpdecYf
2026-07-02 00:07:00 +00:00
Claude 461aa57b57 fix: use AmethystDefaults search relays and drop dead relay.nostr.band
relay.nostr.band has been decommissioned. Remove it from every runtime
relay list and route search-relay defaults through the shared
AmethystDefaults.DefaultSearchRelayList in commons:

- amy NipCommand: SEARCH_RELAYS now = DefaultSearchRelayList (drops the
  hardcoded relay.nostr.band/nostr.wine pair; RelayUrlNormalizer import
  no longer needed).
- desktop DesktopRelayCategories: DEFAULT_SEARCH_RELAYS now =
  DefaultSearchRelayList instead of a single relay.nostr.band entry
  (which would otherwise be empty after removal).
- desktop DefaultRelays and FollowPacks DISCOVERY_RELAYS: drop
  relay.nostr.band.
- Update NIP-50 example hostnames in desktop comments, the search-relay
  editor help text, and the localized search_relays_not_found_examples
  string across all locales to nostr.wine.

Preview sample data, captured sample-event JSON, and quartz test
fixtures that mention relay.nostr.band are left untouched (no runtime
effect).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Ttcqa3V78bugGraGhtehj
2026-07-01 23:59:15 +00:00
Claude b1fda59cd6 fix: drop relay.damus.io from default relay lists ahead of shutdown
relay.damus.io is being decommissioned, so remove it from every runtime
default/fallback relay set to stop the app and amy from wasting connection
slots on a dead host:

- commons Constants: remove `damus`; it dropped out of `bootstrapInbox`
  (default NIP-65 inbox) and `eventFinderRelays` (default outbox/fallback),
  both still carrying 6 healthy relays.
- ChessConfig: remove damus from CHESS_RELAYS / CHESS_RELAY_NAMES, leaving
  the 3 relays the FETCH_TIMEOUT comment already assumes.
- desktop DefaultRelays: remove damus and the also-dead relay.snort.social.
- desktop FollowPacks DISCOVERY_RELAYS: remove damus.
- amy NipCommand SEARCH_RELAYS: swap damus for the NIP-50-capable nostr.wine.

Comments, @Preview sample data, and test fixtures that mention damus.io are
left untouched — they have no runtime effect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Ttcqa3V78bugGraGhtehj
2026-07-01 23:35:24 +00:00
David KasparandGitHub 572fdb5ff4 Merge pull request #3438 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-07-01 22:21:00 +01:00
vitorpamplonaandgithub-actions[bot] 73c9e2086a chore: sync Crowdin translations and seed translator npub placeholders 2026-07-01 21:19:15 +00:00
Vitor PamplonaandGitHub 19d1167ccd Merge pull request #3441 from vitorpamplona/claude/podcast-event-kinds-merge-vv24gd
Add podcast authoring UI and NIP-XX Podcasting 2.0 support
2026-07-01 17:16:46 -04:00
Vitor PamplonaandGitHub 4a66435263 Merge pull request #3445 from vitorpamplona/claude/sqlite-event-store-no-fts-9k6y8a
Add optional full-text search indexing toggle to SQLiteEventStore
2026-07-01 17:14:22 -04:00
Claude 8b938396a0 refactor(quartz): move FTS toggle into IndexingStrategy
Fold the full-text-search on/off switch into `IndexingStrategy` as
`indexFullTextSearch` (default `true`) instead of a separate top-level
`enableFullTextSearch` constructor param on `EventStore`/`SQLiteEventStore`.

`IndexingStrategy` is already the single place that decides which indexes
the store builds — every field is a per-index toggle with a size/speed
tradeoff, and `QueryBuilder` already receives it — so FTS, being just
another index, belongs there rather than split across two config surfaces.

Behaviour is unchanged: the module's no-op path and the QueryBuilder
"search matches nothing" guards now read the flag via the strategy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BjzUpY8H31c7ux669zytWg
2026-07-01 21:11:26 +00:00
Claude cd4edefce0 feat(quartz): allow SQLite event store without FTS indexing
Add an `enableFullTextSearch` flag (default `true`) to `EventStore` and
`SQLiteEventStore` so deployments that never serve NIP-50 search from
SQLite — e.g. a relay that offloads search to an external engine like
Vespa — can skip the full-text-search write cost.

When disabled:
- `FullTextSearchModule` becomes an inert no-op: the `event_fts` virtual
  table and its `fts_foreign_key` delete trigger are never created,
  inserts skip `indexableContent()` + tokenization, and both reindex
  entry points return immediately.
- `QueryBuilder` short-circuits any query/count/delete filter carrying a
  non-empty `search` term to a "matches nothing" result (an empty-string
  search still imposes no constraint), so no SQL ever references the
  absent `event_fts` table. In a multi-filter union the search branch
  contributes nothing while the other filters resolve normally.

Everything else (replaceable/addressable handling, deletions,
expirations, right-to-vanish, negentropy) is unchanged, and the default
keeps FTS on for existing callers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BjzUpY8H31c7ux669zytWg
2026-07-01 21:00:06 +00:00
Vitor PamplonaandGitHub daa453c3c3 Merge pull request #3444 from vitorpamplona/claude/bottom-nav-reset-defaults-jxg5z0
Persist bottom bar defaults as blank sentinel for auto-migration
2026-07-01 16:59:01 -04:00
Claude d723e93e7a fix(quartz): drop commas from podcast test names for Kotlin/Native
Kotlin/Native (the iOS test target) rejects commas in backtick function
names, so test-quartz-ios failed to compile even though jvmTest — which
allows them — passed. Rename the two offending tests. No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JGa1EM5KWyDo1o5Yr6sS18
2026-07-01 20:55:58 +00:00
Claude 679c337427 feat(amethyst): persist default bottom bar as a sentinel so resets auto-migrate
The bottom-bar settings screen already offers a "Restore defaults" action, but it
saved the concrete default list to disk. That pinned the user to the default of
the version they reset on, so a future release that changes DefaultBottomBarEntries
would never reach them. The same happened to users who never touched the bar: any
unrelated settings save wrote the concrete default list.

Persist "follow the defaults" as a blank sentinel instead. On load a blank value
already resolves to the current DefaultBottomBarEntries, so whenever the default
changes in a later version, every user on the defaults is migrated automatically.
Genuinely customized bars are still stored verbatim as JSON.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011EBk58qUYuyKxz9Ju4VpEq
2026-07-01 20:50:42 +00:00
Vitor PamplonaandGitHub 9c191137d5 Merge pull request #3443 from vitorpamplona/claude/nip11-document-builder-gq7eeq
Add type-safe DSL builder for NIP-11 relay information documents
2026-07-01 16:45:31 -04:00
Claude 266f59959e Merge remote-tracking branch 'origin/main' into claude/podcast-event-kinds-merge-vv24gd 2026-07-01 20:30:27 +00:00
Claude 8085f82ca4 feat(podcasts): standard ReactionsRow on each episode in the Podcast screen
Replace the removed per-episode comment chip with the full NoteCompose
ReactionsRow (comment / zap / react) on every episode row in a podcast's
screen — same engagement affordance as the show header and every other note.
addPadding = false so it aligns within the row's existing padding.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JGa1EM5KWyDo1o5Yr6sS18
2026-07-01 20:27:37 +00:00
Vitor PamplonaandGitHub 03c71d1d4e Merge pull request #3440 from davotoula/fix/sonar-encoding-nul-bytes
Escape raw NUL bytes to fix Sonar encoding warning
2026-07-01 16:27:23 -04:00
Vitor PamplonaandGitHub 02df0672e7 Merge pull request #3442 from vitorpamplona/claude/nip05-function-docs-rcebl7
docs: Add NIP-05 identifier resolution guide and update skill
2026-07-01 16:24:13 -04:00
Claude 2f66c31f43 docs(quartz-integration): document the NIP-11 relay info builder
Add a "NIP-11 Relay Information Document" section to the quartz-integration
skill (the guide AI consults when using Quartz in external projects),
covering the relayInformation { } DSL, serving it over
application/nostr+json, the nested limitation/fees/retention builders, and
the limitation(RelayLimits) sync overload. Also add Quick Reference rows
and extend the skill trigger description to the run-a-relay case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C64Yy2d3na7Y28u7GRrHMX
2026-07-01 20:23:35 +00:00
Claude c871597fc9 feat(quartz): add a type-safe NIP-11 relay info builder
Relay operators wiring up a Quartz-based relay had to hand-write the
NIP-11 document as a large JSON string, which is error-prone and drifts
from the model. Add a DSL builder so they can describe the document in
Kotlin instead:

    val info = relayInformation {
        name = "sot"
        description = "NIP-50 profile search ranked by Nostr web-of-trust"
        software = "https://github.com/vitorpamplona/sot"
        version = "0.1"
        supports(1, 11, 42, 50)
    }
    call.respondText(info.toJson(), ContentType.parse(Nip11RelayInformation.CONTENT_TYPE))

The builder covers every field, with nested `limitation { }` / `fees { }`
DSLs, repeatable list helpers, a `retention(...)` entry adder, and a
`limitation(RelayLimits)` overload that advertises exactly the limits the
relay enforces so the two can't drift.

Also fix FlexibleIntListSerializer to emit numeric `supported_nips` as
JSON integers ([1,11,42,50]) instead of quoted strings, matching the
NIP-11 spec; non-numeric ids still fall back to strings. Geode's default
document now builds via the DSL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C64Yy2d3na7Y28u7GRrHMX
2026-07-01 20:19:03 +00:00
Claude 4147298494 docs: explain NIP-05 identifier resolution in nostr-expert skill
AI agents were hand-rolling their own hex/npub/nprofile/NIP-05 resolvers
(e.g. a bespoke `resolveObserver` with its own well-known fetch and JSON
parse) instead of using `resolveUserHexOrNull` in
`quartz/nip05DnsIdentifiers/`, which already handles every identifier form.

Add discoverable explainers so the canonical functions surface before an
agent reaches for a hand-rolled version:

- New reference `references/nip05-identifiers.md`: full API surface
  (`resolveUserHexOrNull`, `Nip05Client`, `Nip05Id`, `Nip05Parser`,
  `KeyInfoSet`, Namecoin `.bit`, `OkHttpNip05Fetcher`) plus the
  hand-rolled anti-pattern to avoid and the  replacement.
- SKILL.md: new "Resolving User Input to a Pubkey" section, a
  When-to-Use bullet, Bundled Resources + Quick Reference rows, and the
  identifier-resolution trigger added to the skill description.
- nip-catalog.md: fix the stale NIP-05 entry (referenced a nonexistent
  `Nip05Verifier.kt`) to point at `UserHexResolver.kt` / `Nip05Client.kt`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bb8txetygxSnLXHJhPZSwH
2026-07-01 20:14:06 +00:00
Vitor PamplonaandGitHub 33abc8acb9 Merge pull request #3439 from vitorpamplona/claude/quartz-optional-auth-policy-gkf5b9
Add OptionalAuthPolicy for NIP-42 without requiring authentication
2026-07-01 15:42:17 -04:00
Claude f7afc56752 feat(quartz): add optional NIP-42 AUTH relay policy
Introduce OptionalAuthPolicy, a relay-server policy that runs the full
NIP-42 challenge/verify handshake — emitting the AUTH challenge on connect
and recording verified pubkeys into the connection scope — but never
requires it: EVENT, REQ, and COUNT are always accepted, so clients that
ignore the challenge keep working.

It subclasses FullAuthPolicy and only relaxes the EVENT/REQ/COUNT gates, so
the authorize() hook and per-connection authenticatedUsers set behave
identically; downstream policies can still gate or rewrite on caller
identity. Wire it into geode via an optional_auth config option and a
--optional-auth CLI flag (ignored when require_auth/--auth is set, since
mandatory AUTH already sends the challenge).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BJWy4dBBYLbqthhLvbcZh7
2026-07-01 19:30:19 +00:00
davotoula f7dd2c210b fix: escape raw NUL bytes to fix Sonar encoding warning
Four Kotlin sources embedded literal NUL (0x00) bytes, used as string
separators and as literal characters in KDoc comments. A NUL is valid
UTF-8 (U+0000) so decoders accept it, but SonarScanner flags an embedded
NUL in a text source as a file-encoding problem, and git tracked these
files as binary.
2026-07-01 21:25:27 +02:00
Claude 9ac1ba692f feat(podcasts): Top Supporters leaderboard, in-app chapters, transcript viewer
Brings three of PodStr's engagement/reading widgets to Amethyst's podcast
screens, reusing existing infrastructure:

- Top Supporters — a sats-ranked zap leaderboard on the show header, top 3
  flagged with gold/silver/bronze medals, tap-through to profiles. Aggregates
  the show note's zaps through the same LiveActivityTopZappersAggregator the
  live-stream leaderboard uses. Anonymous zaps collapse into one bucket.
- In-app Chapters — fetches the Podcasting-2.0 chapters.json referenced by the
  episode's `chapters` tag and renders a collapsible, tappable list; tapping a
  chapter seeks the live media controller (same seek path as soundbites).
- Transcript viewer — fetches the `transcript` file and shows it in a
  collapsible scrollable panel, stripping VTT/SRT scaffolding into flowing text.

Adds PodcastRemoteContent (a bounded URL text fetcher) for the two off-event
side files. Also removes the now-redundant per-episode comment chip from the
episode list rows — the show ReactionsRow and the episode thread already cover
commenting.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JGa1EM5KWyDo1o5Yr6sS18
2026-07-01 19:22:19 +00:00
Vitor PamplonaandGitHub d0497eaed7 Merge pull request #3437 from vitorpamplona/claude/ai-submissions-spotless-check-gftv4v
Add pre-push Spotless formatting gate
2026-07-01 14:57:14 -04:00
Claude be58d98d2b chore: gate remote submissions on spotless formatting for AI sessions
Add a PreToolUse hook (.claude/hooks/pre-push-spotless.sh, wired in
.claude/settings.json) that runs spotlessApply before a git-push subcommand
or the create_pull_request MCP tool. If spotless reformats tracked Kotlin,
or reports a non-autofixable lint, the call is blocked so the fix is
committed first -- turning CI's spotlessCheck failure into an in-session
block. Gradle infra/network failures warn and allow, leaving CI as the
backstop. Boundary detection tokenizes the command so the words appearing
in a commit message do not trip the gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015nWg1Wwq5xCE7kPFzWY95o
2026-07-01 18:48:15 +00:00
Claude dc9579f994 Merge remote-tracking branch 'origin/main' into claude/podcast-event-kinds-merge-vv24gd 2026-07-01 18:39:59 +00:00
Vitor PamplonaandGitHub 4e90fbcc2c Merge pull request #3436 from vitorpamplona/claude/download-button-blossom-uri-ii41o1
Support Blossom URI scheme for media downloads
2026-07-01 14:39:04 -04:00
Claude f35d40cc00 style: drop redundant EOL comments flagged by ktlint in Hex.kt
New KDoc blocks on isHex/isHex64 already state the "~47ns" and
"~30% faster" perf notes, so the trailing EOL comments between the
KDoc and the function now trip ktlint's standard:no-consecutive-comments
rule ("an EOL comment may not be preceded by a KDoc"). Remove them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BawgXifvcPidMMqJ719Ka2
2026-07-01 18:33:19 +00:00
Claude 0689a7d244 fix: resolve blossom: URIs when saving media to gallery
The download/save-to-local button handed the raw content URL straight to
OkHttp. For BUD-10 `blossom:` URIs this failed with "expected scheme http
or https but was blossom" because OkHttp only speaks http/https.

Resolve `blossom:` URIs to a concrete server URL via BlossomServerResolver
(the same resolver the Coil/ExoPlayer pipeline uses) before downloading, in
both save entry points (AccountViewModel.saveMediaToGallery and the zoomable
dialog's save action). When no hosting server can be found, the save reports
an error instead of crashing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BawgXifvcPidMMqJ719Ka2
2026-07-01 18:32:34 +00:00
Vitor PamplonaandGitHub 308058d242 Merge pull request #3435 from vitorpamplona/claude/quartz-hex-utilities-docs-e4eawy
Document Quartz utilities: hex, time, random, hashing, bech32
2026-07-01 14:17:01 -04:00