Commit Graph
18057 Commits
Author SHA1 Message Date
Claude 91bd2b44ee fix: re-probe the local Blossom cache when the toggle flips
Flipping either local-cache setting called probe.invalidate(), which only
clears the TTL timestamp — it never re-runs the probe. Nothing else does
either: isAvailable() is reached only from the one-shot startup warm and
from inside findServers().

That leaves the feature dormant when enabled mid-session. The feed image
path gates on `available.value` (AccountViewModel.useLocalBlossomBridge
and shouldBridgeBlossomCache), so while the probe reads false no request
ever routes through the resolver, and the resolver is the only thing that
would have refreshed the probe. The settings "detected" chip reads the
same flow, so it also stays stale.

Re-probe right after invalidating so enabling the toggle activates the
feature — and reports its true state — in the same session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PCgrM83QetNPJTg3Z5VWnF
2026-07-29 21:34:45 +00:00
Claude 202163f4b9 perf: keep Local Blossom resolver/probe off the main thread
BlossomServerResolver.findServers and LocalBlossomCacheProbe.probe are
suspend functions that don't confine to Dispatchers.IO. They're reached
from Compose produceState/LaunchedEffect (RichTextViewer,
MarmotGroupIconDisplay, ZoomableContentView), which run on the main
dispatcher, so the pre-suspension work — BlossomUri regex parsing,
LruCache lookups, the probe's OkHttpClient build on TTL expiry, and the
server-list flow setup — executed on the UI thread on every uncached
resolution during feed scroll.

Wrap findServersInner and the probe's client-build + HEAD in
withContext(Dispatchers.IO) so that work stays off the main thread.
Behaviour is unchanged; callers already on IO (Coil BlossomFetcher,
PlaybackService) are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PCgrM83QetNPJTg3Z5VWnF
2026-07-29 19:19:00 +00:00
Vitor PamplonaandGitHub 07267e254b Merge pull request #3813 from vitorpamplona/claude/notecompose-media-rendering-f4k7nh
Consolidate NIP-71 video event types under shared interface
2026-07-29 14:04:45 -04:00
Claude ee8623bd20 refactor: dispatch all NIP-71 video kinds via the VideoEvent interface in NoteCompose
RenderNoteRow matched the four concrete video subtypes (VideoNormalEvent 21,
VideoShortEvent 22, VideoHorizontalEvent 34235, VideoVerticalEvent 34236) with
four identical branches. Collapse them into a single `is VideoEvent` branch,
matching how ThreadFeedView's NoteMaster already dispatches, so any future
VideoEvent subtype renders through VideoDisplay automatically instead of
falling through to the text fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QCKP4tD2seoDpr3ZcdPJNx
2026-07-29 17:01:53 +00:00
David KasparandGitHub 7e6170390d Merge pull request #3808 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-07-29 18:52:04 +02:00
vitorpamplonaandgithub-actions[bot] 0279938e05 chore: sync Crowdin translations and seed translator npub placeholders 2026-07-29 16:41:58 +00:00
Vitor PamplonaandGitHub 979ce7a96f Merge pull request #3812 from vitorpamplona/claude/buzz-channel-delete-persist-e3lex3
NIP-29: Channel deletion, archival, and Buzz community UI
2026-07-29 12:38:42 -04:00
Vitor PamplonaandGitHub 7cda707380 Merge pull request #3811 from vitorpamplona/claude/draft-note-expansion-threadview-u2u32l
Make draft posts in thread view open edit screen on tap
2026-07-29 12:36:44 -04:00
Claude d44264a1ac feat(buzz): fix channel/forum type on the create screen from the caller
The create screen no longer shows a "Forum channel" toggle — the type is decided
by the community's per-section "+" (Channels vs Forums) and a Buzz channel's
channel_type isn't editable anyway (the relay's 9002 has no such key). The screen
loads with the fixed isForum parameter and creates the right type; its top-bar
title reads "New forum" when isForum, "New channel" otherwise (on Buzz).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016MNVEKhaAu4vQRZnXv3rfG
2026-07-29 16:22:34 +00:00
Claude 04243d8dfa fix: open edit-draft screen when tapping a draft in ThreadView
Tapping a note in the thread view toggles the collapse/expand state of
its children. For the user's own drafts, tap now opens the edit-draft
screen (via routeEditDraftTo) so the post can be resumed, matching the
behavior everywhere else a draft is tapped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQCNuxdYkfouNN3ujxm3mR
2026-07-29 16:19:17 +00:00
Claude ca61eb107d fix(buzz): drop deleted channels that leaked as bare UUID rows
A channel deleted elsewhere (or before this device saw its metadata) keeps its
kind-44100 membership — the relay never retracts it — but the relay soft-deletes
its 39000, and it does NOT serve a channel_deleted 40099 for an already-deleted
channel, so the discovery fetch can't catch it. Such a channel arrived with no
metadata and was shown optimistically: a bare UUID row with "No messages yet".

