Commit Graph
53 Commits
Author SHA1 Message Date
thePR0M3TH3ANandClaude Opus 4.8 48b6f10e0d fix(watch-history): record new watches under the logged-in identity, not the anonymous session actor
View events are always signed by the anonymous session actor (privacy for the
public view counter). publishView read the watched-actor from that view event's
pubkey, so a logged-in user's new watches were enqueued — and republished via
updateWatchHistoryList — under the SESSION actor. The read path queries
authors:[loggedIn] only, so those session-authored months were never
discovered: newly watched videos never appeared in the synced watch history
(e.g. a 2026-06 month that never showed up despite watching June videos).

Fix:
- publishView now resolves the watch-history actor to the logged-in pubkey
  first, falling back to the view event / session actor only when logged out.
- A watch is flagged session/local-only only when nobody is logged in
  (previously it was flagged whenever the view actor differed from the
  logged-in user, which was always true given anonymized views).

Adds tests/watch-history-records-under-login.test.mjs (mutation-verified: both
the actor-resolution and the session-flag fixes are independently load-bearing).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-21 10:55:27 -04:00
thePR0M3TH3ANandClaude Opus 4.8 2f95d4486e fix(watch-history): logged-out (local) delete now rewrites the local queue
The other half of the synced-vs-local design: for logged-out / session
(local-only) actors, watch history lives in a localStorage queue (loadLatest ->
collectQueueItems), but snapshot no-op'd for local-only actors — so a removal
never touched the queue and the item reappeared on reload (same symptom as the
logged-in bug, different layer).

Now a REPLACE snapshot for a local-only actor rewrites the local queue to exactly
the provided items (new replaceLocalQueue helper): removals prune it, an empty
replace clears it. Normal watch-tracking adds still flow through the queue via
recordVideoView, unchanged.

Tests + mutation-verified: local replace seeds then prunes the queue; empty
replace clears it. Reverting to the no-op fails both.

Build + full lint green (watchHistoryService under file-size budget).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-20 18:45:42 -04:00
thePR0M3TH3ANandClaude Opus 4.8 5f19f53670 fix(watch-history): removal must replace the list, not merge it back
Root cause of "watch history delete does not work at all": the removal paths
call watchHistoryService.snapshot(remaining, …), but snapshot published via
updateWatchHistoryList WITHOUT replace:true. The default merges incoming items
with the existing cached list, so the just-removed item was merged straight back
and the deletion silently had no effect. Removing the LAST item was worse: an
empty list fell through to republishing the pending queue instead of clearing.

- snapshot now honors options.replace: forwards it to updateWatchHistoryList,
  treats an explicit empty replace as a clear (publishes empty instead of
  short-circuiting or falling back to the queue), and skips the cached-items
  merge when replacing. Adds (the default) keep merge semantics.
- Both removal call sites (watchHistoryController.handleWatchHistoryRemoval and
  historyView.defaultRemoveHandler) now pass replace:true.

Tests (snapshot at the updateWatchHistoryList boundary): removing an item
publishes the remaining list with replace:true; removing the last item publishes
an empty replace (not a queue republish / no-op); a normal watch-tracking
snapshot still merges (replace not forced). Mutation-verified — dropping the
replace forwarding fails 2 cases and reverting the empty-clear handling fails the
clear case.

Build + full lint green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-20 12:52:15 -04:00
thePR0M3TH3ANandClaude Opus 4.8 37a675392f refactor(watch-history): extract actor helpers from watchHistoryService (4/10)
File-size decomposition (no behavior change). Move getLoggedInActorKey,
getSessionActorKey, and ensureWatchHistoryExtensionPermissions (which depend only
on imported singletons, not module state) into js/watchHistoryActorHelpers.js, and
drop the now-unused getActiveSigner/requestDefaultExtensionPermissions/getApplication
imports from the service.

watchHistoryService.js: 1491 -> 1388 lines (below its original 1395 baseline;
re-recorded to 1386 to lock the reduction).

