Feed videos run under REPEAT_MODE_ONE and looped forever. AutoReplayLimiter
counts each MEDIA_ITEM_TRANSITION_REASON_REPEAT as one completed play and
pauses the player when the 5th play finishes, leaving the video on its first
frame with the play button showing. Pressing play (or the feed mutex resuming
a video scrolled back into view) grants a fresh 5-play allowance, and pooled
players reset the count when they switch to a different media item.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N9RXiM42yqznwTVjw8tLZv
The relay NOTIFY (payment prompt) went into a process-wide NotifyRequestsCache
and DisplayNotifyMessages showed it under any account whose relay list contained
the relay — so a prompt billed to Amethyst surfaced under Vitor, and stale
entries re-appeared on every account switch.
A NOTIFY doesn't reliably name a pubkey, so instead of parsing the message we
correlate it with the AUTH that triggered it: a paid relay answers an
unauthorized AUTH with `OK <authEventId> false …` right before the NOTIFY, and we
signed that auth event. NotifyCoordinator remembers each auth event's signer,
maps the failing OK back to it, and files the NOTIFY into THAT account's own
Account.relayNotifications. Unattributable NOTIFYs are dropped. DisplayNotifyMessages
now reads only the current account's cache.
Also removes the now-unused app-wide RelayAuthPermissionStore singleton and wires
NotifyCoordinator with the pubkey -> Account lookup.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
One shared NostrClient serves every logged-in account, so a relay's NIP-42 AUTH
challenge is not tied to any one of them. AuthCoordinator used to fold every
account's verdict into one decision and then sign with EVERY account (or a random
throwaway key), so a paid relay like inbox.nostr.wine billed and de-anonymized
accounts that never used it — including via a merged read filter that merely named
them, and via account switches / the background notification consumer.
Decide and sign PER ACCOUNT now: an account signs only if the relay is in its own
relay list, or it is publishing its own event there (RelayAuthFirstParty), AND its
own ledger verdict (Account.relayAuthLedger) allows it. A subscription merely
naming the account is NOT first-party — that is the merged-filter false positive.
The random-ephemeral-key fallback is removed. Own-inbox reads still qualify: the
relay serving them is by definition in the account's own list.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per-relay ALLOW/DENY AUTH overrides (and the policy ledger that reads them)
lived in one process-wide DataStore keyed only by relay URL, so a DENY set for
one account silently applied to every logged-in account. Move them to a
per-account file under accounts/<pubkey>/ and warm-cache the overrides in memory
on the Account (RelayAuthPermissionCache) so an AUTH challenge is answered
without a disk read.
DataStoreRelayAuthPermissionStore now shares one DataStore per file path —
DataStore v1 forbids two live instances on one file, and loadAccount can build
the store more than once per account.
Introduces the per-account state holders wired up by the following commits:
Account.relayAuthPermissions, relayAuthLedger and relayNotifications.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reverts the Kover coverage aggregation (introduced in dca345212b) from every
module, the root aggregation block, the Sonar coverage import, the version
catalog, and BUILDING.md. With the plugin present the 16 GB CI runner OOM-killed
during the Android test+build job even with Kover opt-in disabled; removing it
entirely clears the OOM. No other CI changes — the diff versus main is exactly
the inverse of dca345212b.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The NIP-34 status buttons (Mark merged / Close / Reopen) and the PR
View-changes button used default Material3 sizing, which reads oversized
inside a note card. They now use the same 32dp compact height, tighter
content padding and labelMedium text as the app-recommendation buttons.
Issue and PR cards also showed the title twice when the authoring client
duplicated the subject tag as a leading markdown heading in the content
(gitworkshop/ngit do this). The body renderer now strips that first
heading when it matches the subject already rendered as the card title.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USiovhenaijgVEFFzASgfy
- Apply Kover (Apache-2.0, build-time only) to the KMP/JVM modules and aggregate at the root
- include amethyst playDebug in the aggregated Kover coverage
Clients in the wild publish structurally wrong values in profile
metadata — e.g. "nip05":{} — and the strict field serializers made
JsonMapper throw, so contactMetaData() returned null and the entire
profile (name, picture, about…) was discarded.
Generalize the BirthdayTolerantSerializer precedent: string fields now
use TolerantStringSerializer (accepts any JSON primitive, ignores
objects/arrays/null) and the bot flag uses TolerantBooleanSerializer,
so a single malformed field is skipped instead of being fatal.
Non-JSON content (e.g. "Relay initialized") remains unrecoverable and
still parses to null; a test pins that it does so without throwing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KSCGp6KgcnaepEMbxNroYe
The READ_OUTBOX relay-auth prompt and the settings toggle described the
permission as only loading posts, but authenticating to a follow's relay
also downloads their profile information and post engagement. Update the
prompt title, consequence line, reason label and toggle description to
say so.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012VmzSAtkFv29KWDzPVBbxS
Each PoW job now races PoWPolicy.minerWorkers(cores) = cores/2 parallel
searches (at least 1) instead of a single thread, so a post on an 8-core
phone mines ~4x faster while the other half of the cores stays free for
the UI, and the total CPU budget matches the old 2-job x 1-thread pool.
- PoWPolicy.minerWorkers(availableProcessors) is the single definition of
the per-job worker budget.
- PoWPublishQueue gains minerThreads (used by its built-in miner and
exposed for custom mine lambdas); AppModules wires maxConcurrent = 1 +
minerThreads = half the cores.
- PoWNostrSigner gains a workers param (default 1), covering reactions,
reposts and reports via Account.miningSigner; the anonymous-post paths
in the short-note and comment composers pass the same budget.
- Gift-wrap envelope mining stays single-threaded: the template
conversion is a non-suspend hook inside the synchronous NIP-59 build.
- UI time estimates (settings picker, composer chip, broadcast banner)
now benchmark at the queue's worker count via deviceHashesPerSecond(),
and PoWEstimator caches one rate per worker count.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhaF5scnAvhP9wr9R7bTWM
The miner enumerates the nonce space deterministically (the random base is
overwritten before the first hash), so naively racing N copies of the search
duplicates the exact same candidate sequence N times. PoWMiner.mine() now
races workers over disjoint slices instead: each worker's nonce carries a
distinct fixed prefix while only the bytes after it are enumerated, making the
aggregate hash rate scale with cores (~3.8x on a 4-core box).
The hot loop also switches from sha256() to sha256Into() with a reused
32-byte buffer, so hashing no longer allocates per attempt.
amy wiring:
- `pow mine` and `post --pow` mine on all cores by default; `pow mine
--threads N` overrides.
- `pow bench` measures the all-cores rate (what mining now uses, also the
basis for expected_seconds) alongside a new hashes_per_second_single_core.
- PoWEstimator benchmarks with sha256Into to match the miner, and gains a
workers overload that prices in cross-core contention.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhaF5scnAvhP9wr9R7bTWM
The PoW publishing-queue branch split broadcastPrivately into a
NIP17Factory.Result overload delegating to a List<GiftWrapEvent> one,
while the DM-unread branch appended markDmRoomAsRead(signedEvents.msg)
to the old single body. The merge left that call inside the wraps
overload, where signedEvents does not exist, breaking compilation.
Move the read-marker call to the Result overload, and mark the room as
read on the PoW early-return path too (the rumor is signed inline there
and the wraps only publish after mining), so PoW-enabled accounts keep
the unread-clearing behavior from #1286/#1287.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1hu9zpyiuJo6yyoJs1vdw
The mining banner's indeterminate bar becomes determinate: each mining job
gets its own bar that fills over the device's estimated duration for its
difficulty (elapsed / (2^bits / benchmarked hash rate)), with an
"≈ 10 minutes left" label next to the elapsed time. The nonce search is
memoryless, so past the mean the bar honestly falls back to the
indeterminate sweep with "any moment now" instead of parking at 100%.
Queued-only banners keep the shared activity sweep.
The mining notification mirrors this: single-job progress fills toward the
estimate with the time-left text, refreshed every 30 s while mining (the
queue only emits on state changes, not clock ticks).
The composer difficulty menu now prices each option on this device —
"24 bits · ≈ 45 seconds" — using the same cached benchmark, and the duration
formatting is shared (formatApproxDuration/formatTimeLeft) with the settings
estimate instead of being private to the settings screen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADb3dez9jPk6QqyQ1rTx4V
The per-post PoW button on the New Post screen now sits before the
Subject/Title toggle instead of last in the options row.
Every PoW surface (composer button, note chip, mining banner, settings tile,
relay-info minimum-PoW row) switches from the Bolt icon to Manufacturing
(gear, U+E726): Bolt is the app's zap/lightning glyph everywhere else, so a
Bolt PoW badge next to the zap-split/zapraiser/invoice buttons read as a
payment feature. Subset font regenerated for the new codepoint.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADb3dez9jPk6QqyQ1rTx4V