Commit Graph
17329 Commits
Author SHA1 Message Date
Claude 7b32ca1da3 feat(composer): add "pick on map" location to the short-note composer
The composer could only attach the current GPS location (fixed ~5km), which
fails when GPS is denied/unavailable and can't tag a post with a different
place. Add a "pick a place on the map" action that opens the shared
GeohashLocationPickerDialog and stores the result in
ShortNotePostViewModel.pickedGeoHash, which overrides the GPS fix at build time
and round-trips through drafts. Picking a place also skips the GPS permission
prompt. The existing "use my location" GPS flow is unchanged; this is additive.

Scoped to the short-note composer for now; the other ILocationGrabber composers
(long-form, classifieds, DMs, comments, channel/nest messages) can adopt the
same pickedGeoHash override + section in a follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YU8YLcjH9ALr4PgdAkGPZh
2026-07-18 22:57:55 +00:00
Claude 8658dcd512 refactor(geohash): reuse the shared picker in the Teleport screen
Extract the picker body into GeohashLocationPickerContent (chrome-less: map +
search + use-my-location + precision + readout + confirm), leaving
GeohashLocationPickerDialog as a thin dialog wrapper around it.

Rewrite GeohashTeleportScreen to host that shared content inside its own scaffold,
passing a teleport-specific confirm label and action (follow the cell + open the
chat with the teleport flag). This dedupes the map/precision/city-name code that
Teleport previously copied, and gives Teleport place search, "use my location",
dark-mode tiles, area-size chips, and the animated pin for free.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YU8YLcjH9ALr4PgdAkGPZh
2026-07-18 22:36:47 +00:00
Claude 11590b7081 feat(location): polish the geohash picker and generalize it
Refinements after the initial map picker:

- Dark mode: apply the night tile filter to the interactive LocationPickerMap
  too, so the picker matches the app theme instead of showing a bright map.
- Search results: the place search now shows a tappable list of candidates
  (with human place names) instead of silently jumping to the first hit.
- Precision chips now show approximate area sizes (e.g. "City · ~5 km").
- The center pin lifts with an animated shadow while the target moves.

Generalization: the picker's own strings are renamed from relay_group_location_*
to neutral location_picker_* keys so any caller can reuse
GeohashLocationPickerDialog, not just the NIP-29 group form. The group-form card
strings stay namespaced. LocationPreviewMap gains a configurable aspectRatio.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YU8YLcjH9ALr4PgdAkGPZh
2026-07-18 21:21:57 +00:00
Claude c28c8af13f feat(nip29): map-based location picker for group creation
The NIP-29 group create/edit form asked for the group's location as a raw
geohash text field (placeholder "u0nd") — unusable, since nobody knows their
geohash, so the discovery geo-filter it feeds stayed empty.

Replace it with a first-class, map-first location experience, reusing the
pieces that already power the Geohash-chat Teleport screen:

- New GeohashLocationPickerDialog: a full-screen picker where the user pans a
  map under a fixed center pin, searches for a place by name, or taps "use my
  location" (device GPS). Precision chips (GeohashChannelLevel) control the
  area size; the resolved place name is shown via LoadCityName. Never surfaces
  a raw geohash.
- New ForwardGeolocation service (Geocoder.getFromLocationName) powering search,
  mirroring the existing ReverseGeolocation.
- Extend LocationPickerMap with backward-compatible recenter/recenterZoom and
  onCenterChanged hooks for the center-pin interaction; extend LocationPreviewMap
  with a configurable aspectRatio for the form's map thumbnail.
- Rework the form's location field: an inviting empty-state card, a filled card
  with a themed map thumbnail + place name + geohash + clear, and a collapsed
  "enter manually" field so power users can still paste a known geohash.

The ViewModel's geohash state stays the single source of truth, so the publish
path (parseGeohashes -> kind-9002 EditMetadataEvent) is unchanged. Adds the
MyLocation glyph and regenerates the Material Symbols subset font.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YU8YLcjH9ALr4PgdAkGPZh
2026-07-18 21:07:44 +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
Claude 4d199ab28a fix(media-servers): probe /upload not root; polish rows into cards
Health probe was hitting the bare server root with HEAD. CDN-fronted hosts
like cdn.satellite.earth never answer `/` (verified: HEAD/GET `/` time out,
while HEAD `/upload` returns 404 in ~0.5s), so a fast, working server was
wrongly shown Offline. Probe the BUD-01 `/upload` endpoint instead — the
path that actually matters for an upload target.

Visual polish:
- Server rows are now outlined cards; the primary target (#1) gets an accent
  border, a faint accent tint, and a "Primary" badge.
- Rank and monogram merge into one avatar (rank as a small corner badge),
  removing the separate rank tile and decluttering the row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GJ77Hm5L7fXEbPWbUds1iA
2026-07-17 23:03:05 +00:00
Claude 33f4c648e9 fix(graperank): don't create the operator master for a read-only follower crawl
`amy graperank followers <observer>` (no --relay) assembles the relay universe
via allKnownRelays, which read the reachability cache through ctx.reachability —
and that lazily derives the monitor key from the operator master, forcing a
passphrase prompt (or failing on a fresh machine) purely to READ kind:30166
records. The follower crawl signs nothing, so this defeats its anonymous path.

Read the reachability snapshot with a throwaway signer instead (snapshot() only
reads; the signer is used solely for writes) — the same trick `graperank status`
already uses to stay side-effect-free.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xc3Wm4qVCrAGvSAotTUVt4
2026-07-17 22:57:25 +00:00
Claude 0feb0072a1 feat(media-servers): redesign whole screen as one canvas, drop tabs
Replaces the Servers/Local-cache segmented tabs with a single cohesive
scroll, and redesigns every zone of the screen:

- Upload priority: draggable, ranked server rows now carry a colored
  monogram (identity) and a "Primary" badge on #1, alongside the health dot.
- Add a server: the old inline URL field + a second 10-row recommended
  list are replaced by one inline add area — the URL field plus the
  recommended servers as a horizontal strip of add-chips that read as
  "done" once added (matched by host so URL normalization can't hide it).
- On-device cache: the old bare switch rows become a self-contained card
  with a storage icon, a detection-status dot, and the profile-pics-only
  sub-toggle nested under it.

Removes the segmented tabs and their now-dead strings. Same staged
Save/Cancel model, kind-10063 publish, drag utility, and health probe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GJ77Hm5L7fXEbPWbUds1iA
2026-07-17 22:53:13 +00:00