Commit Graph
744 Commits
Author SHA1 Message Date
thePR0M3TH3ANandClaude Opus 4.8 a18230eb70 submissions(#23): foundation — kind-30083 schema, builder, service + tests
First slice of structured submissions (replacing the kind-4 encrypted DM forms).
Pure, hermetic core — no UI or network yet.

- nostrEventSchemas.js: SUBMISSION_KIND=30083 (+ NOTE_TYPES.SUBMISSION, BASE_SCHEMAS
  entry) and buildSubmissionEvent(). PUBLIC + addressable; data in tags: d (id),
  k (type), applicant (npub), p (admin), t=bitvid-submission; free-text in content.
- js/submissions/submissionService.js: SUBMISSION_TYPES + labels, parseSubmissionEvent,
  buildSubmission, generateSubmissionId. Pure — unit-testable with no relays/signer.
- constants.js: FEATURE_SUBMISSIONS (default OFF until the admin UI + forms ship).
- tests/submission-service.test.mjs: 8 scenarios (build tags/type/default, d-tag
  required, parse structure/reject/unknown-type→other, build→parse round-trip of
  every type, id generation).
- check-file-size.mjs: bump nostrEventSchemas grandfather 3250→3541 (adding a new
  event type to the schema registry is the prescribed pattern).
- docs/nostr-event-schemas.md: catalogue row.

Schema validation ✨, lint, build all pass; tests 8/8.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-06 20:17:47 -04:00
thePR0M3TH3ANandClaude Opus 4.8 e5064eeb87 playlists(#37): foundation — kind-30082 schema, builder, pure service + tests
First slice of creator playlists. Adds a bitvid-native addressable playlist
event and the pure, hermetic core that builds/parses/mutates it. No UI or network
yet — those land in follow-up commits.

- nostrEventSchemas.js: PLAYLIST_KIND=30082 (+ NOTE_TYPES.PLAYLIST, BASE_SCHEMAS
  entry) and buildPlaylistEvent(). Data lives in tags: d (id), title/description/
  image, and ordered `a` refs (30078:pubkey:d) to videos; order = tag order;
  repeated refs deduped. Deliberately NOT NIP-51's 30005 "video curation set" —
  bitvid already reads 30005 as legacy interest-sets, so reuse would collide.
- js/playlists/playlistService.js: parsePlaylistEvent, buildPlaylist, and
  immutable add/remove/reorder item helpers + videoCoordinate() (reuses the
  canonical buildVideoAddressPointer, same 30078 contract as the zap fix) +
  generatePlaylistId(). Pure — fully unit-testable with no relays/signer.
- tests/playlist-service.test.mjs: 12 scenarios (build tags + order + dedup,
  d-tag required, parse structure/rejects/defaults, build→parse round-trip,
  add/remove/reorder immutability + clamping, coordinate derivation incl. the
  kind-0 internal-object case).
- docs/nostr-event-schemas.md: catalogue row for the new kind.

Schema validation, lint, and build all pass; tests 12/12.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-06 12:48:49 -04:00
thePR0M3TH3ANandClaude Opus 4.8 cf82978060 zaps: profile-zap counting + channel "sats zapped" total (#47 — closes the feature)
The store now supports p: (profile/channel) pointers — every zap tagged to a
creator pubkey (direct profile zaps AND zaps to their videos, both carry a `p`
tag), from 9735 receipts + verified tallies:

- pointerKey accepts "p"; new profilePointer(pubkey) helper.
- runBatch batches p: pointers into a #p filter; both folds count #p-tagged
  events (deduped by payment_hash like a:/e:).
- Channel page: a "⚡ N sats zapped" total under the lightning address, wired
  to the store via js/channelZapTotal.js (own module for channelProfile.js's
  size budget), hidden until nonzero.

Completes the D2 video + profile scope; the bitvid-native zap tally feature is
now feature-complete (publish + count + chart + video + profile). Tests:
profilePointer/pointerKey + #p counting in tests/zap-totals.test.mjs. Lint
(size cap ok) + build green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-05 00:36:44 -04:00
thePR0M3TH3ANandClaude Opus 4.8 974fdc836d charts: dual-line popularity chart — views (red) + zaps (orange) over time (#47 §5.9)
The per-video Popularity modal now plots a second, orange zaps-over-time line
beside the red views line, with a legend and — new — VISIBLE x-axis date ticks
(the old chart only put the range in the aria-label).

- buildZapSatsTimeSeries: buckets cumulative SATS from 9735 receipts + verified
  bitvid tallies, deduped by payment_hash (same rule as the badge store) so the
  chart and badge agree.
- buildPopularityChartSvg: two token-colored cumulative lines on a shared time
  x-axis, each with its OWN y scale (independent per-series — D5), plus date-tick
  <text> labels. Views = .text-accent, zaps = .text-zap (new --color-zap
  utility). Legend is plain DOM in the modal; headline gains "· N sats zapped".
- js/zapEventsFacade.js: lists a pointer's [9735, ZAP_TALLY_KIND] events via the
  L1 SubscriptionManager for the chart; modal re-renders on onZapTotalsChanged.
- All flag-gated (FEATURE_ZAP_TALLY); the existing views-only chart + its test
  are untouched (buildViewCountChartSvg kept).

Verified live earlier that imports resolve (app builds/runs). Tests:
tests/zap-time-series-chart.test.mjs (5) + existing view-count-chart green.
Lint + build green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-05 00:27:00 -04:00
thePR0M3TH3ANandClaude Fable 5 69a8ecaa3e docs: mark zap-tally retry item done + status IN PROGRESS (#47)
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-04 23:41:55 -04:00
thePR0M3TH3ANandClaude Fable 5 1d732479ed zaps: end-to-end verify test + plan checklist status (#47)
Adds an integration test exercising the REAL verifier against a genuinely
valid, amount-bearing bolt11 (real bech32 decode + nip57 amount parse + real
preimage/description-hash math, no injected fields) — proves the tally pieces
compose. Marks the shipped slices in docs/zap-tally-plan.md §11 and lists the
remaining v1 work: profile-zap #p counting + channel-page UI, the retry-path
publish hook, and the popularity chart (§5.9).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-04 23:40:26 -04:00
thePR0M3TH3ANandClaude Fable 5 a6b2996f4e zaps: foundation for the bitvid-native zap tally (#47) — schema + verify
First slice of docs/zap-tally-plan.md (decisions D1-D5 resolved in the doc):

- ZAP_TALLY_KIND = 30081 (addressable, d=payment_hash → one canonical event
  per real payment) + NOTE_TYPES.ZAP_TALLY schema + buildZapTallyEvent
  (nostrEventSchemas.js). Video + profile pointers.
- FEATURE_ZAP_TALLY flag (constants.js), default ON per D4.
- zapReceiptValidator.js: extractBolt11Fields (payment_hash + description_hash
  from the existing bech32 walker), verifyPaymentPreimage
  (sha256(preimage)==payment_hash), verifyBitvidZapTally — the security core:
  proves the invoice was paid AND bound to this zap request (description-hash
  binding blocks reusing a leaked preimage for another video). Amount is
  bolt11-derived, never the tag; pointers come from the embedded zap request.
- Schema-doc row for kind 30081.

Tests: tests/zap-tally-verify.test.mjs (8) — preimage check, bech32 field
round-trip, valid/bad-preimage/replay/zero-sats decisions, builder tag shape
(video + profile). Lint + build green.

No publishing or counting wired yet — next slices per the plan checklist.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-04 23:32:12 -04:00
thePR0M3TH3ANandClaude Fable 5 ce4904f1a4 docs: add dual-line popularity chart (views + zaps over time) to the zap plan
Extends docs/zap-tally-plan.md §5.9: a second orange zaps-over-time line on the
per-video Popularity chart beside the existing red views line, a legend
(red=Views / orange=Zaps sats), and VISIBLE date labels (the current chart only
puts the range in the SVG aria-label). Reuses the --color-zap token from the
badges, buckets cumulative sats from 9735 + verified tally events (deduped by
payment_hash, same rule as the store), and can partially ship on 9735-only data
before the tally event lands. Adds checklist items, a chart Y-axis open
decision (D5), test targets, and updates goals/scope + TODO #47.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-04 15:27:47 -04:00
thePR0M3TH3ANandClaude Fable 5 ae7cbf22ba docs: dev plan for bitvid-native, preimage-verified zap tally (#47)
Full spec for making Most Zapped + zap badges global/durable/cross-user
without depending on external wallets to publish NIP-57 receipts (which
custodial providers like Strike don't). Key insight: the payer holds the
preimage after NWC pay_invoice, and the invoice's description hash commits
to the zap request's pointer+amount — so a payer-signed event carrying
{zapRequest, bolt11, preimage} is verifiable with the same cryptographic
strength as a recipient-signed 9735, reusing bitvid's existing bolt11 decode
in zapReceiptValidator.js.

docs/zap-tally-plan.md covers: the event schema, the verify algorithm,
cross-source dedup by payment_hash (9735-preferred), file-by-file code
changes, security/edge cases, a feature-flagged rollout, a test plan, open
decisions, and an ordered task checklist so the build stays neat across
sessions. TODO #47 points at it. No code changes yet.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-04 15:13:34 -04:00
thePR0M3TH3ANandClaude Opus 4.8 8e7c6bc325 [onboarding] First-run guided tour with spotlight + accent glow (#59, phase 1)
New-user onboarding per docs/onboarding-plan.md: on the first login of a pubkey
on a device, a skippable guided tour escorts the user around bitvid — a scrim
dims the page with a spotlight CUTOUT over each step's anchor, ringed by a
pulsing glow driven by the theme's accent tokens, plus an anchored popover with
Back/Next/Skip, progress dots, and arrow/Esc keyboard nav.

- js/ui/onboarding/tourEngine.js: dependency-free engine. Spotlight = one
  element whose 200vmax box-shadow (--surface-overlay-darker) dims everything
  except the cutout; an ::after ring animates between --color-accent and
  --color-accent-strong. Positioning via CSS custom properties set from JS (the
  sanctioned dynamic-var pattern; visual styling lives in an injected token-only
  stylesheet). Steps with missing anchors auto-skip, so one script serves all
  layouts. Repositions on resize/scroll.
- js/ui/onboarding/bitvidTour.js: the step script (welcome → feeds →
  subscriptions → upload → profile → setup CTAs that deep-link into the storage
  and wallet panes via profileController.show).
- js/services/onboardingService.js: once-per-pubkey-per-device flags
  (bitvid:onboarding:v1, settingsRestorePrompt-style).
- app.maybeOfferOnboardingTour fires 3.5s after login (feed already rendered
  thanks to the warm cold-boot work; never blocks login);
  app.startOnboardingTour({force}) + a "Take the tour" button in the profile
  modal footer make it re-launchable.
- Lint allowlists: tourEngine.js added to the inline-style allowlist (CSS-var
  positioning, VideoCard precedent) and the design-token ALLOWED_FILES
  (injected stylesheet geometry, RevertModal precedent) — all colors are tokens.

Tests: tests/onboarding-tour.test.mjs (5 — flag lifecycle, tour render/advance/
finish, skip teardown, missing-anchor exclusion, once-vs-force offering, final
CTA deep-link + completion). Lint + build green.

Phases 2-4 (getting-started checklist, empty-state upgrades, fresh-npub create-
account + key backup + kind-0 bootstrap) tracked in the plan + TODO #59.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-02 22:44:45 -04:00
thePR0M3TH3ANandClaude Opus 4.8 50f1a7c2aa [docs] Document static-first priority principle + the recommended build sequence
Maintainer principle: put as much effort into the static bitvid client as
possible before branching into any separate application.

- bitvid-bridge-plan: prominent "PRIORITY PRINCIPLE — static bitvid first" banner
  up top; the Bridge (a second app) is deliberately DEFERRED until the static
  client's live features (Go-Live Tier-1/2 MVP) ship and are maximally developed;
  build it only when live proves demand for its sovereignty/power features.
- live-publish-plan: new "Build order & the static-first principle" section with
  the recommended sequence — 1) Shorts (#16b), 2) Live ingest (#16 watch
  foundation), 3) Go-Live MVP (#16c P0-1, Tiers 1&2, client-side only, no
  Bridge/VPS), then 4) the Bridge later only if warranted.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 13:53:10 -04:00
thePR0M3TH3ANandClaude Opus 4.8 1d02940bac [docs] Add three-tier provider spectrum (no-VPS options) + reshape Bridge accordingly
Answering "can we avoid the VPS / use a free service like StreamYard?": yes.

live-publish-plan:
- DECISION 1 broadened into a THREE-TIER provider spectrum (bitvid hosts none):
  (1) platform embed (StreamYard/OBS -> YouTube/Twitch, free, iframe),
  (2) serverless RTMP->HLS API (Livepeer/Mux/Cloudflare, no server, NATIVE HLS) --
      the recommended no-VPS default; Livepeer most aligned,
  (3) self-hosted node (Bridge headless on VPS) -- sovereignty + WebTorrent VOD.
- New "Provider options" table/section; StreamYard clarified as a studio (not a
  CDN). Same NIP-53 event + player across all tiers; only a per-tier playback
  adapter differs. Phase 1 reframed as provider-agnostic (Tiers 1 & 2, no infra).

bitvid-bridge-plan:
- Repositioned as the Tier-3 advanced/sovereignty option (serverless is the no-VPS
  default), not the default on-ramp.
- Added the HYBRID that dissolves the NAT/VPS problem: a local Bridge records
  locally + forwards (outbound) to a Tier-2 serverless provider that serves public
  HLS -> home box needs no public reachability, yet keeps local recording +
  full-WebTorrent VOD. Made serverless a first-class restream preset (Phase 3);
  updated the reachability risk + NAT notes to point at the hybrid.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 13:49:25 -04:00
thePR0M3TH3ANandClaude Opus 4.8 1fd3862a95 [docs] Blanket rule: bitvid hosts nothing but a static client — lock publish decisions
Maintainer blanket rule: bitvid.network runs NO servers / media infra, ever. All
media receiving/routing is self-hosted by the creator (the Bridge run headless) or
handled by an external provider.

live-publish-plan:
- Add the blanket rule as a non-negotiable principle in the Core Decision.
- Lock all 6 decisions: D1 self-host OR external only (managed bitvid node
  REMOVED); D2 signing A-then-NIP46, managed account (D) removed; D3 pass-through;
  D4 HLS-first; D5 Bridge IS the self-host path; D6 keep specific flags.
- Sweep every "managed / public BitVid node" reference: the Media Node is always
  self-hosted (= Bridge headless per DECISION 7) or external; Phase 3 reframed as
  a reference node the CREATOR runs; risks reframed (burden is the self-hoster's,
  not bitvid's). Status: decisions locked.

bitvid-bridge-plan:
- Remove "public Media Node" implications; the Bridge is the self-hosting vehicle.
- Add the reachability/NAT reality: a home-only Bridge can't serve public HLS →
  headless-on-VPS is the path (why DECISION 7 matters).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 13:33:17 -04:00
thePR0M3TH3ANandClaude Opus 4.8 01545056af [docs] Harden Go-Live flag-gating + close review gaps in publish/bridge plans
live-publish-plan:
- Expand the config-flags section into a full enumerated "off = no trace" list:
  every Go-Live surface (Studio/entry points, My Videos Live sub-tab, live-event
  builders, Bridge/session UI, restream UI, archive->VOD flow, routes) is absent
  when FEATURE_LIVE_PUBLISH is off. Bridge/restream have their own sub-flags.
- Clarify watch/publish independence (an instance can allow watching without
  broadcasting).
- New gap closed: WHO may go live — publishing should respect the whitelist/WoT
  (operators can restrict broadcasting to whitelisted creators; don't be an open
  ingest relay).

bitvid-bridge-plan:
- Invariant 6: the Bridge respects the connected instance's config (embedded
  webview inherits FEATURE_LIVE_PUBLISH; go-live degrades gracefully when off).
- Invariant 7: bind local ingest to loopback (127.0.0.1) only; auth required for
  any wider bind. Matching risks added.
- Architecture: Studio webview loads the instance's deployed web app (remote,
  cached) so it auto-inherits instance flags and never drifts; instance URL
  configurable.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 13:27:17 -04:00
thePR0M3TH3ANandClaude Opus 4.8 626ef70d3f [docs] Lock all 10 BitVid Bridge decisions (maintainer accepted every rec)
Locked: Wails/Go (D1, tied to D7 one-codebase-two-modes), embedded Studio webview
(D2), receiver-first MVP (D3), NIP-46 + encrypted-nsec signer (D4), RTMP MVP (D5),
bundled rclone (D6), one codebase headless-or-UI (D7), pass-through only (D8),
project-owned code signing (D9), match-bitvid license + no telemetry (D10). Added
a locked summary table; status now "decisions locked."

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 13:24:00 -04:00
thePR0M3TH3ANandClaude Opus 4.8 a2050ccd50 [docs] Dev plan for the BitVid Bridge cross-platform streaming app
Expands live-publish-plan Phase 7 into docs/bitvid-bridge-plan.md: a super-simple
Windows/macOS/Linux app that makes going live turnkey.

Core design: native media core (MediaMTX + FFmpeg + rclone; never sees the Nostr
key) + an embedded bitvid "Studio" webview that signs — talking over in-app IPC,
NOT browser->localhost (dodges mixed-content/PNA and keeps signing in the web
layer, honoring "BitVid signs; Bridge routes").

Surfaced 10 decisions: framework (Wails/Go vs Tauri), control model (embedded
webview vs website-drives-localhost), capture vs receive, webview signer support
(NIP-46 + encrypted nsec), ingest protocols (RTMP MVP), upload (bundled rclone),
one-codebase-two-modes (Bridge desktop == self-hosted Media Node headless),
pass-through-only transcode, code-signing/notarization ownership (the real cost of
"easy install"), license/telemetry.

Phases 0-7 (shell+core -> receive MVP -> record/VOD -> restream -> turnkey/packaging
-> built-in capture -> SRT/WHIP/WebRTC -> headless node + BitVid Box), invariants,
packaging/signing, risks, sources. Linked from the publish plan.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 13:22:02 -04:00
thePR0M3TH3ANandClaude Opus 4.8 8b98de8ccd [docs] Add P2P/WebTorrent story to the live plans + a BitVid Live advantages section
Answering "can live use WebTorrent?": classic WebTorrent can't carry live (its
infohash needs the complete file), but P2P *live* is real via HLS + peer segment
sharing.

- live-ingest-plan: new "P2P-assisted playback" section — P2P Media Loader
  (hls.js-compatible, historically WebTorrent-tracker-based) shares HLS segments
  over WebRTC to offload origin bandwidth. Gated on real concurrency (DECISION 1's
  whitelist scope makes live sparse), so v1 = plain hls.js, P2P later. VOD keeps
  full WebTorrent.
- live-publish-plan: new "Why BitVid Live — advantages" section (P2P offload,
  full-WebTorrent VODs, Nostr-native/no-lock-in, built-in zaps, BYO storage/relays/
  self-host, one-canonical-stream+mirrors, archive = first-class video, self-
  sovereign identity). Playback section cross-references the P2P offload + VOD
  WebTorrent.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 13:18:49 -04:00
thePR0M3TH3ANandClaude Opus 4.8 18d4d29616 [docs] Dev plan for BitVid Live publishing / "Go Live" (#16c)
Third live/short plan, sequenced after #16 (ingest) + #16b (shorts). Turns the
maintainer's BitVid Live design into a repo-accurate, convention-aligned plan:

- Core decision: Nostr-native control layer, NOT a hosting backend. Static client
  stays static (signs, no keys, no server); the one thing a browser can't do
  (receive/route live media) is isolated in an OPTIONAL server-side Media
  Node/Bridge (MediaMTX). External-provider-only mode (Phase 1) needs zero infra.
- Cross-linked to docs/live-ingest-plan.md (#16): the watch side (Live tab,
  player, chat, zaps, hls.js) is a dependency and reused; this doc adds the
  builders + Studio + Media Node/Bridge. Shared Phase 0 schema in nostrEventSchemas.
- Verified against real code: NIP-98 HTTP-Auth (kind 27235) builder exists for
  bridge auth; s-tag + js/utils/storagePointer.js -> .info.json is exactly how
  archive->VOD maps (ended stream becomes an ordinary kind-30078 video + 34235
  mirror); canonical video kind 30078.
- Decisions surfaced: hosting model, signing-during-stream (tab-open vs NIP-46),
  pass-through vs transcode, WebRTC scope, local Bridge scope, flag reconciliation
  (FEATURE_LIVE_INGEST/PUBLISH/BRIDGE/RESTREAM).
- 9 phases (0 flags+schema → 1 external-provider no-infra → 2 mock bridge → 3
  Media Node MVP → 4 recording/VOD → 5 restream → 6 OBS companion → 7 local
  Bridge → 8 WebRTC), 5 engineering-rule invariants, risks, sources.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 13:04:39 -04:00
thePR0M3TH3ANandClaude Opus 4.8 c5f91ce683 [docs] Review pass on shorts + live plans: fix stale/contradictory bits, close gaps
Shorts:
- Status now "decisions locked"; intro lists the real scope (exclusion + publish).
- Remove stale "candidate signals / confirm in Phase 0" preamble (D4 locked).
- Fix inaccuracy: native shorts are kind-30078 + isShort, not kind-22.
- Moderation section says locked + notes the NSFW no-autoplay rule.
- Rewrite Risks to the locked decisions: main-feed-exclusion regression risk,
  native-marker-must-round-trip risk, WebTorrent-swarm-storm on autoplay, search
  scope sub-decision. Drop the now-inverted double-listing/aspect-ratio risks.

Live:
- Status "decisions locked"; scope reads read→post chat + host zaps.
- Config flags corrected: introduces FEATURE_LIVE_INGEST *and* FEATURE_LIVE_CHAT_POST
  (Phase 3b); "two flags" footprint fixed; chat-post UI gated on both.
- Phase 0 now "confirm field mapping + default relays + zap target" (not "lock
  decisions", already locked).
- Moderation: read-chat WoT-mute filtering + post-chat rate/stance as design items.
- Add risks: high-volume chat buffering, planned/scheduled streams, multiple
  streaming renditions, private/token-gated streams. Zap-target resolution noted.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 12:48:58 -04:00
thePR0M3TH3ANandClaude Opus 4.8 a1eb87873d [docs] Lock all live-ingest plan decisions (1-5)
- D1 whitelisted hosts only (empty-when-nobody-live accepted).
- D2 read + post + zaps as the end state, phased: Phase 3 read chat + host zaps,
  Phase 3b post chat (own sub-flag + moderation stance).
- D3 hls.js, lazy-loaded + vendored behind the flag (native HLS is Safari-only).
- D4 one Live tab: live first, then recent ended (VOD) — also mitigates the
  sparse-live empty-tab concern.
- D5 user read relays + a configurable set of live-discovery relays (widens
  where, not who).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 12:44:23 -04:00
thePR0M3TH3ANandClaude Opus 4.8 75c5c0a9bb [docs] Lock shorts D5 (phased UI) + D8 (marker on 30078)
- D8: native short = kind 30078 + isShort marker (mirrors to 34236); marker is
  fine since a single isShort boolean reliably distinguishes shorts everywhere.
- D5: Option C phased — Phase 1 grid + portrait card + main-feed exclusion,
  Phase 2 immersive feed later.

Shorts plan decisions now locked except D6 (card: VideoCard variant vs dedicated
ShortCard) which is a build-time implementation choice.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 12:36:54 -04:00
thePR0M3TH3ANandClaude Opus 4.8 fd13c9a145 [docs] Lock shorts decisions 1-4/7, native-short representation (8), publish+management UI
Shorts plan decisions from maintainer walkthrough:
- D1 whitelist scope, D2 inherit moderation (+ NSFW no-autoplay safeguard),
  D3 Shorts tab ONLY (exclude from main feeds), D4 kind is the definer.
- D7 (agreed): upload-modal content-type selector + explicit isShort marker on
  the kind-30078 schema (native shorts need it; kind-22 only covers ingested).
- D8 (leaning marker): native short = 30078 + isShort marker mirrored to 34236,
  OR a distinct kind that also mirrors to 34236 — both yield the same external
  result (WebTorrent-rich native + standard 34236 mirror so other NIP-71 apps
  see bitvid shorts). Marker recommended; maintainer to confirm.
- Publish/management UI is flag-gated (off = no trace): no upload selector, no
  My Videos Shorts sub-tab when FEATURE_SHORTS off.
- My Videos gets Videos/Shorts/Live sub-tabs (reusing the admin sub-tab pattern);
  Live sub-tab tied to publish (#16c), not ingest. Noted in both plans.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 11:54:06 -04:00
thePR0M3TH3ANandClaude Opus 4.8 b63e4bef4d [docs] Add researched live watch-UX best practices (desktop vs mobile) to live plan
Fold July-2026 live-streaming watch UX into docs/live-ingest-plan.md:
- Desktop cinematic layout: 16:9 player + collapsible chat side rail, host/
  LIVE badge/viewer count below, theater + fullscreen, keyboard controls.
- Mobile: player-top / chat-below, resizable chat overlay in fullscreen.
- Live-specific: LIVE badge + viewer count, auto-scroll chat w/ pause-on-scroll,
  live vs ended seeking (VOD scrubber in Phase 4).
- Latency reality baked in: HLS ~6-30s behind → chat runs ahead of video; don't
  promise low-latency.
- bitvid angle: live zaps to the host (zap.stream-style) fit the existing zap
  system and can ship even before/with read-only chat.
- Phase 2 now includes the cinematic layout; sources cited.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 11:21:05 -04:00
thePR0M3TH3ANandClaude Opus 4.8 0cf1838227 [docs] Add researched short-form UX best practices (desktop vs mobile) to shorts plan
Fold July-2026 short-form UX research into docs/shorts-plan.md:
- Immersive one-at-a-time 9:16 feed (grid = entry point), muted autoplay + loop,
  preload next 2-3 (no spinner between shorts), overlaid action rail
  (Zap/Comment/Share/follow — zaps as the bitvid differentiator), captions when
  present, prefers-reduced-motion.
- Mobile: full-screen + swipe up/down.
- Desktop: centered letterboxed player (never stretch 9:16) + NATIVE keyboard nav
  (up/down prev/next, Space, M, Esc) + side action rail — YouTube Shorts' desktop
  keyboard-nav gap is an opportunity to do it right.
- Phase 2 reframed as the responsive immersive feed; sources cited.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 11:18:28 -04:00
thePR0M3TH3ANandClaude Opus 4.8 8e76f0d7a8 [docs] Refine shorts/live plans: portrait card design + committed VOD & chat phases
Shorts (docs/shorts-plan.md):
- Add DECISION 6 + architecture/phase items for a NEW portrait 9:16 card design.
  The existing VideoCard.js is hardcoded 16:9 (ratio-16-9), so shorts need either
  a "short" variant of VideoCard or a dedicated ShortCard — a first-class Phase 1
  deliverable, not an afterthought.

Live ingest (docs/live-ingest-plan.md):
- Reframe past streams (VOD) and chat from "deferred/maybe" to committed phases:
  Phase 3 read-only chat (+ optional 3b post), Phase 4 ended/recording VOD
  playback through the same HLS path. So the plan's phases fully account for
  live AND past zap.stream streams AND chat.
- Add a distinct LiveCard design (LIVE badge, viewer count, ended state).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 11:15:01 -04:00
thePR0M3TH3ANandClaude Opus 4.8 163f7e11d5 [docs] Dev plans for live-stream ingest (#16) and shorts (#16b)
Two flag-gated, watch-only content types, each spec'd following the repo's
docs/*-plan.md convention with open decisions embedded as DECISION callouts.

- docs/shorts-plan.md (#16b): NIP-71 kind 22. Ingest already exists
  (nip71IngestAdapter handles kind 22), so this is an isShort marker + a Shorts
  sidebar tab + vertical UI + a shorts feed source. Behind FEATURE_SHORTS.
- docs/live-ingest-plan.md (#16): NIP-53 kind 30311 (+1311 chat), HLS playback.
  Leads with a Phase 0 research spike (confirm tag shapes from zap.stream/
  shosho.live, de-risk hls.js + CORS). Behind FEATURE_LIVE_INGEST; publishing
  (#16c) is explicitly out of scope with its own future flag.

Both docs specify the "off = no trace" wiring (no tab, no feed registration, no
subscriptions, no route, no lazy assets) per the locked shared requirement.
TODO #16/#16b now link their plans.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 11:10:38 -04:00
thePR0M3TH3ANandClaude Opus 4.8 ccbe8d9bb2 feat(liveness): config-driven card visibility policy + probe prefetch (#5)
Makes the card liveness-visibility behavior switchable so it can be A/B
feel-tested, and probes ahead of the scroll for a snappier result.

- CARD_LIVENESS_POLICY (config/instance-config.js): "show-pending" (default,
  unchanged) | "hide-foreign" (foreign/ingested cards hidden until a source
  verifies; native cards stay show-pending) | "hide-all". Owner's own cards are
  always visible. Flip live for testing without a rebuild:
  window.__BITVID_CARD_LIVENESS_POLICY__ = "hide-foreign" then refresh/scroll.
  Implemented via a data-foreign provenance flag on the card +
  getCardLivenessPolicy() in cardSourceVisibility.js.
- LIVENESS_PROBE_PREFETCH_MARGIN (default 600px): both liveness probes were
  already viewport-gated (IntersectionObserver, on-screen-first, WebTorrent
  priority-queued + concurrency-capped); this expands their rootMargin so cards
  just below the fold verify before the user reaches them.
- Tests: three policy scenarios in tests/video-card-source-visibility.test.mjs;
  also un-broke 3 pre-existing VideoCard tests by adding the missing
  IntersectionObserver JSDOM stub.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-25 09:13:52 -04:00
thePR0M3TH3ANandClaude Opus 4.8 df99bdb65f fix(zap): collapse split when creator == platform; close #3 audit
Finishes the remaining #3 zap audit items.

- zapSplit: when the platform fee would pay the SAME Lightning address as the
  creator (e.g. the creator is the platform operator), collapse to a single
  full-amount payment instead of sending two invoices to one wallet — avoids an
  extra routing fee and a confusing duplicate receipt for the same total.
- Audited split correctness: floor(amount*fee/100) with the creator taking the
  remainder loses no sats; a sub-1-sat fee rounds to 0 and the whole zap goes to
  the creator (no zero-amount platform send); fee is clamped 0–100 and a junk
  override falls back to the default. The earlier "fee landed in my own wallet"
  report is expected behavior (sender is the platform operator, or a self-zap),
  not a bug.
- Cheat-resistant tests: testCreatorEqualsPlatformCollapsesToOnePayment and
  testSplitRoundingPreservesEverySat in tests/zap-split.test.mjs.
- Receipt validation (9735) and NWC budget/retry UX confirmed already solid;
  audit plan + TODO #3 closed.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-25 08:56:00 -04:00
thePR0M3TH3ANandClaude Opus 4.8 380de0922a docs: update zap-audit plan, pre-launch TODO, and test-integrity notes
Record this session's fixes (delete/zombie, NIP-71 mirror teardown,
watch-history bucketing, the zap chain, the test-runner quarantine work)
and the test spec-corrections.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-23 14:53:51 -04:00
thePR0M3TH3ANandClaude Opus 4.8 58047c2589 docs: mark Capacitor + NIP-46 plans on hold
Defer the Capacitor native-app plan and the NIP-46 roadmap enhancements
(current NIP-46 login works well) while the pre-launch backlog clears.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-23 14:53:11 -04:00
thePR0M3TH3ANandClaude Opus 4.8 07b42788d5 docs: zap-audit plan + capture cross-session learnings in TODO
- docs/zap-audit-plan.md: architecture map, findings (LNURL CORS send-error,
  comment box confirmed working, popover mis-position), and prioritized next
  steps (CORS/LNURL proxy decision first, then popover, fee-split correctness,
  receipt validation, NWC UX). Notes the injectable-fetcher/deps test seams.
- TODO #3 points to the doc; #9 adds two unverified learnings: confirm the
  community-blacklist batching actually dropped the kind-30000 storm (still saw
  =32 in a later load), and trace the `from_hex` malformed-hex source via the
  new sanitizer dev-log.

Planning/docs only.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-22 22:26:07 -04:00
thePR0M3TH3ANandClaude Opus 4.8 5e2b22156f docs: video-liveness plan (speed, playability probe, multi-source)
Audited the card liveness/health system. Policy (cardSourceVisibility) is
already correct — hides only when neither CDN nor WebTorrent is healthy,
un-hides when WebTorrent flips green. Documents the real gaps (slow probes;
opaque HEAD no-cors inaccuracy for cross-origin NIP-71 CDNs; single-source
adapter dropping extra imeta) and a 3-phase plan: multi-source capture,
video-element playability probe across sources, and speed tuning. Adds
docs/video-liveness-plan.md + expands pre-launch TODO #5. Planning only.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-22 21:26:20 -04:00
thePR0M3TH3ANandClaude Opus 4.8 c404618137 docs: flag externally-managed storage for NIP-71 reference imports
Imports not produced by bitvid's storage system (reference imports / external
URLs) must be explicitly flagged externally-managed so storage tooling never
treats them as bitvid-owned: external badge (never "missing from bucket"),
excluded from orphan reconciliation, delete "file left behind" warning
suppressed, liveness unverifiable. Adds import modes (full re-host vs
reference) + Phase 2b to the plan and TODO 17c. Notes the existing
isUrlUnderBase heuristic is the baseline but a durable flag is needed.

Planning only; no code changes.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-22 19:30:42 -04:00
thePR0M3TH3ANandClaude Opus 4.8 0112903a6d docs: NIP-71 on-boarding + cross-ecosystem dedup dev plan
Adds docs/nip71-onboarding-plan.md and pre-launch TODO 17c for the agreed
two-way NIP-71 interop:
- Phase 1 (first): cross-ecosystem dedup — prefer the bitvid (30078) version,
  show exactly one in every grid; identity = author + import-link/hash/infohash.
- Phase 2: creator-initiated self-import (model A) — re-host to the creator's
  storage (CDN) + WebTorrent, publish a signed 30078 with an imported-from
  provenance tag. No forging others' notes; no instance auto-rehost.

Planning only; no code changes.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-22 19:28:15 -04:00
thePR0M3TH3ANandClaude Opus 4.8 dff1a27c0c docs(nip71): record dual-event vs single-canonical decision
Decided to keep the dual-event model (kind 30078 canonical + additive 34235/36
mirror) rather than collapse to a single canonical 34235 note. Captures the
rationale: private videos can't be public NIP-71 (no encryption), and kind 30078
is the addressing root (watch-history/comments/reactions/views/playlists/naddr/
moderation + hardcoded video filters) so switching it is a large forward-only
migration not worth the interop win right now. Accepted trade-off: keep the mirror
in sync on edit/delete (Phase 1 polish). Phase 0/1 builder/parser remain reusable
if single-canonical is ever revisited.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-22 09:57:59 -04:00
thePR0M3TH3ANandClaude Opus 4.8 5c93b7496e docs(nip71): audited moderation/WoT/mute/NSFW interop requirements
Audit (verified in code) of how moderation, web-of-trust, mute/block lists, and the
site ALLOW_NSFW_CONTENT config interact with NIP-71 interop:
- Controls key on author pubkey + event id, so they apply to NIP-71 videos
  automatically IF ingested videos share the feed pipeline (blacklist-filter,
  moderation/WoT, suppression, kids, nsfw).
- Phase 1 (outbound): gate the mirror on ALLOW_NSFW_CONTENT (don't mirror NSFW from
  an instance that forbids it); builder stays pure + always sets content-warning.
- Phase 2 (inbound) gap: mergeNip71MetadataIntoVideo does NOT map content-warning →
  isNsfw, so foreign NSFW would bypass the site filter — must add that mapping; plus
  route ingest through the full pipeline and trust-gate unknown authors.
- Kids feed already safe (allowlist on isForKids).

No code changes yet (both items land in their phases); captured as checklist items.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-21 23:10:19 -04:00
thePR0M3TH3ANandClaude Opus 4.8 80dce87195 docs(nip71): plan review pass — discovery/relays, dedup, capture, delete nuance
Improvements from a second pass over the plan + code:
- Add "Discovery & relays" section: the mirror must publish to the user's NIP-65
  OUTBOX write relays (publish kind 10002 first if missing) or outbox clients
  (Amethyst) can't find it — discovery is a relay problem, not just format. bitvid
  already has publishRelayList/outbox; reuse it.
- Architecture: bitvid never double-renders its OWN mirror (merged onto 30078 by
  videoRootId via nip71Cache) — dedup applies from Phase 1, not just Phase 2.
- 3rd hard rule: url must be publicly reachable (gate on verifyPublicAccess).
- Capture: wire existing upload `duration` into imeta; add a loadedmetadata probe
  for `dim` + 34235-vs-34236 selection (pulled into v1; trimmed from backlog).
- Mapping: split `.content` (full) vs `alt` (short a11y text); note published_at is
  read from the original 30078 so it's stable across edits.
- Surface the mirror toggle in the My Videos tab (existing management surface).
- Delete nuance: NIP-09 (a/k tags) AND empty-replace the addressable event so
  NIP-09-ignoring clients still see it cleared.
- Phase 0: add an interop golden-shape test to guard against silent drift.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-21 22:49:17 -04:00
thePR0M3TH3ANandClaude Opus 4.8 5a71016388 docs(nip71): hashtags + existing-note conversion in the plan
- Document that the explore/kids feed ALREADY consumes NIP-71 hashtags (both raw
  ["t",…] tags and video.nip71.hashtags) — feed algo needs zero changes; only
  capture is missing (no per-video hashtags field on 30078 today).
- Add a "Converting existing videos" section: opting in is an edit that maps every
  available 30078 field into its NIP-71 home (incl. hashtags → t) and writes the
  34235/36 mirror with d=videoRootId; non-destructive, lockstep edits.
- Pull hashtag capture + field conversion explicitly into Phase 1.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-21 22:44:41 -04:00
thePR0M3TH3ANandClaude Opus 4.8 bd056b2e3b docs(nip71): interop migration plan + refresh stale vendored NIP-71 spec
Adds docs/nip71-migration-plan.md — the agreed spec to build from:
- Dual-event, opt-in (off by default): keep canonical kind 30078; add addressable
  34235/34236 mirror (d=videoRootId, edits in lockstep).
- Research correction: 34235/36 are NOT deprecated — current NIP-71 designates the
  addressable pair for content that needs post-publication updates (exactly bitvid's
  edit-in-place model).
- WebTorrent rides STANDARD NIP-94 imeta fields (magnet, i) rather than a hack;
  private videos never mirrored; HTTPS url required to mirror.
- Phases: 0 retarget builders → 1 outbound publish/edit/delete → 1.5 NIP-89 "Open in
  bitvid" handler + NIP-51 kind 30005 portable playlists → 2 inbound ingest.

Also refreshes docs/nips/71.md (was stale: only 21/22; now the current 233-line
spec with 34235/36 addressable kinds, waveform, origin, multi-audio). Updates
TODO #17 to point at the plan.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-21 22:40:57 -04:00
thePR0M3TH3ANandClaude Opus 4.8 2a979c2eca docs(deploy): record release-channel → Vercel project promotion + on-demand deploy
Captures the 3-project mapping (bitvid-unstable/beta/bitvid → unstable.bitvid.network
/ bitvid-beta.vercel.app / bitvid.network), the vercel.json knobs (npm install pin,
deploymentEnabled.unstable), the promotion git steps (merge unstable→beta taking
unstable's tree, ff main), per-project on-demand deploy commands, and the
version-marker verification + soak-skip caveat.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-21 22:23:32 -04:00
thePR0M3TH3ANandClaude Opus 4.8 8022942464 docs: record nip-07 list-decrypt + post-login freeze resolution
- README "Login with Nostr": recommend a well-maintained NIP-07 signer
  (nos2x/Alby) and add a troubleshooting note for the locked/asleep-signer
  case (unlock/reload + refresh), since a flaky extension was the root cause
  of the encrypted-list load failures.
- KNOWN_BUGS #0: mark RESOLVED (2026-06-16) — confirmed in real env after the
  user switched to nos2x; DMs/watch-history/lists decrypt in ms with no
  timeouts. Documented the DM decrypt-resilience + receiver fixes and the
  post-login profile-panel freeze fix (defer panel rendering to selectPane).
  Recorded the remaining low-priority follow-ups (liveness-ranked relays,
  opt-in queue concurrency, optional DM double-decrypt routing).
- Retitle the section "## Open" → "## Tracked" so resolved entries (#0/#1/#2)
  aren't mislabeled as open; status is now per-entry.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-16 16:23:17 -04:00
thePR0M3TH3ANandClaude Opus 4.8 4f7ef08935 fix(feed): render the grid after the disclaimer is dismissed (logged-out)
KNOWN_BUGS #1: logged out, the video grid stayed empty after dismissing the
disclaimer until a manual page refresh.

Reproduced deterministically with scripts/perf/disclaimer-grid.mjs (fresh
context, real relays) once it actually waits for and dismisses the
disclaimer: post-dismiss card timeline was 0,0,0,… and only a reload
yielded 132 cards.

Root cause: bootstrapInterface (js/index.js) opens the disclaimer — which
makes the background inert via staticModalAccessibility — and THEN awaits
handleHashChange(), which loads the feed view and renders into that
inert/hidden background, so the initial grid mount/render is dropped.
Dismissing the disclaimer never re-triggered a render.

Fix: disclaimer.js hide() now dispatches a `bitvid:disclaimer-dismissed`
event; index.js listens once and calls application.loadVideos(true) so the
feed re-renders into the now-interactive background. Only fires when a shown
disclaimer is dismissed, so returning visitors (who never see it) get no
redundant reload.

Harness after fix: post-dismiss timeline 132,132,… immediately. Full build
+ unit suite green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-16 15:40:06 -04:00
thePR0M3TH3ANandClaude Opus 4.8 8f39b2c479 feat(signer): surface an actionable notice when the signer goes unresponsive
A raw window.nostr probe in the user's browser (getPublicKey → nip04.encrypt
→ nip04.decrypt, bypassing ALL of bitvid) HUNG forever, with the extension's
own console showing `ObjectMultiplex - orphaned data for "background-liveness"`
+ `malformed chunk` + listener-leak warnings. That is the NIP-07 extension's
content-script ↔ background-service-worker channel being dead (classic MV3
worker death) — an extension/environment failure, not a bitvid bug. No client
change can force an unresponsive signer to answer; the fix is to reload/unlock
the extension.

What bitvid CAN do is stop failing silently. Add js/utils/signerHealthNotice.js:
after a few consecutive list-decrypt timeouts while the signer reports itself
present, emit ONE rate-limited, actionable user notice ("your signer isn't
responding to decryption — unlock/reload the extension and refresh") instead of
the silent 15-minute retry loop. Reset on any decrypt success so a transient
blip doesn't warn. Wired into blocks, hashtags, and subscriptions.

Also documents the definitive root-cause + all resilience increments in
KNOWN_BUGS #0. Test: tests/signer-health-notice.test.mjs (threshold, rate-limit,
success-reset). Full unit suite green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-16 14:18:46 -04:00
thePR0M3TH3ANandClaude Opus 4.8 ab57f8d596 fix(nostr): circuit-break the dead nip-07 signer channel
Real-env console (2026-06-15) showed that once the extension's message
port drops under the post-login burst ("message channel closed"), EVERY
subsequent decrypt call hangs to its full ~15s timeout. Each list service
(blocks, hashtags, subscriptions, watch history) independently retries on
that timeout, so the app sits in a permanent loop of 15s-blocking
extension calls — pinning the CPU ("fans at max") and never recovering
until a manual page refresh.

Add a shared circuit breaker at the runNip07WithRetry chokepoint:
- After CIRCUIT_TIMEOUT_THRESHOLD (3) consecutive timeouts the circuit
  OPENS; further calls fail fast with code "nip07-channel-unresponsive"
  (no multi-second hang), so the retry loops become cheap and the CPU
  settles.
- While open, a single probe per CIRCUIT_PROBE_INTERVAL_MS is allowed
  through; one success CLOSES the circuit and normal decryption resumes —
  no page refresh required.
- Responsive failures (extension answered with an error) reset the
  timeout streak — only genuine channel hangs open the circuit.
- Interactive permission prompts bypass the breaker entirely: a user
  taking their time at the extension UI can neither trip it nor be
  blocked by it, and such a prompt can itself heal the channel.

Increment 2 of the "cut the login burst, then add a safety net" work for
KNOWN_BUGS #0. Test: tests/nostr/nip07-channel-breaker.test.mjs
(cheat-resistant — drives the real queue with hanging/responsive/success
ops; asserts open-after-threshold, fast-fail, probe-recovery, responsive
errors don't open, and bypass is never fast-failed). Full unit suite green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-15 08:49:05 -04:00
thePR0M3TH3ANandClaude Opus 4.8 55bd181867 fix(nostr): bound read/subscribe relay set to stop cold-login REQ storm
On cold login the app opened WebSockets to a user's full NIP-65 read set
(in prod often ~20 relays, most dead). The flood of failing connections
churned the main thread and starved the nip-07 extension's postMessage
round-trips, so encrypted lists (blocks, subscriptions, hashtags) were
fetched but timed out during decryption ("message channel closed",
"Decryption timed out").

Bound every read/subscribe path to MAX_SUBSCRIBE_RELAYS=8 via a shared,
test-aware capReadRelays helper, applied at the chokepoints:
- client.js applyRelayPreferences (this.relays / this.readRelays)
- relayBatchFetcher.js fetchListIncrementally (blocks, subs, hashtags)
Writes stay uncapped (publish reach preserved).

capReadRelays is user-relays-first (a user's own relays are where their
data authoritatively lives, and small fast-path limits would otherwise
crowd them out — the regression that reverted the first attempt) while
reserving RESERVED_DEFAULT_RELAY_SLOTS=2 for reliable default aggregators
so the "~20 personal relays, all dead" case still reaches a live relay.
Under test the reserved core resolves to the test-relay override, keeping
harnesses isolated.

Tests: new relay-subscribe-cap coverage (bounded + reserved-defaults +
writes-uncapped); spec-corrected user-blocks, subscriptions-manager and
nostr/client fetch tests to bounded (not exact) relay fan-out while
keeping all behavioral/security assertions (per-relay coverage, correct
decrypt, block hydration, dedup) intact. See TEST_INTEGRITY.md and
KNOWN_BUGS #0/#4. Full unit + dm:unit suites green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 22:51:36 -04:00
thePR0M3TH3ANandClaude Opus 4.8 49b7645f80 fix(decrypt): bump hashtag+subscription decrypt timeout 6s->15s; document lists-decrypt root cause
Real-env console shows the encrypted lists are FETCHED but DECRYPTION fails on
cold login: hashtags time out at 6s, blocks report signerStatus 'missing', DMs
throw 'helpers unavailable'. Root cause: a relay-connection storm to the user's
~20 dead NIP-65 relays (subsystems use uncapped relayManager read+write) starves
the nip-07 extension. Bumping the decrypt timeout to 15s (matching blocks) is a
partial mitigation for hashtags/subscriptions. The proper fix (bound reads to a
liveness-ranked healthy core incl. defaults + DM decrypt retry) is documented in
KNOWN_BUGS #0 — a first capReadRelays attempt was reverted because capping block
reads broke the security-critical user-blocks integration test.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 22:12:21 -04:00
thePR0M3TH3ANandClaude Opus 4.8 c0a8636678 feat(feed): make For You inclusive + ranked by your lists (G1-G5)
The For You feed was chronological, hashtag-interest-EXCLUSIVE, and ignored
follows entirely (docs/feed-algo-audit.md). Now it is inclusive + ranked:

- createForYouScorerStage scores each video: follows-boost (author you follow)
  + interest/watch-topic affinity (cosine vs your interests+watch-history vector)
  + freshness - disinterest penalty. Reuses Explore's vector/similarity helpers.
- createForYouScoreSorter ranks by that score (muted last, recency tiebreak).
- For You pipeline: interests now BOOST not exclude (enforceInterests:false);
  scorer added; chronological sorter replaced. Never empty (falls back to
  recency when you have no follows/interests/history).
- buildForYouFeedRuntime now feeds subscriptionAuthors + watchHistoryTagCounts.

Watched/blocked/muted are still removed (suppression/blacklist/moderation
stages unchanged). Cheat-resistant tests (older boosted items only win via the
boost). Grid renders end-to-end (~1.3s). Weight tuning is a follow-up.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 16:57:05 -04:00
thePR0M3TH3AN 44bf88cf88 docs: audit For You feed algo <-> lists wiring (read-only, gaps + fix order)
Traced registerForYouFeed/buildForYouFeedRuntime + feedEngine. Confirms hashtag
prefs, blocks, watch-history suppression, and moderation DO drive For You. Gaps:
G1 follows have zero effect on For You; G2 chronological not relevance-ranked;
G3 interest filter is binary/exclusive; G4 watch-topic affinity unused; G5 no
blending. No code changed.
2026-06-14 16:43:49 -04:00
thePR0M3TH3ANandClaude Opus 4.8 9c3ee01755 fix(feed): reload only when the block set changes (P5; KNOWN_BUGS #2)
handleBlocksLoaded reloaded the feed on every 'blocks-loaded' signal — but a
forced reload re-fetches the lists, which re-emit 'blocks-loaded', a
self-sustaining loop. Now it computes a signature of the block set and reloads
only on actual change (emit-on-change — the P5 principle). handleRelaysLoaded
only touches relay UI; hashtag prefs already dedup by signature.

Cheat-resistant regression test added. Bumped authSessionCoordinator file-size
baseline. Logged 2 pre-existing app test failures (fail at pre-session baseline)
in KNOWN_BUGS. Marks KNOWN_BUGS #2 resolved.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-14 16:25:42 -04:00
thePR0M3TH3AN e528a17975 docs: mark P4 (lists via L1) complete 2026-06-14 16:15:31 -04:00