Build + full unit suite green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-19 11:33:35 -04:00
thePR0M3TH3ANandClaude Opus 4.8 70ce597a86 [watch-history] activate fast cold-load render + background chunk backfill
Slice B part 2 (final). On a cold watch-history load (nothing cached to
show yet) the service now decrypts only the newest chunks first
(chunkDecryptLimit = 3) so the UI renders recent history immediately
instead of freezing behind a deep, monthly-chunked history — the original
NIP-46 symptom. If older chunks were deferred, a single background pass
then backfills them (no chunk cap; already-decrypted chunks are served
from cache, so only the deferred older chunks cost signer RPCs) and emits
"fingerprint", which the history view already re-renders on. The serial
NIP-46 request queue paces the backfill, so there is no rate-limit flood.

Scoped to cold loads only: a warm refresh (items already on screen)
resolves the full history directly, avoiding a transient "history shrank
then grew" flicker. No view / feed-engine changes and no change to the
shared For You / explore paths beyond a fast first render then a fill-in.

Chosen over strict scroll-triggered lazy loading, which would have
required risky surgery on the history view's feed-engine + cursor
pagination and the shared loadLatest path pre-launch.

Tests: cold load passes a finite chunk limit on the first resolve, then an
unbounded background backfill runs and emits recent-only -> full; warm
refresh is unbounded. Both mutation-verified (removing the bound and
no-op'ing the backfill each fail).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-17 18:23:10 -04:00
thePR0M3TH3ANandClaude Opus 4.8 915b77b740 fix(watch-history): stop infinite feed-refresh loop pegging the CPU
updateFingerprintCache (and the fingerprint getter) emitted "fingerprint"
unconditionally on every load. The For You feed subscribes to that event and
re-runs refreshFeed -> loadLatest -> updateFingerprintCache -> emit, so once the
For You feed was active the app entered an infinite loop: a full feed rebuild
plus a watch-history reload (with extension decrypts) every ~400ms. That pegged
the CPU (fans maxing) and starved every other load — videos, hashtags, subs,
DMs and watch history all failed to appear.

Emit "fingerprint" only when the fingerprint actually changes. The first real
resolution emits once (triggering a single refresh); the follow-up load produces
the same fingerprint and emits nothing, so the loop settles immediately.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-13 07:57:04 -04:00
thePR0M3TH3AN 9dddda0326 Revert "perf(watch-history): decrypt only recent months on load (cap cold cost)"
This reverts commit b5ad582b6c.
2026-06-12 21:21:16 -04:00
thePR0M3TH3ANandClaude Opus 4.8 b5ad582b6c perf(watch-history): decrypt only recent months on load (cap cold cost)
Root cause of the ~20s watch-history load: history is bucketed by month (one
encrypted snapshot event per month) and the fetch decrypted EVERY month via the
serialized nip-07 extension — ~1 slow call per month, so accounts with years of
history paid dozens of back-to-back extension decrypts on every load.

Cap the load to the most recent RECENT_MONTHS_DECRYPT_LIMIT (6) months by default
— enough for the feed's watched-video suppression and the initial view. Callers
that need the full history (the explicit History view) pass recentMonthsLimit: 0,
threaded loadLatest -> scheduleWatchHistoryRefresh -> resolveWatchHistory ->
resolve -> fetch.

Harness (fresh-context wh-repro): 600 items / 18 months goes from 18 nip-07
decrypt calls to 6. Watch-history tests green. Bumps the watchHistory.js
file-size baseline for the added logic.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 21:11:40 -04:00
thePR0M3TH3ANandClaude Opus 4.8 9b12a282af perf(watch-history): show persisted history instantly on reload
After a page reload the in-memory caches (service fingerprintCache and the
client watch-history cache) are empty, so loadLatest's non-blocking path returned
[] and the For You feed suppressed nothing until the slow background network
refresh (~150 encrypted chunk events over 2 sequential relay round-trips) landed.

Return the best available items immediately via getCachedSnapshotItems, which
falls back to the persisted localStorage snapshot. Reloads now suppress watched
videos right away using the last-known history, while the background refresh
still runs and corrects it (firing "fingerprint" to re-run the feed). Persisted
items were previously valid, so there is no correctness risk.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 13:47:27 -04:00
thePR0M3TH3ANandClaude Opus 4.8 52ceb4f211 perf(watch-history): load in background; re-run For You when it resolves
The For You feed awaited watchHistoryService.loadLatest() before rendering, and
loadLatest blocked on the full ~1500-item watch-history fetch on a cold load even
with allowStale:true (the `!allowStale || !hasCachedItems` guard awaited whenever
there was no cache). So the main feed froze behind the entire watch-history
resolution, and nothing re-ran the feed once history finished loading — watched
videos were never suppressed and watch-history tags never applied until a manual
refresh.

- watchHistoryService.loadLatest: allowStale callers now NEVER block. On a cold
  load the refresh is kicked off in the background and the call returns
  immediately with whatever is available (cached items, else the local queue).
- feedCoordinator.subscribeWatchHistoryFeedRefresh: subscribes to the
  "fingerprint" event and, when the For You feed is active, debounced-re-runs it
  so newly-resolved watched videos are suppressed and tags applied. Wired in at
  bootstrap alongside feed registration.

The re-run reads the now-fresh cache (no further refresh scheduled), so there is
no loop. Adds feedCoordinatorWatchHistoryRefresh.test.mjs (debounced single
refresh while active; no refresh when inactive; idempotent subscription).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-12 11:44:01 -04:00
thePR0M3TH3AN 244f5f80a8 Merge pull request #2367 from PR0M3TH3AN/claude/fix-sync-and-dm-issues-NMShC
Fix NIP-07 adapter support and improve signer resolution
2026-02-11 15:01:35 -05:00
Claude 53b8a2609c fix: recognize NIP-07 signer type for extension permission pre-granting
The NIP-07 adapter uses type "nip07" but all permission pre-granting
checks only matched type "extension", causing NIP-07 browser extension
users to fail decryption for block/mute lists, subscriptions, DMs, and
watch history. Hashtag preferences appeared to work because their read
path had a separate fix, masking the systemic issue.

Changes:
- shouldRequestExtensionPermissions() now matches both "extension" and
  "nip07" signer types (central fix affecting DM signals, comments,
  publish helpers, and watch history fetch)
- buildDmDecryptContext() pre-grants extension permissions for NIP-07
- userBlocks.js: remove capabilities-gated decryptor check that failed
  with lazy NIP-07 module injection (same fix already in subscriptions.js);
  always resolve signer regardless of allowPermissionPrompt; increase
  fast-path relay timeout from 6s to 10s
- watchHistoryService: recognize NIP-07 type and always resolve signer
- All publish paths (blocks, subscriptions, hashtags, relays, uploads,
  sharing, moderation) now pre-grant permissions for NIP-07 signers

https://claude.ai/code/session_01PipsdaTQmw7wBTJ1LmJjkk
2026-02-11 19:41:53 +00:00
google-labs-jules[bot]andPR0M3TH3AN c47392aba6 Refactor numeric constants to centralized module
- Introduce `js/constants.js` with semantic time constants (e.g., `ONE_MINUTE_MS`, `STANDARD_TIMEOUT_MS`) and feature-specific TTLs.
- Export `KIND_MUTE_LIST` from `js/nostrEventSchemas.js` to standardize NIP-51 mute list kind usage.
- Refactor `js/state/cache.js`, `js/nostr/cachePolicies.js`, `js/channelProfile.js`, and various services to use centralized constants instead of hardcoded magic numbers.
- Update `js/nostr/relayConstants.js` to derive values from the shared time constants.
- Replace duplicate definitions of timeouts and retry delays across the codebase.

Co-authored-by: PR0M3TH3AN <[email protected]>
2026-02-11 03:27:26 +00:00
google-labs-jules[bot]andPR0M3TH3AN eeb3283344 fix(ci): prevent signer hangs in tests and background services
Co-authored-by: PR0M3TH3AN <[email protected]>
2026-01-31 08:14:53 +00:00
google-labs-jules[bot] a484fb8f36 improve watch history reliability
- Reduced view threshold from 12s to 5s in watchHistoryTelemetry.js
- Enabled immediate watch history republishing (0ms delay) for the first attempt in watchHistory.js
- Updated watchHistoryService.js to recursively schedule queue flushes if items accumulate during an inflight sync
2026-01-21 22:07:23 +00:00
thePR0M3TH3AN 52d2488d17 Refactor nostr signer access 2026-01-18 09:15:18 -05:00
thePR0M3TH3AN 3a95e5ea87 Merge queued watch history items during sync 2026-01-09 16:00:48 -05:00
thePR0M3TH3AN 922951d74c Add session actor fallback for watch history 2026-01-09 15:53:26 -05:00
google-labs-jules[bot] c322fcee14 fix(watch-history): add manual refresh and fix dynamic pulling
This commit addresses issues with the watch history not updating dynamically and adds a manual refresh mechanism.

Changes:
- Added a "Refresh" button to the watch history view in `views/history.html` and `js/historyView.js`.
- Updated `watchHistoryService.js` to accept a `forceRefresh` option in `loadLatest`, which bypasses the cache freshness check and triggers a relay fetch.
- Updated `js/feedEngine/watchHistoryFeed.js` to propagate the `forceRefresh` flag from the runtime context to the service.
- Added user feedback (toast messages and logs) for "Clear Local History" and "Republish Now" actions.
- Updated `tests/watch-history.test.mjs` to align with the new `fetchHistory` signature.
2026-01-07 14:05:21 +00:00
google-labs-jules[bot] 873b534867 fix(watch-history): use localStorage for pending video queue
Replace sessionStorage with localStorage to ensure pending watch history updates persist across tab closures and sessions. Update `loadLatest` to always restore queue state on initialization to catch any pending uploads.
2026-01-07 00:11:56 +00:00
thePR0M3TH3AN 891226697d Simplify watch history pipeline 2026-01-06 18:53:27 -05:00
google-labs-jules[bot] eff9dfed85 Fix watch history sync for logged-in users without NIP-04 permissions.
Updates `shouldUseExtensionForHistory` in `js/watchHistoryService.js` to check for `signEvent` instead of `nip04.decrypt`. This ensures that logged-in users can sync their watch history (which is now plaintext) even if they haven't granted the legacy encryption permission.
2026-01-06 18:38:21 +00:00
google-labs-jules[bot] 89ce081453 Update history view copy to reflect event ID-only tracking
- Rename "Clear local cache" button to "Clear local history"
- Update info text to remove references to hydrating from session/caches
- Simplify copy to remove "encrypted" references in favor of "syncing" or "history"
- Update success/error messages to match new terminology
2026-01-05 19:17:51 +00:00
google-labs-jules[bot] 4c7230f540 Remove metadata storage and handling from watch history
Removes the metadata argument from `WatchHistoryService.publishView` and cleans up the associated caching and resolution logic in `watchHistory.js` and `watchHistoryFeed.js`. Deletes `js/watchHistoryMetadata.js` as it is no longer used.

This simplification ensures watch history strictly manages pointers and timestamps, leaving metadata resolution to higher-level consumers. It also reduces the payload size and complexity of persisted history records.

Tests have been updated to reflect the API changes.
2026-01-05 16:58:59 +00:00
google-labs-jules[bot] ac3873e5d7 Refactor watch history service to use idempotent monthly upsert logic.
Removes legacy snapshot/chunk retry scheduling in favor of a monthly upsert approach.
Updates state persistence to remove `pendingSnapshotId`.
Fixes race condition in `scheduleRepublishForQueue` by using selective queue pruning.
Ensures data is not lost on publish failure in `snapshot`.
Updates tests to align with new architecture.
2026-01-05 15:42:48 +00:00
google-labs-jules[bot] e9ba3f6681 Refactor watch history to use plaintext monthly buckets
- Update `js/nostrEventSchemas.js` to remove encryption tagging and update schema for `WATCH_HISTORY`.
- Update `js/nostr/watchHistory.js` to:
  - Bump storage version to 4.
  - Bucket items by `YYYY-MM`.
  - Publish plaintext JSON payloads with only event IDs and timestamps.
  - Remove encryption logic from publishing.
  - Update fetching to handle plaintext and fallback to legacy parsing.
- Update `js/watchHistoryService.js` to strip metadata from queued items and handle new snapshot response.
- Update `tests/watch-history.test.mjs` to reflect new plaintext behavior and disabled obsolete tests.
2026-01-05 15:26:31 +00:00
thePR0M3TH3AN e9ce494ccd Remove watch history metadata caching 2026-01-03 09:11:26 -05:00
thePR0M3TH3AN a4bd71ef78 update 2025-11-19 10:25:01 -05:00
thePR0M3TH3AN 217fe807b7 Handle inflight watch history queue updates 2025-10-26 16:14:26 -04:00
thePR0M3TH3AN 9ed0fae127 refactor: route nostr imports through facades 2025-10-22 22:40:17 -04:00
thePR0M3TH3AN 5536180682 refactor: centralize nostr client bootstrap 2025-10-22 21:09:18 -04:00
thePR0M3TH3AN 75ea73b178 Refactor Nostr client imports 2025-10-22 20:55:54 -04:00
thePR0M3TH3AN a5562b4707 Refactor watch history helpers to new module 2025-10-22 14:08:47 -04:00
thePR0M3TH3AN 7c9db2eeaf Fix watch history snapshot merging 2025-10-20 22:36:19 -04:00
thePR0M3TH3AN 26b9bc9dc7 Handle NIP-07 permission failures when loading watch history 2025-10-15 22:51:52 -04:00
thePR0M3TH3AN 7ae07b1369 refactor: migrate console usage to shared loggers 2025-10-15 14:37:25 -04:00
thePR0M3TH3AN d705cf176b Revert "Refactor logging to centralized logger" 2025-10-15 11:14:10 -04:00
thePR0M3TH3AN 37ba57aa37 Refactor logging to centralized logger 2025-10-15 11:11:14 -04:00
thePR0M3TH3AN 4d367109d5 Preserve playback metadata in watch history 2025-10-09 19:23:29 -04:00
thePR0M3TH3AN 507f09dff5 Preserve video metadata in watch history payloads 2025-10-09 11:31:47 -04:00
thePR0M3TH3AN 46ef2b8123 Ensure watch history loads include full lists by default 2025-10-08 08:11:26 -04:00
thePR0M3TH3AN 10751a8fb9 Improve watch history loading responsiveness 2025-10-08 07:33:44 -04:00
thePR0M3TH3AN cf34e28a47 Refactor app orchestrator into Application with dependency injection 2025-10-03 21:10:22 -04:00
thePR0M3TH3AN 762dbe5dcc Fix watch history sync when logged in 2025-10-02 18:45:44 -04:00
thePR0M3TH3AN c3ed4bc6ea Scope watch history sync to logged users 2025-10-02 18:26:43 -04:00
thePR0M3TH3AN b1a493f113 Enable local watch history fallback when sync disabled 2025-10-02 17:07:21 -04:00
thePR0M3TH3AN 84b70475d9 Ensure watch history logging executes when service available 2025-10-02 16:53:09 -04:00
thePR0M3TH3AN a4eb4f2367 Add detailed logging for watch history flow 2025-10-02 16:44:13 -04:00
thePR0M3TH3AN 041aa58398 Handle partial watch history publish retries 2025-10-02 16:14:20 -04:00
thePR0M3TH3AN 6cfaf38ce1 Gate watch history v2 behind runtime flag 2025-10-02 15:19:55 -04:00