Commit Graph
17332 Commits
Author SHA1 Message Date
Claude 55884f81eb refactor(marmot): extract the incoming-sync policy from the CLI into commons
Context.syncIncoming carried real protocol policy — the NIP-59 gift-wrap
since-cursor rule (2-day lookback, advance only when events arrive),
per-group cursor bookkeeping, and the MIP-00 consumed-KeyPackage
rotation — that the Android app implements separately. Divergence there
silently drops DMs, so the policy now lives once in
commons/marmot/MarmotSyncPolicy with Cursors/Relays/drain/publish
injected, and the CLI Context wires itself in as a thin adapter.
Behavior is unchanged (the body moved verbatim, comments included).

Also resolves aliases from the per-account aliases.json in
Context.requireUserHex, so 'amy dm send bob ...' works with a local
alias — previously aliases.json was written but never read.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CP4kfLCa3wWtE8Khy21Pkj
2026-07-18 20:58:45 +00:00
Claude 7b5d9a0fe4 feat(cli): contract sweep for notes/nsite/offer/podcast/relay/search/zap verbs
Completes the module-wide contract sweep (N-Z families):
- rejectUnknown() everywhere Args is constructed
- publishGuard on single-event publishes (post, publish, profile edit,
  nsite/napplet publish, podcast + podcast20, relaygroup join/leave/
  message/invite/edit/put-user/remove-user)
- USAGE constants + route(help=...) / --help fast-paths; nsite/napplet
  publish/serve/list, offer discover/pay and zap --with are now
  documented in-binary; subscribe's USAGE pins the NDJSON stream shape
- notes post parses flags before positionals (flags may appear anywhere)
- search note: --kind canonical (--kinds kept as alias); search user
  default limit 20 -> 50
