Commit Graph

11800 Commits

Author SHA1 Message Date
Vitor Pamplona ae1549b884 Merge pull request #2488 from vitorpamplona/claude/debug-marmot-whitenoise-oO26C
Add CLI interface (amy) for Marmot/MLS group operations
2026-04-21 18:47:56 -04:00
Claude 569a785963 Merge remote-tracking branch 'origin/main' into claude/configurable-bottom-nav-rFdQw 2026-04-21 22:46:38 +00:00
Vitor Pamplona 4f82b68d06 Merge pull request #2487 from vitorpamplona/claude/add-public-chats-screen-LSgH4
Add Public Chats screen with feed filtering and relay subscriptions
2026-04-21 18:42:44 -04:00
Claude 25781a8815 fix(quartz): send JVM PlatformLog output to stderr
On the JVM, Log.d / .i / .w / .e were printing through \`println()\` which
lands on stdout. The amy CLI uses stdout as its JSON contract — any
Marmot/MLS command would restore state, emit a burst of DEBUG lines,
then emit the JSON object, and every caller piping through jq failed
to parse because the first non-empty line was a log line.

Switch to \`System.err.println\` inside PlatformLog.jvm.kt. Conventional
for CLIs (data on stdout, diagnostics on stderr), and the desktop app
already redirects as part of packaging. Callers who want everything
mixed can still \`2>&1\` at the shell level.

Fixes amy marmot group create / group add / message send etc. when
their output is captured by a shell script.

https://claude.ai/code/session_01M6dCKAF5Y1VyHGZPjzwDXq
2026-04-21 22:38:38 +00:00
Claude 11f14f7497 Merge remote-tracking branch 'origin/main' into claude/add-public-chats-screen-LSgH4
# Conflicts:
#	amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt
#	amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt
#	amethyst/src/main/java/com/vitorpamplona/amethyst/model/AccountSettings.kt
#	amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/RelaySubscriptionsCoordinator.kt
#	amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/RememberForeverStates.kt
#	amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt
#	amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerContent.kt
#	amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt
#	amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountFeedContentStates.kt
#	amethyst/src/main/res/values/strings.xml
2026-04-21 22:35:13 +00:00
Claude 020abccf57 fix(cli): mark Identity.hasPrivateKey @JsonIgnore
Jackson was writing the computed \`hasPrivateKey\` into identity.json
and then refusing to read it back ("Unrecognized field hasPrivateKey")
on the next invocation. All amy commands that reload the data-dir
(\`marmot group create\`, \`marmot key-package publish\`, …) exited 1 at
the Context.open step. Pure boolean getter, no persistence needed.

https://claude.ai/code/session_01M6dCKAF5Y1VyHGZPjzwDXq
2026-04-21 22:30:08 +00:00
Claude 760e682b89 fix(marmot-interop): parse post-v0.2 \wn --json whoami\ shape
wn now wraps account listings in {"result": [{...}]} so extract_pubkey's
object + array fallbacks both missed it and ensure_identity bailed with
"could not determine \$who npub" immediately after create-identity.

Also: create-identity on a fresh box can exit non-zero with
"failed to connect to any relays" even when the account *was* created
locally. Probe --json whoami afterwards before calling that a fatal.

https://claude.ai/code/session_01M6dCKAF5Y1VyHGZPjzwDXq
2026-04-21 22:27:20 +00:00
Vitor Pamplona c9127111d2 Merge pull request #2486 from vitorpamplona/claude/add-livestreams-screen-gr5Ak
Add Live Streams feed screen with filtering and discovery
2026-04-21 18:26:19 -04:00
Claude 376e404c96 Make bottom navigation bar configurable
Users can now pick any subset of drawer items, in any order, to appear
in the bottom bar. If zero items are selected the bottom bar is hidden.

- NavBarItem enum + NavBarCatalog: every drawer destination gets an id,
  label, icon, and route resolver so items can be looked up by id.
- UiSettings gains bottomBarItems: List<NavBarItem>; persisted in the
  existing UiSharedPreferences DataStore as a comma-separated enum list.
- AppBottomBar reads the list from UiSettingsFlow and renders a zero-
  height spacer (preserving nav-bar insets) when the list is empty.
- New Navigate section at the top of the drawer exposes Home, Messages,
  Media, Discovery, Notifications as drawer destinations.
- New BottomBarSettingsScreen under All Settings lets users pin/unpin
  entries and drag to reorder (same UX as ReactionsSettingsScreen).
2026-04-21 22:23:15 +00:00
Claude 37515ec975 feat(marmot-interop): let wnd run in sandboxes without kernel keyring
Containers / CI often block the keyutils syscalls wnd uses by default
to store secret keys (add_key returns EACCES, keyctl_search returns
ENOSYS). The integration-tests feature ships a mock keyring store, but
the stock wnd binary doesn't wire it up. Add a tiny opt-in patch.

Changes:
- headless/patches/whitenoise-mock-keyring.patch: if built with the
  integration-tests feature and \$WHITENOISE_MOCK_KEYRING is set, wnd
  calls Whitenoise::initialize_mock_keyring_store() before the normal
  init path. Harmless on production builds.
- headless/setup.sh: apply both harness patches generically from a
  list; build wn/wnd with --features cli,integration-tests; export
  WHITENOISE_MOCK_KEYRING=1 alongside WHITENOISE_DISCOVERY_RELAYS when
  launching each daemon.
- preflight swaps keyctl for patch in required tools — we no longer
  need a real session keyring.

https://claude.ai/code/session_01M6dCKAF5Y1VyHGZPjzwDXq
2026-04-21 22:20:54 +00:00
Claude 3de0814e4f Add Live Streams screen accessible from the left drawer
Introduces a dedicated "Live Streams" screen that mirrors the Shorts
architecture (FeedContentState + FilterAssembler + SubAssembler) and
reuses the Discovery/LiveStreams rendering (ChannelCardCompose) and
relay filter builders (makeLiveActivitiesFilter) for the same top-nav
filters available on Shorts: Following, Global, Hashtag, Location,
Authors, Community, etc.

Refactors LocalPreferences.innerLoadCurrentAccountFromEncryptedStorage
to extract follow-list pref reads into a small helper so the method
stays under the JVM 65KB method size limit after adding the new
defaultLiveStreamsFollowList setting.
2026-04-21 22:19:16 +00:00
Claude 6213fb2197 feat(marmot-interop): patch wnd to honour \$WHITENOISE_DISCOVERY_RELAYS
Without this, wnd exits on startup with NoRelayConnections in any
environment that can't reach wnd's baked-in public discovery relay
set (nos.lol, relay.damus.io, …). The headless harness runs entirely
on a loopback relay, so hitting the public internet is never OK.

Changes:
- headless/patches/whitenoise-discovery-env.patch: adds an env-var
  override at the top of DiscoveryPlaneConfig::curated_default_relays.
  When \$WHITENOISE_DISCOVERY_RELAYS is set (comma-separated) we use
  that list instead of the hardcoded public one.
- headless/setup.sh: applies the patch in preflight (idempotent —
  checks for a .headless-discovery-patched marker), invalidates the
  previous wn/wnd build on first patch, rebuilds, and threads
  \$WHITENOISE_DISCOVERY_RELAYS=\$RELAY_URL into every start_daemon
  invocation.

https://claude.ai/code/session_01M6dCKAF5Y1VyHGZPjzwDXq
2026-04-21 22:14:16 +00:00
Claude 871a01c4aa feat: add standalone Public Chats screen
Introduces a dedicated Public Chats screen accessible from the left drawer,
mirroring the architecture of the Shorts screen. It reuses the existing
Discover public-chats renderer (ChannelCardCompose + RenderPublicChatChannelThumb)
and its relay-filter helpers (makePublicChatsFilter), but with its own feed
content state, filter assembler/sub-assembler, and top-nav follow-list filter
setting so the selection is independent from Discover.

- New Route.PublicChats, drawer entry, screen/top-bar/feed composables
- PublicChatsFeedFilter scans LocalCache.publicChatChannels
- defaultPublicChatsFollowList persisted in AccountSettings / LocalPreferences
- Live per-relay follow list flow on Account for relay subscription
2026-04-21 22:13:51 +00:00
Vitor Pamplona 78b7fd228f Merge pull request #2484 from vitorpamplona/claude/reduce-topic-chips-emphasis-gI6Jr
De-emphasize topic chips in LongFormHeader
2026-04-21 18:13:21 -04:00
Vitor Pamplona 7cc6f05daa Merge pull request #2485 from vitorpamplona/claude/add-follow-packs-screen-pRAmv
Add Follow Packs discovery feed screen
2026-04-21 18:12:54 -04:00
Claude 6c137636e5 Add Follow Packs drawer screen mirroring Shorts architecture
Introduces a standalone Follow Packs screen (Route.FollowPacks)
accessible from the drawer, rendering FollowListEvent (kind 39089)
entries from LocalCache with the same top-nav filter as Shorts and
the Discovery tab. Reuses the existing defaultDiscoveryFollowList
setting and DiscoverFollowSetsFeedFilter logic so the two surfaces
stay in sync. Item rendering goes through ChannelCardCompose pinned
to FollowListEvent.KIND, matching the Discovery follow-packs tab.
2026-04-21 22:07:21 +00:00
Claude bed3514700 chore(marmot-interop): check for protoc in preflight
nostr-rs-relay's build.rs needs protoc to compile the nauthz gRPC
definitions. Without it the build fails deep in prost-build with a
panic that's hard to read, so fail up front with a pointer to
`apt-get install protobuf-compiler`.

https://claude.ai/code/session_01M6dCKAF5Y1VyHGZPjzwDXq
2026-04-21 22:06:59 +00:00
Claude 51ad472046 feat(marmot-interop): run headless suite against a local nostr-rs-relay
Every test now flows through one loopback nostr-rs-relay on
ws://127.0.0.1:8080 — no public relays, no egress. Addresses the
repeated 503s we saw from the sandbox and the kind:445 rejections we
hit on public relays even with internet.

Changes:
- Preflight clones https://github.com/scsibug/nostr-rs-relay into
  state-headless/nostr-rs-relay and runs `cargo build --release`
  (~3 min first run). Cache hits on subsequent runs.
- New start_local_relay / stop_local_relay functions render a minimal
  config.toml each run (binds to 127.0.0.1, no kind blacklist, data
  under state-headless/relay) and wait up to 20s for the port.
- configure_relays collapsed to a single-relay path — A/B/C all point
  at \$RELAY_URL. Kept publish-lists + key-package publish as smoke
  signals for the advertise path.
- Flags pared down: drop --local-relays (always local now), add
  --port N for when 8080 is taken. --no-build still honoured.
- trap wires relay shutdown alongside daemon shutdown.

https://claude.ai/code/session_01M6dCKAF5Y1VyHGZPjzwDXq
2026-04-21 22:04:28 +00:00
Claude c2e2d4eca8 De-emphasize topic chips in LongFormHeader
Move topic chips below the author meta row and shrink them
(10sp gray text on a subtle fill, no border, tighter padding)
so the title, summary, and author are the primary focus.
2026-04-21 21:47:28 +00:00
Claude 559c69660f feat(cli,commons): amy login + amy create, share defaults with Amethyst
Move the "defaults a new Amethyst account gets seeded with" into commons
so the UI and amy agree byte-for-byte on what a fresh account looks like.

commons additions:
- commons/defaults/Constants.kt         — relay URL constants (moved from amethyst/)
- commons/defaults/AmethystDefaults.kt  — DefaultChannels, DefaultNIP65RelaySet,
                                          DefaultNIP65List, DefaultGlobalRelays,
                                          DefaultDMRelayList, DefaultSearchRelayList,
                                          DefaultIndexerRelayList (moved from
                                          amethyst/model/AccountSettings.kt)
- commons/account/AccountBootstrapEvents.kt — data class + bootstrapAccountEvents(signer, name)
  that builds the nine events a new Amethyst account publishes: kind:0, 3,
  10002, 10050, 10051, 10099, 50, 51, + channel list. One source of truth.

Retrofits:
- AccountSessionManager.createNewAccount now delegates event construction to
  the commons helper; it still packages them into AccountSettings for the
  Android storage path.
- DefaultSignerPermissions stays in AccountSettings.kt — it uses Android
  NIP-55 Permission/CommandType types.
- 19 import updates across amethyst/ to point at the new commons paths.

New CLI verbs:
- amy create [--name NAME]: mint a keypair, write identity.json, seed
  relays.json with the Amethyst defaults, publish all nine bootstrap events
  to DefaultNIP65RelaySet via NostrClient.publishAndConfirmDetailed.
- amy login KEY [--password X]: accept any identifier form Amethyst's login
  screen accepts — nsec1, ncryptsec (+ --password, NIP-49), BIP-39 mnemonic
  (NIP-06), npub1, nprofile1, 64-hex pubkey (default) or 64-hex privkey
  (with --private), NIP-05 (name@domain.tld, HTTP lookup). Read-only keys
  land with privKeyHex=null; Identity now carries that cleanly.

https://claude.ai/code/session_01M6dCKAF5Y1VyHGZPjzwDXq
2026-04-21 21:41:47 +00:00
Claude 39e11d29c1 feat(marmot-interop): add zero-prompt headless harness driving amy
Matches the 13 scenarios in marmot-interop.sh but runs A via the new
`amy` CLI instead of prompting the human. Identities B and C still go
through `wn`/`wnd` from whitenoise-rs.

Layout:
- marmot-interop-headless.sh    — top-level entrypoint
- headless/setup.sh             — preflight, daemon lifecycle, identities
- headless/helpers.sh           — amy wrappers + assertion helpers
- headless/tests-create.sh      — tests 01..05
- headless/tests-manage.sh      — tests 06, 07, 08, 11
- headless/tests-extras.sh      — tests 09, 10, 12, 13

Reuses lib.sh for colours, record_result, wait_for_invite/message,
jq_group_id, and dump_daemon_diagnostics.

Keeps the interactive marmot-interop.sh intact for final UI sign-off.

https://claude.ai/code/session_01M6dCKAF5Y1VyHGZPjzwDXq
2026-04-21 21:18:48 +00:00
Vitor Pamplona 53db6fa10e Merge pull request #2483 from vitorpamplona/claude/review-claude-files-hJo5h
Expand skill library with feed patterns, auth signers, and relay client docs
2026-04-21 17:08:08 -04:00
Claude 60edd473c7 Refreshes .claude/ skill library: fixes stale refs, adds 4 new skills
- Updates CLAUDE.md tech stack to current versions (Compose 1.10.3, Kotlin 2.3.20).
- Reframes kotlin-multiplatform iOS as mature; adds secp256k1-kmp 0.23.0 references.
- Updates desktop-expert Main.kt references (code grew from ~270 to 1341 lines and
  NavigationRail moved to ui/deck/SinglePaneLayout.kt); replaces obsolete
  "hardcoded ctrl = true" anti-pattern note with accurate isMacOS branching.
- Removes compose-desktop.md (superseded by desktop-expert/).
- Adds nostr-expert references: nip19-bech32, event-factory, crypto-and-encryption,
  large-cache. Adds kotlin-expert/common-utilities, compose-expert/rich-text-parsing,
  android-expert/image-loading.
- New skills: account-state (Account + LocalCache), relay-client (subscriptions,
  filter assemblers, preloaders), feed-patterns (FeedFilter + FeedViewModel family),
  auth-signers (NostrSigner across internal / NIP-46 / NIP-55).
2026-04-21 21:00:45 +00:00
Vitor Pamplona 9b945d32e8 Merge pull request #2482 from vitorpamplona/claude/reorganize-app-navigation-wkjQ6
Organize drawer navigation into collapsible sections
2026-04-21 16:59:55 -04:00
Claude bf28fbf26f Add @Preview composables for CollapsibleSection and ListContent
CollapsibleSectionPreview shows two sections with representative rows so
header styling, expand state, and chevron alignment can be verified
against light/dark themes. ListContentPreview wires mockAccountViewModel
and EmptyNav to render the whole reorganized drawer body.
2026-04-21 20:53:11 +00:00
Claude 60c046ed69 Hoist drawer section header modifier and drop per-frame allocations
CollapsibleSection was re-allocating a Modifier chain, uppercasing the
title String, and formatting an accessibility label on every
recomposition. Move the static padding/fillMaxWidth into
DrawerSectionHeaderModifier in Shape.kt, use the section title as-is,
and rely on the chevron's contentDescription instead of a per-frame
onClickLabel format call.
2026-04-21 20:47:16 +00:00
Claude 89198ab24e refactor(marmot,cli): lift shared logic into quartz/commons
Five extractions so the Amethyst UI and the new amy CLI share one
implementation for each piece of Marmot/identity behaviour instead of
reimplementing it per platform.

quartz additions:
- MarmotGroupData.bootstrap(..) + withMergedRelays(..): factory for the
  metadata shape every inviter stamps on a fresh group and the outbox-
  merge rule every admin commit carries forward.
- KeyPackageFetcher: (a) pure fetchRelaysFor union of target kind:10051,
  target outbox, my outbox; (b) one-shot fetchKeyPackage; (c) publish-
  side resolveKeyPackagePublishRelays.
- resolveUserHexOrNull: single entry point for npub / nprofile / 64-hex
  / NIP-05 identifiers. Uses the existing Nip19Parser + Nip05Client
  infra so NIP-05 resolution is live over HTTP.

commons additions:
- MarmotManager.leafIndexOf(..): pubkey -> leaf index lookup used by
  every removeMember caller.
- MarmotManager.addMember(nostrGroupId, keyPackageEvent, relays):
  convenience overload that lifts the base64 decode into the manager.
- MarmotManager.buildTextMessage(..) returning a TextMessageBundle;
  optionally persists the outbound inner event (CLI needs persist,
  Amethyst relies on LocalCache loopback).
- MarmotIngest.ingest(event): routes kind:1059 / kind:445 through
  unwrap -> processWelcome / processGroupEvent -> persist the decrypted
  application message. Deliberately platform-agnostic.

Retrofits:
- Account.addMarmotGroupMember and fetchKeyPackageAndAddMember now take
  a KeyPackageEvent directly; Account.keyPackagePublishRelays delegates
  to KeyPackageFetcher.
- AccountViewModel.updateMarmotGroupMetadata uses bootstrap + merged.
- AccountViewModel.sendMarmotGroupMessage builds the kind:9 template via
  buildTextMessage(persistOwn = false) to avoid double-persisting.
- AccountSessionManager's NIP-05 login branch delegates to
  resolveUserHexOrNull; no more hand-rolled Nip05Id / nip05Client.get.
- CLI Context exposes nip05Client and requireUserHex; syncIncoming now
  calls MarmotManager.ingest; all command sites use KeyPackageFetcher
  + the shared identifier resolver. Npubs util deleted.

https://claude.ai/code/session_01M6dCKAF5Y1VyHGZPjzwDXq
2026-04-21 20:43:00 +00:00
Claude 24fc44c77f Group drawer items into collapsible You/Feeds/Create/System sections
Separates personal lists (profile, bookmarks, my emoji packs, drafts,
wallet, etc.) from discovery feeds (pictures, videos, emoji packs feed,
communities, etc.) so the two uses of an emoji icon and the general mix
of ownership vs. browsing are no longer visually merged into one flat
list.

Each section is collapsible via a chevron header but expanded by default
for quick access.
2026-04-21 20:28:05 +00:00
Claude 3fa45a47c6 feat(cli): add amy CLI with Marmot subcommand surface
New :cli JVM module producing an `amy` binary that drives Amethyst
functionality headlessly. Identity and relay configuration sit at the
root (`amy init`, `amy whoami`, `amy relay …`); Marmot/MLS lives under
`amy marmot …` so future verbs (dm, feed, profile) can slot in cleanly.

Marmot surface covered:
- key-package publish / check
- group create / list / show / members / admins / add / rename /
  promote / demote / remove / leave
- message send / list (kind:9 inner events)
- await key-package / group / member / admin / message / rename / epoch
  (non-interactive polling with --timeout; exit 124 on timeout)

Wiring:
- NostrClient + BasicOkHttpWebSocket.Builder, reusing the existing
  publishAndConfirmDetailed and fetchFirst accessories
- MarmotManager from :commons with file-backed MlsGroupStateStore,
  KeyPackageBundleStore, and MarmotMessageStore (unencrypted — scratch
  harness use only)
- each invocation is one-shot: prepare → syncIncoming → run command →
  persist → disconnect

All commands emit one JSON object on stdout; diagnostics on stderr.
Designed so shell harnesses can pipe through jq without bespoke parsing.

https://claude.ai/code/session_01M6dCKAF5Y1VyHGZPjzwDXq
2026-04-21 20:09:11 +00:00
Vitor Pamplona 9147f1b08b Removes the topNav filter by chess for now.. we should use the left drawer/chess screen 2026-04-21 15:54:24 -04:00
Vitor Pamplona a9c9ae21ab Tries to improve the names on the left drawer 2026-04-21 15:45:36 -04:00
Vitor Pamplona fea9b001a0 Fixes lack of margin for custom emoji sets 2026-04-21 15:45:36 -04:00
davotoula 93755d5739 Add missing translations for badges, emoji packs, communities, and interest sets
Translates 112 new strings into cs-rCZ, pt-rBR, sv-rSE, and de-rDE covering
badges, emoji packs, communities, interest sets, autoplay videos, chat
raid/clip/zap suffixes, and marmot group empty state.
2026-04-21 20:24:27 +01:00
Vitor Pamplona 67cb3a619b Merge pull request #2480 from davotoula/claude/cache-hls-videos-evictor
Cache on-demand HLS videos + adaptive cache sizing
2026-04-21 15:13:27 -04:00
David Kaspar 15ea7930c7 Merge pull request #2479 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-21 21:08:29 +02:00
Crowdin Bot 6309309673 New Crowdin translations by GitHub Action 2026-04-21 18:56:42 +00:00
davotoula 2ed1b17f03 chore(playback): add diagnostic logs for cache + quality selection
Three debug-level log lines for verifying the HLS caching and
resolution-policy plumbing on a real device. Easy to revert as a single
commit before merging if the noise is unwanted.

- VideoCache: one line at app start with computed size, available, total disk
- CustomMediaSourceFactory: one line per MediaSource creation, CACHE or BYPASS
- InitialVideoQualitySelector: one line per applied policy, with selected
  resolution and the rendition list

Tail with: adb logcat -s VideoCache CustomMediaSourceFactory VideoQuality

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 19:42:49 +01:00
davotoula 63454761ef fix(playback): bypass cache for live streams opened via ZoomableContentView
Live streams routed through the Live Streams tab go through ShowVideoStreaming
→ ZoomableContentView, not LiveActivity.kt. ZoomableContentView is generic
and was calling VideoView without isLiveStream=true, so live stream segments
ended up in SimpleCache. The cached HLS manifest then went stale (live
manifests roll constantly) and playback stopped after ~30 seconds.

Plumb the flag through the data model: add isLiveStream to MediaUrlVideo,
set it true when ShowVideoStreaming constructs the model, and pass it from
ZoomableContentView into VideoView. URL heuristic (.m3u8) wasn't viable —
that would also bypass cache for on-demand NIP-71 multi-rendition videos
and defeat the original feature.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 19:42:33 +01:00
Vitor Pamplona a566c663ec Better way to control the optimization level 2026-04-21 14:39:41 -04:00
Vitor Pamplona 17a971b384 Moves resource loader to appleTest 2026-04-21 14:17:30 -04:00
Claude 0218653a9c perf(playback): size the video cache adaptively instead of fixed 1 GB
With multi-rendition HLS renditions in the tens-to-low-hundreds of MB
each, the previous 1 GB fixed budget held only ~10-20 videos and
evicted everything not currently on screen. A Nostr timeline is
append-only so LRU degenerates to FIFO here, which is actually fine —
the bottleneck was the budget, not the policy.

Mirror the image cache pattern: target 20% of currently-available disk,
clamped between a 256 MB floor (low-storage devices) and a 4 GB cap.
That typically lets the evictor keep an order of magnitude more videos
without notably affecting disk pressure on modern devices.

https://claude.ai/code/session_01W8yGieuEyMKeKY9vU9zjKH
2026-04-21 18:43:20 +01:00
Claude 6afacfb747 feat(playback): cache on-demand HLS videos in SimpleCache
Previously any URL containing ".m3u8" was routed through the non-caching
MediaSource factory under the assumption that HLS meant a live stream.
That was correct for kind-30311 live events but wrong for on-demand
NIP-71 videos with multi-rendition fMP4 segments: their .m4s files are
immutable and a perfect fit for SimpleCache's byte-range caching, yet
they were being re-downloaded on every scroll.

Plumb an explicit `isLiveStream` flag from the caller (LiveActivity sets
it to true; everything else keeps the default of false) down through
GetMediaItem / MediaItemData and stash it in MediaItem.mediaMetadata
extras. CustomMediaSourceFactory now reads that flag to decide whether
to use the caching or non-caching factory, falling back to the old URL
heuristic only if the flag is absent (e.g. a MediaItem built outside
the MediaItemCache path).

Also persist the flag through PiP's IntentExtras bundle and use the new
constants in MediaSessionPool for the callback URI key.

https://claude.ai/code/session_01W8yGieuEyMKeKY9vU9zjKH
2026-04-21 18:43:20 +01:00
Vitor Pamplona 2fed69107a Merge pull request #2478 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-21 13:35:47 -04:00
Crowdin Bot 57599eff4f New Crowdin translations by GitHub Action 2026-04-21 17:28:58 +00:00
Vitor Pamplona 8bd1a70867 Merge pull request #2477 from vitorpamplona/claude/fix-marmot-welcome-event-W66a8
Fix MLS spec compliance issues in commit path and message encryption
2026-04-21 13:27:35 -04:00
David Kaspar 862beb0548 Merge pull request #2476 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-21 19:25:57 +02:00
David Kaspar 74b7660af8 Merge branch 'main' into l10n_crowdin_translations 2026-04-21 19:25:48 +02:00
Claude 4feea50ed2 fix(marmot): compute parent_hash chain + align required_capabilities id
Answers "can MDK and marmot-ts see and talk to a user whose group was
created on Amethyst?" — yes, after four more spec-conformance fixes:

1. parent_hash chain (RFC 9420 §7.9.2): Amethyst's commit() and
   externalJoin() now compute the parent_hash for every parent node on
   the committer's direct path and seal the committer's LeafNode with
   the correct leaf parent_hash. Amethyst↔Amethyst used to work only
   because both sides stored empty parent_hash values; against a
   strict peer (ts-mls, openmls) every Amethyst-authored commit was
   rejected with "Unable to verify parent hash". processCommit()
   now also patches the computed parent_hashes back into its tree so
   treeHash() agrees with the sender — otherwise the epoch key
   schedule diverges and AEAD tags mismatch.

2. REQUIRED_CAPABILITIES extension type: 0x0002 → 0x0003 per
   RFC 9420 §13.3. The old value was ratchet_tree's slot, so Amethyst's
   GroupContext.extensions carried a required_capabilities blob
   labelled as ratchet_tree, and openmls rejected the GroupInfo
   as Malformed (ratchet_tree is not valid in GroupContext). This
   completes the extension-ID set from d7114fc (ratchet_tree,
   external_pub) now aligned with the IANA registry.

3. verifyParentHash on the receive side now computes the expected
   chain top-down from the post-update tree rather than reading
   parent_hash fields that applyUpdatePath leaves as empty
   placeholders.

4. An explicit parentHash parameter on buildLeafNode so COMMIT-source
   leaves include the computed value in their TBS signature.

Test harness — reverse interop (Amethyst → outside world):

  - quartz/tools/{mdk,tsmls}-vector-gen/emit-joiner-kp.{rs,mjs}
    generate an MDK/openmls and a marmot-ts/ts-mls KeyPackage with
    marmot_group_data (0xF2EE) and self_remove (0x000A) advertised in
    capabilities so Amethyst's required_capabilities is satisfiable.

  - AmethystAuthoredVectorGen.kt (env-var gated JUnit test) takes the
    foreign KP, adds it to a fresh Amethyst group, and emits the
    Welcome plus three application PrivateMessages as JSON.

  - verify-amethyst.{rs,mjs} replay the joiner's private state,
    call the foreign MLS library's join + process_message, and
    assert the plaintexts match.

Both verifiers now print ALL PASS end-to-end:
  * openmls ← Amethyst: joinGroup + 3× process_message ✓
  * ts-mls  ← Amethyst: joinGroup + 3× processPrivateMessage ✓

https://claude.ai/code/session_01HfHdd5S5rvxUW2ihEpLGJr
2026-04-21 16:13:42 +00:00
Crowdin Bot a6532f69f5 New Crowdin translations by GitHub Action 2026-04-21 15:20:36 +00:00
David Kaspar 4596316817 Merge pull request #2474 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-21 17:20:06 +02:00