Outgoing kind-20000 geohash messages mine a NIP-13 nonce tag (8 leading
zero bits, ~256 hashes, typically <1 ms) off the main actor before
signing. Mining is hard-capped at 2 s and cancellable (newer send or
channel switch): on cap/cancel the committed target steps down so the
message still ships promptly with an honest commitment - sending is
never blocked and nothing is dropped. The hot loop serializes the
canonical event once and rewrites only the fixed-width nonce bytes.
Inbound kind-20000 events are scored per NIP-13 commitment semantics
(committed target counts; the ID must actually meet it, extra work
earns nothing) and never hard-rejected: validated PoW >= 8 bits skips
the per-sender rate-limit bucket while the per-content flood bucket
still applies, so old non-mining clients keep working under today's
strict limits while bulk spam gets expensive.
Presence heartbeats (kind 20001), kind-1 notes, and DMs are unchanged;
no UI beyond a pow= field in an existing sampled debug log.
Reimplemented from scratch rather than cherry-picking the stale
feature/pow-geohash-mining-ui branch (unbounded loop, hard receive
filtering, mining UI, XCTest, force unwraps).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mesh and geohash timelines are now store conversations. All public
mutation sites flow through store intents; PublicMessagePipeline keeps
its 80ms UI batching but commits batches via store appends with each
buffered entry carrying its destination conversation (a mid-batch
channel switch now flushes instead of dropping the buffer).
ChatViewModel.messages becomes a cached get-only view of the active
conversation, invalidated through the change subject. The mesh
late-insert threshold is consciously removed: it only ever ordered the
non-rendered messages copy, so strict timestamp insertion makes the
working set agree with rendered order. PublicTimelineStore and the
per-message full-array legacy sync are deleted; the coalescing bridge
mirrors public conversations for the remaining legacy readers.
pipeline.publicIngest: 6.6k -> 9.5k msg/s (+45%); private steady;
store.append 237k/s.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ChatPeerListCoordinator, ChatComposerCoordinator, ChatOutgoingCoordinator,
and GeoChannelCoordinator complete the migration; every coordinator now
depends on a narrow @MainActor context protocol. GeoChannelCoordinator's
three injected closures collapse into a weak context. New intent op
recordPublicActivity(forChannelKey:) keeps lastPublicActivityAt
single-writer. 15 new mock-context tests; flaky-poll deadline in the
gift-wrap dedup test raised for parallel load.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>