- --identifier accepted as alias of --d in nsite/napplet/podcast20
- relay dispatch gains a full USAGE (--help) documenting every noun

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CP4kfLCa3wWtE8Khy21Pkj
2026-07-18 20:58:19 +00:00
Claude 11901df2c6 feat(cli): contract sweep for event/git/geochat/graperank/group/key/login verbs
Same contract as the A-D sweep, applied to the E-M command families:
- rejectUnknown() everywhere Args is constructed (typo'd flags -> bad_args)
- publishGuard on single-event publishes (total rejection -> non-zero)
- USAGE constants + route(help=...) / --help fast-paths; graperank's
  full sub-verb set (including the previously undocumented 'followers')
  and 'key validate' + the --pw alias are now documented in-binary
- geochat --relay is a strict comma-list (bare positional relays kept);
  geochat listen default limit 500 -> 50
- graperank update/probe aliases now print deprecation notes
- git --identifier accepted as alias of --d
- marmot message list defaults to --limit 50 (0 = everything)
- dm-style guards for marmot message send/react/delete and group edits

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CP4kfLCa3wWtE8Khy21Pkj
2026-07-18 20:56:23 +00:00
Claude 471883a9f2 feat(cli): contract sweep for admin/await/blossom/bunker/concord/dm/cashu verbs
Applies the new CLI contract across the A-D command families:
- rejectUnknown() after flag parsing, so typo'd flags fail with bad_args
  instead of silently no-oping
- publishGuard on single-event publishes: total relay rejection now
  exits non-zero ('rejected') instead of 0
- per-group USAGE constants wired into route(help=...) and --help
  fast-paths on flat commands; previously invisible sub-verbs
  (blossom media/report, the concord moderation set, the whole cashu
  surface, NIP-86 method list) are now documented in-binary
- dm send/send-file parse flags before positionals (flags may appear
  anywhere); dm list/await accept a positional USER as alternative to
  --peer; dm list caps at 50 by default
- concord create: --relay is canonical (--relays kept as alias)
- cashu receive resume: deprecation warning pointing at 'complete'
- offer/debit: --timeout is seconds (was raw milliseconds)
- Identity.fromBunkerUri now delegates to quartz NostrConnectURI
  parseBunker instead of duplicating the URI parsing

BREAKING (--json): unknown flags and malformed numeric/relay/author
flag values now exit 2 where they were previously ignored.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CP4kfLCa3wWtE8Khy21Pkj
2026-07-18 20:56:09 +00:00
Claude 6bcae39b83 fix(cli): repair the exit-code and flag-parsing contract
- Output.error now derives the exit code from the error code
  (bad_args -> 2, timeout -> 124, else 1), so every
  'return Output.error(...)' site honours the documented contract.
  Previously ~225 bad_args sites exited 1 while the docs promised 2,
  and two timeout paths (nostrconnect wait, namecoin lookup) exited 1
  instead of 124.
- Args: literal '--' ends flag parsing (escape hatch for values that
  start with '--'); intFlag/longFlag reject non-numeric values instead
  of silently using the default; requireFlag/positional no longer
  double-print to stderr; new rejectUnknown() turns typo'd flags into
  bad_args failures; new 'help' detection.
- route() understands --help/-h/help (prints group usage, exit 0) and
  names the expected verbs on an unknown sub-verb.
- Unknown or missing top-level subcommand now emits a proper bad_args
  error (JSON-aware under --json) plus a one-screen verb list instead
  of dumping the full 400-line usage.
- RawEventSupport: --relay/--kind/--author/--id/--since/--until/--limit
  entries that do not parse are now bad_args errors; previously an
  unresolvable --author was silently DROPPED and the query ran with a
  weaker filter than requested. New shared publishGuard() reports
  'rejected' (exit 1) when every relay refuses an event.
- runCli() seam extracted from main() plus an 'amy.home' system-property
  override of DEFAULT_ROOT so the new JVM test suite can drive the CLI
  in-process; first contract tests: ArgsTest, ExitCodeContractTest,
  JsonContractTest (NIP-19 vector goldens).

BREAKING (--json): error code pow_timeout is now timeout; exit codes
for bad-argument failures move from 1 to 2 as documented.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CP4kfLCa3wWtE8Khy21Pkj
2026-07-18 20:54:14 +00:00
Vitor PamplonaandGitHub 7e3c01ab08 Merge pull request #3637 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-07-18 16:18:38 -04:00
vitorpamplonaandgithub-actions[bot] c5f10c7518 chore: sync Crowdin translations and seed translator npub placeholders 2026-07-18 18:06:39 +00:00
Vitor PamplonaandGitHub 0d534a3149 Merge pull request #3638 from vitorpamplona/claude/post-image-render-shift-b4vx28
Fix media detection for malformed imeta MIME types
2026-07-18 14:04:00 -04:00
Claude 4653951b54 fix: recover media type from file extension when imeta MIME is malformed
Primal iOS writes a bare subtype in the imeta MIME tag (`m jpeg`) instead of a
full type (`m image/jpeg`). `createMediaContent` classified media only by the
MIME `startsWith` prefixes when a MIME was present, so a bare subtype matched
neither image/video/pdf and the whole imeta was dropped: the URL rendered as a
plain link. That path is doubly bad — it discards the imeta `dim`/blurhash, so
the loading placeholder cannot reserve the image's height and the feed jumps
when the bitmap finally arrives, and it forces a URL-preview network round-trip
just to rediscover the type the imeta already declared.

Fall back to file-extension detection whenever the type is still unknown after
the MIME/data: checks. This recovers the `.jpg` (or `.mp4`, …) classification
and keeps the imeta metadata, so the image renders through the fast media path
with its dimensions reserved up front. `data:` URIs keep their type in the
prefix and are left unprobed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NQ9F7xnRvRgBmVNbo8hRKc
2026-07-18 17:40:31 +00:00
Vitor PamplonaandGitHub 8ba0d0c317 Merge pull request #3633 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-07-18 12:57:53 -04:00
Vitor PamplonaandGitHub 854c9907e2 Merge pull request #3636 from vitorpamplona/claude/strictmode-disk-read-violation-2s5q8e
Fix StrictMode DiskReadViolation in BootCompletedReceiver
2026-07-18 12:57:47 -04:00
Claude 20e86b8998 fix: move BootCompletedReceiver disk read off the main thread
isEnabled() reads plain SharedPreferences (a synchronous disk read, by
design) and scans the accounts cache. Since onReceive runs on the main
thread, this triggered a StrictMode DiskReadViolation at boot. Wrap the
work in goAsync() + a background thread so it runs off the main thread
while keeping the receiver alive until the check completes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P4tx1QSE27uoDpnxZZ7msT
2026-07-18 14:39:03 +00:00
vitorpamplonaandgithub-actions[bot] 9fb3603ed6 chore: sync Crowdin translations and seed translator npub placeholders 2026-07-18 14:24:00 +00:00
Vitor PamplonaandGitHub 2466132fb7 Merge pull request #3635 from vitorpamplona/claude/amethyst-blossom-client-p35sql
Add Blossom blob manager UI and BUD-07 payment handling
2026-07-18 10:21:08 -04:00
Claude a25c5f9518 fix: harden Blossom sync concurrency and cancellation
Audit fixes for the Blossom client:

- BlossomBlobManagerViewModel: use StateFlow.update{} for the presence
  matrix so the Main-thread sync collector and IO-thread delete/mirror
  actions can't lose each other's writes; add refreshJob de-dup so two
  quick refreshes can't interleave; rethrow CancellationException; bound
  the /list HEAD-probe backfill with a Semaphore(8).
- BlossomClient.has(): rethrow CancellationException instead of
  swallowing it as 'not found'.
- BlossomSyncForegroundService: drop the stale 'running' de-dup guard so
  a fresh sweep always gets foreground protection.
- CLI mirror: strip query/fragment before extracting the sha256.
- DisplayBlossomSyncProgress: retain the last state so the slide-out exit
  animation still has content to draw after the state clears.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ckbnz1N94W1hnNC9xpsCNP
2026-07-18 13:46:06 +00:00
Claude 84640a5890 perf(blossom): parallelize "sync all" across servers
The sweep was strictly sequential (one mirror at a time). Now it parallelizes
ACROSS servers but stays serial WITHIN each server, so every server gets a steady
one-at-a-time stream (no rate-limit storms) while all of the user's servers work at
once — wall-clock drops to roughly the slowest single server's queue instead of the
sum of everything.

- BlossomMirrorQueue: group work by target server, run one coroutine per server, and
  update the shared progress atomically via MutableStateFlow.update from the parallel
  workers. Extracted mirrorOne() for the per-task call.
- Dropped the now-ambiguous currentHost from BlossomSyncState (and the banner /
  notification text) since multiple servers are in flight at once.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ckbnz1N94W1hnNC9xpsCNP
2026-07-18 13:31:36 +00:00
Vitor PamplonaandGitHub bc3c3460c0 Merge pull request #3634 from davotoula/fix/graperank-flag-constants
Extract GrapeRank CLI flag names into constants
2026-07-18 08:11:37 -04:00
davotoula ab6682d521 refactor(cli): extract GrapeRank CLI flag names into constants 2026-07-18 11:00:41 +01:00
davotoula bb75ebf87c update cz, se, pt, de in amethyst 2026-07-18 10:16:46 +01:00
davotoula 314c5e41f2 update cz, se, pt, de in commons 2026-07-18 10:16:35 +01:00
davotoula 2f0c178eab Update translation skill for new commons strings 2026-07-18 10:16:18 +01:00
Claude 5d54a0ff90 refactor(fgs): extract FlowProgressForegroundService; add dataSync Blossom sync service
Generalizes the shared foreground-service scaffolding (channel setup, start/stop-
when-idle over a StateFlow, the ProgressStyle notification skeleton, tap + cancel
intents, onTimeout) into an abstract FlowProgressForegroundService<T>. Kept as a
base — NOT one mega-service — because Android 14+ binds foregroundServiceType to
behavior, so each workload keeps its own subclass with the correct type.

- FlowProgressForegroundService: the shared base.
- PowMiningForegroundService: refactored onto the base (still shortService); mining
  state maps to the card via render(); behavior preserved.
- BlossomSyncForegroundService: new, typed dataSync (the right type for a multi-file
  sync that exceeds shortService's ~3-min budget), bound to blossomMirrorQueue.state.
  Started (from the foreground, as dataSync requires) when a "Sync all" sweep begins,
  so it survives backgrounding; stops itself when done.
- BlossomMirrorQueue: publishes the active state and fires onActive() synchronously so
  the FGS starts with live state; wired in AppModules.
- Manifest: FOREGROUND_SERVICE_DATA_SYNC permission + the service declaration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ckbnz1N94W1hnNC9xpsCNP
2026-07-18 04:33:10 +00:00
Claude 4845e95c9f chore(blossom): rename "My Blossom Data" to "My Blossom Files"
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ckbnz1N94W1hnNC9xpsCNP
2026-07-18 03:59:37 +00:00
Claude ecbe2c6704 feat(blossom): app-level "Sync all" with floating progress; rename to "My Blossom Data"
Sync-all now runs like the PoW miner — an app-level job with a floating progress
banner — instead of a screen-bound loop, so it keeps going as the user navigates:

- BlossomMirrorQueue (app-level, on applicationIOScope via Amethyst.instance): runs
  the BUD-04 fan-out, exposes an aggregate BlossomSyncState for the banner and a
  results SharedFlow so an open manager flips each pill green/grey live. Servers that
  need payment are skipped (pay those per-row).
- DisplayBlossomSyncProgress: a bottom floating banner mounted at the navigation root
  (sibling of the mining/broadcast banners) with a determinate bar, "x / N · host",
  failed count, and cancel/dismiss.
- Manager: a "Sync all" banner appears when any file has gaps; tapping it enqueues the
  sweep and optimistically spins the affected pills.

Also, per request: the screen is renamed "My Blossom Data" and moved into the left
drawer's "You" section (just before My Emoji Packs); the standalone Settings-catalog
entry is removed and the Media Servers shortcut relabeled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ckbnz1N94W1hnNC9xpsCNP
2026-07-18 03:57:58 +00:00
Claude da633342bc feat(blossom): faster, more legible blob manager (load, progress, refresh)
Addresses three UX gaps on the "Manage stored files" screen:

- Loading was a full-screen spinner until every server × every blob HEAD probe
  finished, sequentially — and it even HEAD-probed servers that had already
  returned a /list. Now: /list runs across all servers in parallel and renders
  immediately; only servers that don't implement /list are HEAD-probed, and those
  run in parallel too. A server that listed its blobs needs no probe. Result is
  shown as soon as the lists land.

- Mirror-to-missing gave no feedback. Presence is now a per-server state
  (present/missing/pending); the target pill shows a spinner while mirroring and
  turns green on success or back to grey on failure — updated in place, no full
  reload. Delete spins the pill then greys it (and drops the row when it's gone
  everywhere).

- The refresh control was a FAB mislabeled "Retry". It's now a proper refresh
  action in the top bar (a spinner while loading); the contextual "Retry" stays
  only on the error state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ckbnz1N94W1hnNC9xpsCNP
2026-07-18 03:34:42 +00:00
Claude 6920dc6e82 fix(blossom): don't let mirroring corrupt upload progress or fail the upload
Two regressions from the mirror-on-upload change:

1. Progress state stuck. verifyHeader() ends by calling finish() (state=Finished,
   progress=1.0), but mirrorToOtherServers then ran updateState(0.95,
   ServerProcessing) and never restored Finished — so the on-screen progress ended
   pinned at "processing / 95%" and isUploading never cleared. Mirroring is now
   fire-and-forget on account.scope AFTER the upload finishes: it never touches the
   progress state, never delays the post, and can never turn a completed upload into
   a failure.

2. Upload could fail outright. The upload token was BUD-11 server-scoped; some
   servers reject an upload whose auth carries a `server` tag, surfacing as a bare
   "Uploading error:" with no detail. Upload-token replay isn't the threat scoping
   guards against (delete tokens are — those stay scoped), so upload/media tokens
   are no longer scoped. Also: blank exception messages now fall back to the class
   name so the dialog is never empty.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ckbnz1N94W1hnNC9xpsCNP
2026-07-18 03:05:37 +00:00
Claude 22396cf583 feat(blossom): redesign blob manager to match the app's modern UI
Brings the "Manage stored files" screen up to the design language main just
applied to the Media Servers screen:

- Rounded surfaceContainer cards instead of flat Cards.
- Image thumbnail, or a file-type glyph in a rounded secondaryContainer box
  for non-images.
- Monospace, truncated-both-ends hash; type · size subtitle.
- Per-server presence as pills with a status dot — allGoodColor tint for
  present, muted for missing — replacing the ambiguous same-shaped chips.
- A single MoreVert overflow (copy link / open / report / per-server delete)
  with leading icons, plus a full-width tonal "Mirror to missing" CTA.
- Icon-in-rounded-box empty/error states and an ExtendedFloatingActionButton
  refresh.
- BUD-07 pay dialog restored with a Bolt icon and tonal confirm; payment
  message now names the server.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ckbnz1N94W1hnNC9xpsCNP
2026-07-18 02:32:51 +00:00
Claude e87309e038 test(blossom): live interop smoke test for amy blossom (BUD-01/02/04/09)
Adds cli/tests/blossom/blossom-live.sh, an end-to-end harness that drives the
full amy blossom lifecycle against a REAL public Blossom server: upload → HEAD
check → download-and-verify-hash → list → cross-server mirror → delete. Verified
green (6/6) against files.sovbit.host with a mirror to blossom.primal.net, which
exercises the whole quartz/commons/CLI Blossom stack over the wire.

Server-side write rejections (whitelists, rate limits, payment) record as SKIP,
not FAIL — only a broken client contract (bad descriptor, hash mismatch,
unparseable list) fails — since public servers gate anonymous writes very
differently. Documented in cli/tests/README.md; state dir gitignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ckbnz1N94W1hnNC9xpsCNP
2026-07-18 02:16:17 +00:00
Vitor PamplonaandGitHub 4e53f26b38 Merge pull request #3630 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-07-17 22:10:07 -04:00
Claude 9bc436b0c2 feat(blossom): BUD-07 confirm-then-pay for paid-server mirroring
Adds a confirm-then-pay flow so a 402 from a paid Blossom server can be settled
from the app instead of only being reported.

- quartz: BlossomPaymentProof (settled Cashu token / lightning preimage) with the
  X-Cashu / X-Lightning retry headers; BlossomClient.mirror accepts a proof.
- BlossomPaymentHandler (Android): pays the challenge's BOLT-11 invoice via the
  account's existing NIP-47 (NWC) wallet and returns the preimage — it never
  handles keys or funds itself, only drives the connected wallet. Decodes the
  invoice amount for display.
- Blob manager: a mirror that hits 402 now raises a payment prompt; a dialog shows
  the amount and, on confirm, pays and retries the mirror, then continues with the
  remaining servers. Cancel leaves the blob unmirrored.

Cashu-only servers and the composer upload path still surface a clear message;
auto-settlement there can reuse this handler next. Not yet validated against a
live paid server.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ckbnz1N94W1hnNC9xpsCNP
2026-07-18 01:18:46 +00:00
vitorpamplonaandgithub-actions[bot] 69a5b3be00 chore: sync Crowdin translations and seed translator npub placeholders 2026-07-18 01:09:34 +00:00
Vitor PamplonaandGitHub 2cfac578fe Merge pull request #3631 from vitorpamplona/claude/concord-nip29-message-indicators-jwyvgt
Add unread indicators for NIP-29 relay groups and Concord channels
2026-07-17 21:06:55 -04:00
Vitor PamplonaandGitHub 67e02618e2 Merge pull request #3632 from vitorpamplona/claude/settings-search-placement-n1wkqw
Move settings search field to top bar
2026-07-17 21:04:37 -04:00
Claude 4f661a501f feat(blossom): blob-manager per-file controls + clear 402 on upload
- Blob manager rows now show an image thumbnail (image/* blobs) and Copy-link /
  Open controls, alongside the existing mirror-to-missing / delete / report.
- BUD-07: BlossomUploader now raises a typed BlossomPaymentException on a 402, and
  UploadOrchestrator surfaces a specific "server requires payment" message instead
  of a generic upload failure (auto-settlement via the NIP-60 wallet is still TBD).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ckbnz1N94W1hnNC9xpsCNP
2026-07-18 01:04:35 +00:00
Claude 5ad3e143e4 fix: keep Concord community unread flow alive across Refounding + dedupe emissions
Audit follow-ups on the Messages unread dot:

- concordCommunityHasUnreadFlow captured the community session once, but a
  Refounding rebuilds a still-joined community's session in place (same id, new
  object). Because the row's flow is remember(communityId)-scoped it was never
  recreated, so the fan-in kept watching the dead session and the dot froze.
  Re-resolve the session on every concordSessions.revision tick instead — the
  same signal ConcordServerRoomCompose already uses to refresh the row's
  name/icon — which also covers channel-set changes from folds.
- distinctUntilChanged() on both aggregated server flows so a new message that
  doesn't flip the has-unread boolean no longer pushes a redundant value into
  composition.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ZkjdHANMKyu2EbM7eEM8C
2026-07-18 00:55:30 +00:00
Claude 6c01ad2b10 feat(blossom): mobile upload controls — mirror + /media toggles, manager refresh
Adds first-class Blossom upload controls to the Media Servers screen and rounds
out the blob manager:

- New persisted account settings (LocalPreferences + AccountSettings):
  `mirrorUploadsToAllServers` (BUD-04, default on) and `optimizeMediaOnUpload`
  (BUD-05, default off), each with a change fn.
- UploadOrchestrator honours both: uploads via /media with a t=media token when
  optimize is on, and only mirrors when the mirror toggle is on.
- BlossomUploader gains a useMediaEndpoint flag to target /media vs /upload.
- "Upload behaviour" section on the Media Servers screen: mirror toggle,
  optimize-via-/media toggle, and a shortcut into the blob manager.
- Blob manager gains a refresh FAB.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ckbnz1N94W1hnNC9xpsCNP
2026-07-18 00:07:30 +00:00
Claude d0f2f08e19 fix(blossom): mirror and blob-manager use only the user's configured servers
Both the mirror-on-upload fan-out and the blob-manager matrix read the raw
kind-10063 list (BlossomServerListState.flow) instead of hostNameFlow, which
injects the 10 public DEFAULT_MEDIA_SERVERS when the user has none set. This
avoids fanning uploads out to — and listing/HEAD-probing across — public
servers the user never opted into.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ckbnz1N94W1hnNC9xpsCNP
2026-07-17 23:50:53 +00:00
Claude ed4b851518 feat(settings): host the settings filter in the app bar
Move the All Settings search field from a separate row below the top bar
into the app bar itself, replacing the static "Settings" title. The back
arrow is still shown only when the back stack can pop (deep-link entry),
so bottom-nav entries stay chromeless.

Adds SettingsSearchTopBar with a compact pill search field sized to fit
the app bar and leaves the shared TopBarWithBackButton untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RBSLyPYssVu8QdBrvCM7wk
2026-07-17 23:50:25 +00:00
Vitor PamplonaandGitHub 3e9da48c8a Merge pull request #3629 from vitorpamplona/claude/crowdin-ci-fix-lzm3ac
fix(ci): reclaim working-tree ownership after Crowdin Docker action
2026-07-17 19:45:47 -04:00
Claude 91765c56aa fix(ci): reclaim working-tree ownership after Crowdin Docker action
The crowdin/github-action runs inside a Docker container as root, so files
and directories it downloads are root-owned. When Crowdin creates a brand-new
locale folder (e.g. values-en-rGB/ under commons), the unprivileged runner
user in the peter-evans/create-pull-request step can't unlink files inside it,
so the branch checkout aborts with 'unable to unlink ... Permission denied'.

Add a chown step that reclaims ownership of the whole working tree after the
Crowdin action and before any git manipulation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0179E3AfsnB1NP87YmtAMZNX
2026-07-17 23:39:44 +00:00
Claude bb03cd2a3c feat(blossom): full-client protocol support across quartz, commons, CLI and Android
Extends Blossom support toward a full client on both the CLI and the mobile app.

Quartz (protocol):
- BlossomAuthorizationEvent: add t=media auth (BUD-05) and optional BUD-11
  `server` domain scoping on every factory (stops replayable upload/delete tokens)
- BlossomServerUrl: mirror/media/list/report path builders, BUD-06 preflight and
  BUD-07 payment header constants, and a lowercase bare-domain helper
- BlossomUploadResult: parse `ox` (BUD-05 original hash) and `nip94` (BUD-08)
- BlossomPaymentRequired: BUD-07 402 challenge model (Cashu/Lightning)
- BlossomReport: BUD-09 kind-1984 blob report reusing NIP-56 tag builders

Commons (shared JVM client, now in jvmAndroid so Android shares it too):
- BlossomClient gains mirror (BUD-04), list/delete (BUD-02), media (BUD-05),
  preflight/has (BUD-06/01), report (BUD-09) and typed 402 handling
- BlossomAuth: media/list/delete passthroughs with server scoping

CLI (first-class):
- amy blossom now routes all HTTP through the shared client and adds `media`
  and `report` verbs; auth tokens are scoped to --server

Android (first-class):
- uploads mirror to the user's other Blossom servers (BUD-04) best-effort
- new "Manage stored files" screen: per-server presence matrix (BUD-02 list +
  BUD-01 HEAD), delete, mirror-to-missing, and report actions

Tests: quartz URL/auth/descriptor/payment parsing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ckbnz1N94W1hnNC9xpsCNP
2026-07-17 23:36:41 +00:00
Claude 26b24c9125 feat: show unread dot for NIP-29 groups and Concord communities in Messages
The Messages list showed the purple "new messages" dot (NewItemsBubble) for
DMs, NIP-28 public chats, ephemeral chats, geohash chats and Marmot groups,
but hardcoded hasNewMessages = false for the four NIP-29 / Concord rows, so
those never indicated unread activity.

Wire real unread detection into all four, reusing the last-read markers their
open-chat screens already advance:

- Individual NIP-29 group row and Concord channel row compare the newest
  message against the persisted last-read timestamp, exactly like the Marmot /
  public-chat rows do.
- The collapsed "grouped by relay" (RelayGroupServerRoomNote) and "grouped by
  community" (ConcordServerRoomNote) rows light the dot when ANY child group /
  channel has unread, via new reactive fan-in flows that follow the joined list
  / community session so joins and folds re-subscribe.

Extract the relay-group last-read route into relayGroupChannelLastReadRoute so
the read side (row) and write side (RelayGroupChannelView) can't drift, mirroring
marmotGroupLastReadRoute / concordChannelLastReadRoute.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ZkjdHANMKyu2EbM7eEM8C
2026-07-17 23:33:18 +00:00
Vitor PamplonaandGitHub cd5060e5dc Merge pull request #3626 from vitorpamplona/claude/graperank-algorithm-improvements-oco5ue
Add follower crawl and trusted-follower metrics to GrapeRank
2026-07-17 19:29:49 -04:00
Vitor PamplonaandGitHub bdbf12f8be Merge pull request #3628 from vitorpamplona/claude/mediaservers-settings-ui-design-gbq9m7
Redesign media servers UI with drag-reorder, health probes, and cards
2026-07-17 19:28:01 -04:00
Claude 699c2dc7e5 fix(graperank): import kotlinx.coroutines.IO for Kotlin/Native compile
FollowerCrawler is in commonMain but used `Dispatchers.IO` without importing
the multiplatform `kotlinx.coroutines.IO` extension. On JVM `Dispatchers.IO`
resolves to the JVM member (compiled fine locally), but on Kotlin/Native that
member is internal, so `:quartz:compileKotlinIosSimulatorArm64` failed with
"Cannot access 'val IO': it is internal". Add the explicit import — the same
idiom the sibling GrapeRankCrawler already uses across all targets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xc3Wm4qVCrAGvSAotTUVt4
2026-07-17 23:26:46 +00:00
Claude d2e18cb18d fix(media-servers): trim the gap above the first section header
The first "Upload priority" header stacked FeedPadding's top inset, an
extra 8dp, and SectionLabel's 20dp inter-section top padding — ~38dp of
dead space under the top bar. Make SectionLabel's top padding a parameter
and shrink it for the first section.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GJ77Hm5L7fXEbPWbUds1iA
2026-07-17 23:26:34 +00:00
Claude a99fc59de8 feat(media-servers): auto-save on every change; polish cache card & add flow
Addresses review feedback on the redesign:

- Auto-save: drop the Save button and replace the cancel "X" with a
  standard back arrow (TopBarWithBackButton). Every add/remove persists
  immediately; a reorder persists once the drag gesture ends, so dragging
  no longer publishes a kind-10063 event on every intermediate swap.
- On-device cache card now uses the app's filled surfaceContainer card
  style (no outline), matching the main Settings screens.
- Drop the redundant intro sentence at the top of the screen.
- Rework the "Add a server" block: recommended servers come first as
  one-tap chips, followed by the "or paste a server address" field.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GJ77Hm5L7fXEbPWbUds1iA
2026-07-17 23:21:02 +00:00
Vitor PamplonaandGitHub 0ac31a7919 Merge pull request #3627 from vitorpamplona/claude/commons-crowdin-ci-acdrjg
chore: include commons Compose resources in Crowdin sync
2026-07-17 19:20:24 -04:00
Claude 7f99cbc3d6 fix(ci): include commons translations in the Crowdin PR
The Crowdin action now downloads the commons module's Compose-resource
translations too, but the create-pull-request step's add-paths still only
covered amethyst/ and docs/. New untracked commons files created by the
Crowdin action (run as root in its container) were therefore left out of
the commit and shelved by git stash --include-untracked, which failed with
'Permission denied' when trying to remove the root-owned files.

Add commons/src/commonMain/composeResources/**/strings.xml to add-paths so
those translations are committed into the PR instead of being stashed.
2026-07-17 23:14:36 +00:00
Vitor PamplonaandGitHub 451e27e38d Merge pull request #3625 from vitorpamplona/claude/event-renderers-commons-7pttym
refactor(commons): extract note-type UI rendering to commons
2026-07-17 19:04:41 -04:00