Commit Graph
10 Commits
Author SHA1 Message Date
thePR0M3TH3AN 4a3fe06a03 chore: bump userBlocks file-size baseline; flaky-harness tolerate dead-relay connect delay
userBlocks grew (P4 handle teardown) — bump baseline. flaky-relays grid-render
timeout 15s->30s so dead-relay connect timeouts don't cause a false-negative
(ux-harness with a healthy relay renders in ~1.2s; the harness now reliably
passes 4/4).
2026-06-14 16:18:46 -04:00
thePR0M3TH3ANandClaude Opus 4.8 1ac03c49e9 test(perf): flaky multi-relay harness (verification-fidelity fix)
Stands up several mock relays — some alive (seeded), some dead (unreachable
ports) — points the app at all of them, logs in via the fake nip-07 extension,
and asserts the refactor invariants under reconnect churn:
  1. grid renders from alive relays
  2. subscribes on <= the capped core (not all configured relays)
  3. no steady-state REQ storm despite dead relays (was 1000+/s)
  4. report subs are batched (one kind-1984 filter with many #e, no per-id)

All four PASS on the current build. This is the realism the single perfect mock
relay lacked — the reason earlier 'fixed' verdicts didn't hold in production.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 15:19:05 -04:00
thePR0M3TH3ANandClaude Opus 4.8 35c4e68cde chore(perf): add nip-07/relay diagnostic harnesses + dev REQ monitor
Headless Playwright probes that reproduce the real login path via a fake
window.nostr extension (which loginWithNsec bypasses) and measure
steady-state extension-call and relay-REQ rates:
- nip07-heat.mjs, req-capture.mjs, ws-lifecycle.mjs, loopc-capture.mjs, etc.

Plus js/devReqMonitor.js: a localhost-only WebSocket REQ-rate monitor that
warns in-console when relay traffic spikes, naming the storming kinds — used
to capture the real-environment storm the mock-relay harness could not see.
Removable once the refactor lands.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 09:52:41 -04:00
thePR0M3TH3ANandClaude Opus 4.8 2c657c961e test(perf): add fresh-context watch-history decrypt repro
wh-repro.mjs uses a SEPARATE browser context per session (seed vs cold), so the
persisted decrypt cache doesn't warm between runs, and captures the page-side
stack of every window.nostr.nip44.decrypt call.

It root-caused the watch-history cold-load cost: history is bucketed by month
(one encrypted kind-30079 snapshot per month) and the load decrypts EVERY month
via the extension — 600 items / 18 months => 18 serialized decrypt calls. So the
cost is ~1 nip-07 decrypt per month of history; a multi-year history is the ~20s
cold load. (The earlier "~180" figure was a shared-context harness artifact.)

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 20:55:22 -04:00
thePR0M3TH3ANandClaude Opus 4.8 8171fcb347 perf(dm): persist decrypted DM results so reloads skip the extension
Consolidates the persisted-plaintext-decrypt-cache pattern (already used by watch
history) into a shared factory, persistedPlaintextCache.js, and adds a DM cache
on top of it. watchHistoryDecryptCache now delegates to the factory (same public
API; tests unchanged).

decryptDirectMessageEvent now also consults/writes a persisted cache keyed by the
immutable DM event id (JSON of the decrypt result; flushed once per
listDirectMessages batch; cleared on logout alongside the in-memory cache). DM
events never change, so a reload re-decrypts only messages it has not seen and
skips the (serialized, slow) nip-07 extension for the rest.

Harness (dm-repro, now cold/warm): 20 DMs -> COLD nip04.decrypt 50, WARM 0 —
reloads cost zero extension decrypt calls. Also excludes the actor's own key from
NIP-04 remote candidates (decrypting against self is always wasted). DM unit +
integration and watch-history cache tests green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 19:55:07 -04:00
thePR0M3TH3ANandClaude Opus 4.8 bbf8d10473 fix(dm): stop the per-message extension decrypt storm in legacy DMs
The DM-repro harness (fake nip-07 extension) showed legacy DM history made ~12
extension decrypt round-trips PER message (20 DMs -> 241 calls: nip44.decrypt
121 + nip04.decrypt 120). At a real ~100ms extension latency that's ~24s, which
is why DM history "looked broken" — it was just catastrophically slow.

Two causes in decryptLegacyDm:
- It raced EVERY (decryptor x remote-pubkey) combination in parallel. That looked
  like a latency win, but the nip-07 extension serializes calls, so racing N
  combinations just queued N slow round-trips per message.
- orderDecryptors ranked nip44 first (by candidate priority), so every kind-4
  (always NIP-04) message burned a failing nip44 extension call first.

Fix: order nip04 first for legacy DMs (preferLegacy) and try candidates
SEQUENTIALLY, stopping at the first success. With the right scheme and remote
party tried first, it's ~1-3 calls/message instead of ~12. Harness after: 20 DMs
-> 60 calls, zero nip44 attempts (was 241). DM unit + integration tests green.

Adds scripts/perf/dm-repro.mjs (seeds kind-4 DMs, logs in via the fake extension,
calls listDirectMessages, reports decrypted count + extension call counts).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 19:43:49 -04:00
thePR0M3TH3ANandClaude Opus 4.8 04156eb40c test(perf): finish UX harness seeding — watch history + decrypt-cache proof
Extends the fake-nip-07-extension harness to a multi-session flow that publishes
a watch history via the app (real chunking + encryption through the fake
extension), then reloads to measure the decrypt-cache effect by counting
nip44.decrypt extension round-trips:

  seed  : publishes the snapshot (nip44.encrypt per chunk)
  COLD  : first reload — decrypts chunks via the extension (~180 calls @ 300 items)
  WARM  : second reload — decrypt cache hits, ~2 calls

Validates the watch-history decrypt cache (watchHistoryDecryptCache.js): the
warm reload skips ~180 serialized extension round-trips (~9s @ 50ms, more at
real latency). Uses context-level bindings so localStorage (and the cache)
persists across reloads; bypasses feed signature verification of seeded data via
the test-only videoEventVerifier hook so the feed renders deterministically.

Also surfaced: cold load still pays the full decrypt cost, and the decrypt path
makes far more extension calls than there are chunks — an inefficiency to chase.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 16:32:09 -04:00
thePR0M3TH3ANandClaude Opus 4.8 7e3cdb4174 fix(nostr): make feed signature verification fail-open and non-hanging
The UX harness (fake nip-07 extension) revealed that the off-thread verification
could blank the feed: if nostr-tools is slow/unable to load inside the verify
worker, the worker hangs, each batch waits the full 10s timeout, and the
main-thread fallback could return an empty set (fail-closed) — dropping every
event so no videos ever render, silently (devLogger is suppressed in prod).

Harden it so verification can never stall or blank the read feed:
- Worker timeout 10s -> 2.5s, and disable the worker for the session after one
  timeout/error so we never pay the timeout on every batch.
- Bound the main-thread nostr-tools wait (Promise.race ~2s) so a slow/failed
  load can't hang the fallback.
- Fail OPEN when the verifier is genuinely unavailable: pass events through
  unverified rather than dropping them (the pool already does not verify, so this
  does not regress the prior posture). Real verification still runs whenever
  nostr-tools is available.

Adds a regression test (hung worker -> resolves via fallback, worker disabled).
Also updates the harness to seed mode:"live" videos + a NIP-65 relay list for
mock-relay isolation.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 15:52:40 -04:00
thePR0M3TH3ANandClaude Opus 4.8 86b4d4dcae test(perf): add UX harness with a fake nip-07 extension (phase 1)
Lets the agent observe the real app the way a user does: runs bitvid in headless
Chromium against an in-process mock relay, injects a fake window.nostr extension
backed by Node-side nostr-tools crypto (via page.exposeFunction) with a
serialized ~100ms latency queue to model a real extension. Drives the real
nip-07 login flow, isolates to the mock relay via a seeded NIP-65 list, and
captures console logs, per-method extension call counts, feed-render timing, and
screenshots.

Phase 1 (this commit) verifies fake-extension login + relay isolation + metrics.
Already surfaced a stuck "Fetching recent videos…" state to investigate. Next
phases: seed encrypted watch-history chunks and DMs to reproduce/verify the
decrypt-cache wins and the DM-history bug, with before/after extension-call
counts.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 15:38:43 -04:00
thePR0M3TH3ANandClaude Opus 4.8 be602a5a10 chore(perf): add live CPU profiling scripts
Standalone Playwright/CDP profilers used to find the main-thread bottlenecks:
- profile-live.mjs: cold-load CPU profile + long-task report against a URL.
- diagnose-feed.mjs: dumps feed DOM/app-state/console to explain empty feeds.
- profile-seeded.mjs: seeds an in-process mock relay, drives login/tab-switch/
  modal-open in test mode, and attributes secp256k1 self-time to its nearest
  app-level caller.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 11:16:36 -04:00