The reliable signal is that very metadata absence. BuzzRelayImportViewModel.discover
now drops channels the relay serves no 39000 for — but only when the fetch clearly
worked (some channel returned a 39000); if none did, the read failed (auth/
connectivity) and the whole set is kept rather than blanking the community. A
genuinely new channel whose 39000 is merely slow returns on the next bind once its
metadata is cached, so the prune is live (not persisted) and self-correcting.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016MNVEKhaAu4vQRZnXv3rfG
2026-07-29 16:09:11 +00:00
Claude 9d2c5e0cee fix(buzz): show the channel/forum "+" on any community, not gated on an unloaded roster
The "+" was gated on BuzzCommunityMembership (kind-13534), but that roster is only
fetched by the Members screen — nothing on the channel-list screen subscribes to
it, so isMember read false and the "+" hid even from admins. Match the workspace
overflow menu's approach instead: offer create on any Buzz community and let the
relay reject a non-member's kind-9007 (its own doc: "any member sees them, the
relay only serves the owner/admin ones").

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016MNVEKhaAu4vQRZnXv3rfG
2026-07-29 16:07:07 +00:00
Vitor PamplonaandGitHub 2496a7fb3d Merge pull request #3809 from vitorpamplona/claude/flaky-quic-stream-drain-oge3ze
Fix flaky PeerUniStreamDrainTest by confining drainer to producer's event loop
2026-07-29 11:09:18 -04:00
Claude f723310dc7 feat(buzz): archive/unarchive channels + fix visibility edits on Buzz
Two channel-management gaps vs the Buzz interface (both ride kind-9002 tags; no
new protocol), verified against block/buzz:

Archive/Unarchive — the reversible hide-from-the-sidebar the Buzz client has,
distinct from delete. EditMetadataEvent gains an `archived` tag; Account/
AccountViewModel expose archiveRelayGroup; the channel and forum top bars offer
Archive/Unarchive to admins (no confirm — it's reversible). The relay stamps
`["archived","true"]` on the 39000, so GroupMetadataEvent.isArchived() /
RelayGroupChannel.isArchived() read it directly; the community list drops archived
channels out of Channels/Forums into a collapsed "Archived" tail from which they
can be reopened and unarchived.

Visibility-on-edit — a Buzz relay reads its own `visibility` (open/private) tag,
not the NIP-29 `private` status flag, so the edit screen's private toggle was a
silent no-op on Buzz. editRelayGroupMetadata now sends the `visibility` tag on
Buzz relays (status flag still sent for vanilla NIP-29).

Not gaps (checked): topic/purpose/TTL are in the relay's system-message
vocabulary but not extracted on 9002/9007, so there's nothing to mirror.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016MNVEKhaAu4vQRZnXv3rfG
2026-07-29 15:05:59 +00:00
Vitor PamplonaandGitHub 662bd87495 Merge pull request #3807 from vitorpamplona/claude/swipe-reply-sensitivity-y0y0v1
Refactor chat bubble swipe gesture to use lower-level pointer APIs
2026-07-29 11:05:32 -04:00
Claude 21c44cfc36 test(quic): de-flake PeerUniStreamDrainTest slow-consumer overflow
drainPeerInitiatedUniStreamsIntoBlackHole_keeps_connection_alive passed
in isolation but flaked under parallel load with the audit-4 #3
"slow consumer overflowed incoming channel" teardown.

Root cause: the test ran the black-hole drainer on the shared
Dispatchers.Default pool. The drainer only has to keep the 64-deep
per-stream incomingChannel drained, but when the rest of the JVM test
suite saturates every Default worker thread the drainer coroutine can be
denied a scheduling slot long enough for the synchronous feed loop to
push more than 64 chunks ahead of it, tripping the slow-consumer
teardown. The fixed-delay yields (delay(1) every 16 chunks) do not help
when all pool threads are pinned.

Fix: run the drainer on the runBlocking coroutine's own single-threaded
event loop (CoroutineScope(coroutineContext + SupervisorJob())) and pace
the feed loop with cooperative yield() instead of wall-clock delays. Each
yield deterministically runs the drainer's queued channel-receive
continuation before the producer resumes, so the buffer never approaches
its bound regardless of machine load. The drain contract under test
(parser trySend -> incomingChannel -> collect -> discard) is exercised
identically; only the scheduling is pinned.

Verified with a temporary reproduction that pins every Default worker
thread: the old Default-scheduled drainer overflows to CLOSED while the
confined drainer stays CONNECTED under the same saturation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MGZt6D298kGv2XgAi3Uuaj
2026-07-29 15:02:16 +00:00
David KasparandGitHub cb648011d3 Merge pull request #3806 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-07-29 16:59:01 +02:00
Claude 0284c8c86a fix: keep swipe-to-reply from stranding the chat bubble off-screen
Two ways the bubble could get stuck shifted sideways (dragOffset frozen at a
non-zero value, reply icon left visible), both fixed:

1. Cancelling the settle-back animation on awaitFirstDown — which fires for
   every touch, including one that turns out to be a vertical scroll — while
   settleBack() only ran on the horizontal-drag path. A scroll started during
   an in-flight settle froze dragOffset. Now the settle is cancelled only once
   a horizontal drag is actually committed, so every cancel is paired with a
   settleBack().

2. Keying pointerInput on the onSwipeReply lambda. The caller allocates a fresh
   lambda every recomposition (it captures the note), so the detector was torn
   down and restarted on every row update. A restart mid-drag cancelled
   horizontalDrag before settleBack() ran, stranding the bubble; idle restarts
   also churned the gesture coroutine across all visible bubbles. Now keyed on
   the stable isLoggedInUser, with the callback read through rememberUpdatedState
   and the drag wrapped in try/finally so an interrupted drag always settles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016f7dTu8RoJRXjnHM7FKAE2
2026-07-29 14:58:08 +00:00
davotoulaandgithub-actions[bot] d504e43924 chore: sync Crowdin translations and seed translator npub placeholders 2026-07-29 14:58:05 +00:00
davotoula f353389892 update cs,sv,de,pt 2026-07-29 16:54:25 +02:00
Claude 97ff971542 feat(buzz): move per-row channel actions into the opened screen's top bar
The community list rows (channels, forums, DMs) each carried a 3-dot overflow —
Pin/Unpin and Add/Remove-from-Messages. Those are gone; each row is now a clean
tap-to-open target and its actions live in the top-bar overflow of the screen it
opens:

- Channels & DMs open RelayGroupChatScreen (RelayGroupTopBar): add Pin/Unpin
  (Buzz, non-DM) alongside the existing Messages toggle. A DM has no kind-10009
  entry, so its Messages toggle drives the DM-specific hide/unhide (kind-41012 /
  re-open) read from the per-viewer 30622 snapshot; the DM overflow is always
  shown so that action stays reachable.
- Forums open RelayGroupThreadsScreen: give its top bar an overflow with Pin/Unpin
  and Add/Remove-from-Messages.

Shared BuzzPinDropdownItem / RelayGroupMessagesDropdownItem keep the two bars
consistent. AccountViewModel gains hideBuzzDm/unhideBuzzDm wrappers. The
community-list top-bar overflow (Add people, Invite, Agent Console, Add all) is
unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016MNVEKhaAu4vQRZnXv3rfG
2026-07-29 14:36:42 +00:00
Claude ce25273898 perf: animate swipe-to-reply icon in the layer phase, not composition
The reply icon's fade/scale read dragOffset in composition, and because Box is
an inline composable that read invalidated the whole ChatBubbleLayout on every
drag and settle frame. Gate the icon on a derivedStateOf boolean that flips only
at the 0<->non-0 edges (icon added/removed twice per gesture) and move the
progress math into the icon's graphicsLayer block so it's read in the layer
phase. The bubble translation was already deferred; now the entire swipe and
settle animate with zero recomposition.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016f7dTu8RoJRXjnHM7FKAE2
2026-07-29 14:33:47 +00:00
Vitor PamplonaandGitHub 3cfa2e0efb Merge pull request #3805 from vitorpamplona/chore/bump-winget-manifest-v1.13.1
chore: sync winget manifests to v1.13.1
2026-07-29 10:33:20 -04:00
vitorpamplonaandgithub-actions[bot] bf73e0934a chore: sync winget manifests to v1.13.1 2026-07-29 14:31:13 +00:00
Vitor Pamplona 8a183d1cbd refactor(ci): move the Winget bump out of CI onto a maintainer machine
Mirrors the Homebrew cask change. The old design stored a classic `public_repo`
PAT as WINGET_TOKEN and handed it to the third-party
vedantmgoyal9/winget-releaser action: a token with write access to every public
repo the owning account can reach, given to code we do not control, in a place
any push-access collaborator could read it from (a pushed branch containing a
workflow runs with repo secrets).

- CI (bump-winget.yml, now "Sync Winget Manifest Reference") uses GITHUB_TOKEN
  only: downloads the MSI, computes the sha256, reads the ProductCode from the
  MSI Property table via msitools, and opens an in-repo PR syncing
  desktopApp/packaging/winget/. Runs on ubuntu (1x billing) rather than Windows
  since msitools reads the Property table fine.
- scripts/bump-winget.sh does the upstream PR. It needs NO new token: it drives
  `gh`, which a maintainer already has authenticated, and it does not need
  wingetcreate (Windows-only) because the manifests are plain YAML — so it runs
  from macOS or Linux.

Add the three reference manifests under desktopApp/packaging/winget/, matching
the schema 1.12.0 shape used upstream. All three validate against Microsoft's
published JSON schemas.

Validation caught one real bug worth noting: an all-digit 64-char InstallerSha256
parses as a YAML *integer* and fails the schema's `string` type, so it is written
quoted. ProductCode is re-read every release because jpackage regenerates it per
build; it is the ARP key `winget upgrade` matches on.

Drops the last package-manager PAT from the secret inventory.
2026-07-29 10:30:21 -04:00
Claude 34fa103dbd feat(buzz): gate the channel/forum "+" to community members
Only a Buzz community member may create a channel (the relay makes the creator
its owner; a non-member's kind-9007 is rejected), so the section-label "+" now
shows only when the viewer is in the community's NIP-43 roster. Reactive: the
relay-signed roster snapshot (kind 13534, republished on every membership change)
can arrive after first composition, so BuzzCommunityMembership is re-read whenever
one lands. The section labels themselves still render for everyone; only the "+"
is gated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016MNVEKhaAu4vQRZnXv3rfG
2026-07-29 14:26:23 +00:00
Vitor Pamplona db529444c4 refactor(ci): move the Homebrew cask bump out of CI onto a maintainer machine
`brew bump-cask-pr` forks homebrew-cask into the token owner's account, which
requires a CLASSIC PAT with the `repo` scope. That scope cannot be narrowed: it
grants write to every repository the owning account can reach, including this
one. As an Actions secret it would be usable by anyone with push access here,
because a pushed branch containing a workflow runs with repo secrets — a strict
escalation for a channel that ships one DMG a month.

Split the work so the token never becomes a CI secret:

- CI (bump-homebrew.yml, now "Sync Homebrew Cask Reference") does the
  error-prone bookkeeping with GITHUB_TOKEN only: downloads the DMG, asserts it
  is notarized + stapled, computes the sha256, and opens an in-repo PR syncing
  desktopApp/packaging/homebrew/amethyst-nostr.rb. This makes it a third sibling
  of the amy/geode formula-sync workflows rather than a special case.
- scripts/bump-homebrew-cask.sh does the upstream PR from a maintainer's shell,
  re-verifying sha256 and the notarization ticket against the live asset first,
  and refusing to submit if either fails. Verified against v1.13.1: the sha256
  matches and it correctly refuses on the missing notarization ticket.

Drop HOMEBREW_TOKEN from the secret inventory, and rewrite the two formula
TODO(bootstrap) notes that proposed reintroducing it so a future homebrew-core
submission follows the same local pattern.
2026-07-29 10:19:57 -04:00
Claude a89aaed9fa feat(buzz): create channels/forums from section-label "+" instead of a FAB
On a Buzz community the FAB (which created a channel) is replaced by a "+" on the
Channels label — matching how Direct Messages already offers a "+" — and a Forums
label gains its own "+" that starts the create flow pre-set to a forum channel.
Both section headers now always render so the "+" is reachable even before any
channel loads; the collapse chevron is shown only when the section is non-empty.

The create route gains an isForum flag (threaded onto RelayGroupCreateScreen →
RelayGroupMetadataViewModel.isForum) so the Forums "+" opens the create screen on
a forum, the Channels "+" on a chat channel. The vanilla NIP-29 relay (a flat
directory with no sections) keeps its FAB. The stale "accept the invite in the
browser" empty text is dropped — an empty community is now a starting point.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016MNVEKhaAu4vQRZnXv3rfG
2026-07-29 14:10:11 +00:00
Vitor Pamplona 2e47e9cd56 docs: correct the HOMEBREW_TOKEN type and scope
BUILDING.md called for a fine-grained PAT scoped to Homebrew/homebrew-cask.
That token cannot exist: a fine-grained PAT's repository selector only lists
repos owned by the resource owner, so a repo you do not own can never be
selected, and Homebrew's API layer authorises against classic OAuth scopes
(x-oauth-scopes) which fine-grained tokens do not emit.

brew bump-cask-pr forks homebrew-cask into the TOKEN OWNER's account, pushes a
branch there, and opens the PR upstream. Homebrew declares the requirement as
CREATE_ISSUE_FORK_OR_PR_SCOPES = ["repo"] in utils/github.rb, so it is a classic
PAT with the `repo` scope.

Add the create-token link and call out that `repo` grants write to every repo
the account can reach -- including this one -- so the CI secret should belong to
a dedicated bot account whose only asset is the fork.
2026-07-29 10:04:25 -04:00
Vitor PamplonaandGitHub 09f2ec79e7 Merge pull request #3798 from davotoula/fix/code-review-followups
Address buzz CLI review findings; pin the wrapper copies and the amy arg surface
2026-07-29 10:03:11 -04:00
Vitor Pamplona 8a8f58e7ad fix(release): actually notarize the macOS DMG, and verify it
The macOS leg ran only `packageReleaseDmg`, which SIGNS the DMG but does not
notarize it — notarization is a separate Compose task. The `notarization {}`
block in desktopApp/build.gradle.kts only supplies credentials; nothing invoked
it. So every release up to v1.13.1 shipped a signed but unnotarized DMG:
`xcrun stapler validate` reports no ticket on either the .app or the .dmg, and
Gatekeeper blocks it on first launch.

Append `:desktopApp:notarizeReleaseDmg` on the macOS leg. Compose 1.11.1's
AbstractNotarizationTask runs `notarytool submit --wait` and then `stapler
staple` in place, so the asset-collection step still finds the same file. Raise
that leg's inner timeout to 45m since the submit blocks on Apple.

Gate it on the cert AND all three notary secrets, so forks and credential-less
runs keep producing a plain unsigned DMG instead of failing.

Add a verify step that asserts the stapled ticket. The bug survived many
releases precisely because nothing ever checked the outcome.

Also update the reference cask to 1.13.1 and make it pass `brew audit --new
--cask` + `brew style` cleanly: add the missing conflicts_with (the tiling
window manager's cask installs the same Amethyst.app), add depends_on :macos,
fix stanza order, drop the unnecessary `verified:` (url and homepage share a
domain), and widen the zap to the state dirs the source actually uses.

Correct BUILDING.md's macOS state table, which listed a
com.vitorpamplona.amethyst.desktop.plist that does not exist and omitted
~/.amethyst. Note that Java's Preferences API writes to a SHARED
com.apple.java.util.prefs.plist, which is why the cask must not zap it.
2026-07-29 09:59:41 -04:00
Vitor PamplonaandGitHub 37b3d650e0 Merge pull request #3803 from vitorpamplona/claude/feed-kind-toggles-home-4cjyvp
Add Pictures, Videos, Shorts, and Torrents feed types
2026-07-29 09:52:59 -04:00
Vitor PamplonaandGitHub b0c0f12591 Merge pull request #3801 from vitorpamplona/chore/bump-amy-formula-v1.13.1
chore: sync amy Homebrew formula to v1.13.1
2026-07-29 09:45:01 -04:00
Vitor PamplonaandGitHub a5d59a69c3 Merge pull request #3802 from vitorpamplona/chore/bump-geode-formula-v1.13.1
chore: sync geode Homebrew formula to v1.13.1
2026-07-29 09:44:51 -04:00
davotoulaandClaude Opus 5 592fd8f542 test: pin the amy argument surface the buzz-agent path depends on
Flag names and error strings are the contract between amy and the wrapper
scripts (and their operators), but nothing type-checks them: renaming --base-ref
or rewording an error breaks callers while compiling clean. This harness pins the
ones the buzz-agent path drives.

14 cases, all offline against an isolated ~/.amy via the `amy.home` seam the JVM
tests use: the shared `repo-naddr-or-coordinates` positional on git
browse/cat/log, `pass --channel GID` on the workflow verbs, --base-ref accepted
while --base-reff is rejected on both agent up and agent serve, and the
no_relays detail.

Confirmed to discriminate: 14/14 on this branch, 10/14 when built against
main-upstream's BuzzCommands, with the four failures printing the old misleading
"no relays: pass --relays ws://…" for input the user did pass.

Worth recording from writing it: a bare word like `garbage` is *accepted* as a
relay (the normalizer prepends wss://), so the realistic trigger for the empty
set is a pasted http:// url — which is what the cases use.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WopdqNoZ9tYoMNppJG17BL
2026-07-29 15:42:48 +02:00
Vitor Pamplona a302b82413 fix(ci): replace the unresolvable homebrew-bump-cask action with brew itself
bump-homebrew.yml pinned macauley/action-homebrew-bump-cask to
ad984534de44a9489a53aefd81eb77f87c70dc60, commented "v4.0.0". That SHA does not
exist — the action's only release is v1 (445c4239) from 2021. GitHub resolves
every `uses:` during "Set up job", so the job died before running a single step,
which no `if:` gate can avoid. It stayed invisible because the workflow had
never been triggered; fixing the trigger surfaced it immediately.

Call `brew bump-cask-pr` directly instead. It is the same command BUILDING.md
already documents for manual recovery, and it removes an unmaintained
third-party action that would hold a PAT with write access to homebrew-cask.

Split the workflow into check (ubuntu) + bump (macOS): cask commands are
macOS-only, but the bump is gated on a cask that does not exist yet, so the
10x-billed macOS job is only created once there is something to bump.
2026-07-29 09:42:20 -04:00
davotoula 42bf2a43d8 fix: address code-review findings on the buzz CLI + wrapper scripts
Three findings from the review of the Sonar literal-extraction pass. None were
introduced by that pass — it renamed or sat next to each one.

no_relays told the wrong story. `relaysFor` returns an empty *non-null* set when
--relays was given but every entry failed `normalizeGroupRelay`, so the elvis to
`outboxRelays()` never fires and the user was told to pass the flag they had just
passed. The message now names the real problem and echoes the rejected value; the
`no_relays` error code is unchanged.

`participants` was string-munged. `arr.toString().trim('[',']').split(",")` turns
any shape other than a flat string array into plausible-looking fake pubkeys, and
splits a comma-bearing string into two entries. Now decoded as a JsonArray, with
non-string elements skipped rather than stringified. Extracted to
`participantsOf` so it is testable; BuzzParticipantsTest covers the shapes and
was confirmed to fail (3 of 5 cases) against the old implementation.

Nothing kept the two copies of the buzz-agent wrappers in sync. They exist as
byte-identical trees under cli/src/main/resources/buzz-agent (what `agent up`
extracts) and tools/buzz-agent (what the README tells people to use), with no
build step or test pinning them — I had to hand-apply the same patch twice this
week. BuzzAgentWrapperSyncTest now asserts it. The tools/ tree is declared as a
`:cli:test` input, without which Gradle calls the task up-to-date after a
tools/-only edit and misses exactly the drift being guarded (verified both ways).
2026-07-29 15:42:20 +02:00
Claude 3ab87dd48e fix: don't hijack vertical scroll with swipe-to-reply in chats
The chat bubble used detectHorizontalDragGestures, which claims the pointer
as soon as horizontal movement crosses touch-slop regardless of how much
vertical movement is happening. A mostly-vertical scroll with a little
diagonal drift got grabbed as a swipe, so the bubble slid sideways while the
user was just trying to scroll the conversation.

Replace it with a custom awaitEachGesture detector that only consumes the
pointer once the motion is clearly more horizontal than vertical
(abs(over.x) > abs(over.y)). A predominantly-vertical drag leaves the pointer
unconsumed, so the enclosing scroll keeps it and the bubble stays put. Once
committed to a horizontal drag, behavior (clamp, haptic tick at threshold,
release-past-threshold to reply, settle-back animation) is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016f7dTu8RoJRXjnHM7FKAE2
2026-07-29 13:39:24 +00:00
vitorpamplonaandgithub-actions[bot] 54b5ff8876 chore: sync geode Homebrew formula to v1.13.1 2026-07-29 13:39:22 +00:00
vitorpamplonaandgithub-actions[bot] 5a665950b8 chore: sync amy Homebrew formula to v1.13.1 2026-07-29 13:38:36 +00:00
Vitor Pamplona 054dffd55d fix(ci): make the Homebrew/Winget bump workflows actually fire
All four bump workflows triggered on `release: types: [released]`, which never
fires here: create-release.yml publishes the release with GITHUB_TOKEN, and
GitHub suppresses workflow-triggering events for GITHUB_TOKEN actions. They had
zero runs across every release up to v1.13.1.

Switch them to `workflow_run` on "Create Release Assets" completion, filtered to
a successful tag push. That also removes a latent race: `released` fired while
the matrix legs were still uploading assets, whereas workflow_run fires after
all of them finish.

The workflow_run payload carries no draft/prerelease flags, so add a
resolve-release composite action that reads them back from the API and feeds
assert-stable-release, keeping the defense-in-depth guard intact instead of
inferring stability from the tag string alone.

Also gate the cask/winget bumps on the package existing upstream. Neither
`amethyst-nostr` nor `VitorPamplona.Amethyst` has been bootstrapped, and
bump-cask-pr/winget-releaser can only update an existing package — without the
gate, fixing the trigger would file a spurious [release-ops] issue on every
release.

Docs: correct the claims this uncovered — Homebrew/Winget are not shipping,
macOS is arm64-only (no Intel DMG), the release carries 31 assets (13 Android,
not 12), Maven Central publishes from a step inside deploy-android and lags
repo1 by tens of minutes, RELEASE_NOTES_ID is minor-releases-only, and note the
git-credential-manager hang that blocks the release push.
2026-07-29 09:37:24 -04:00
Claude 9a5b36d0ba fix(buzz): honor the relay's channel_deleted signal so deleted channels leave the list
The first pass only recorded a deletion when THIS device published the 9008, so a
channel deleted on another Buzz client — or before the fix existed — kept showing
and survived restarts. The Buzz relay's handle_delete_group soft-deletes the
channel and its 39000/39001/39002 discovery events but emits NO member-removed
notification and never retracts the kind-44100 that seeds the browse list; its
only cross-device signal is the relay-signed kind-40099 system message with
type=channel_deleted (verified against block/buzz side_effects.rs).

Consume that signal: LocalCache records a relay-signed 40099 channel_deleted into
RelayGroupDeletions (gated on isRelaySignedGroupEvent so a spoofed one can't hide a
channel), and BuzzRelayImportViewModel.discover now also fetches the #h-scoped
40099 for its candidate channels and drops deleted ones — the relay keeps
re-announcing their 44100 with blank metadata, so this is the only place they
leave the list. Cross-device and retroactive: the relay replays the 40099 on
subscribe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016MNVEKhaAu4vQRZnXv3rfG
2026-07-29 13:25:37 +00:00
Claude b666a189ac feat(home): split Videos into long-form Videos and Shorts toggles
Separate the Home feed video toggle into two independent groups,
matching how the app already classifies them (see ShortsFeedFilter):

- Videos (long-form): normal (21) + horizontal (34235)
- Shorts: short (22) + vertical (34236)

Each is its own Settings › Home tile with distinct icon/string. The
relay assembler filters and New Threads DAL already list all four
kinds, so only the HomeFeedType grouping changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0198T5VTjA1x2dCfBLhXZaiL
2026-07-29 13:22:12 +00:00
Vitor PamplonaandGitHub 7d26c06fe9 Merge pull request #3799 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-07-29 09:10:52 -04:00
Vitor PamplonaandGitHub 0ae90dbdc6 Merge pull request #3797 from davotoula/fix/sonar-case-default-clause
Harden workflow-ship.sh and pin it with a regression harness
2026-07-29 09:09:03 -04:00
vitorpamplonaandgithub-actions[bot] 8d877ea70d chore: sync Crowdin translations and seed translator npub placeholders 2026-07-29 12:33:41 +00:00
Vitor PamplonaandGitHub 7ba5631ec3 Merge pull request #3796 from davotoula/fix/hls-strip-ll-tags
Work around a fatal media3 crash on Low-Latency HLS (LL-HLS), eg NoGoodRadio
2026-07-29 08:30:39 -04:00
Vitor PamplonaandGitHub 1133560dd6 Merge pull request #3795 from nrobi144/feat/desktop-profile-parity
Desktop: profile screen parity — tabs, header actions, banner/bio, zaps
2026-07-29 08:30:02 -04:00
David KasparandGitHub 842a01ea2f Merge pull request #3794 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-07-29 14:28:47 +02:00
davotoula fcbd9d727b test: add a regression harness for workflow-ship.sh 2026-07-29 14:14:39 +02:00