Real-environment console logs revealed nip-07 login pegging the CPU and
starving encrypted-list decryption via relay REQ storms (~1000 REQ/s):
- moderationService.rebuildTrustedContacts emitted "contacts"
unconditionally, forming an emit->fetch->emit loop (~1600 kind-3 REQ/s).
Now emits only on actual change. Regression test added.
- Per-video live report (kind 1984) and view-count (kind 30079)
subscriptions fan out O(videos x relays) and re-fire on every flaky-relay
reconnect. Circuit-broken behind flags until batched (see refactor plan).
- onVideosShouldRefresh now coalesces forced reloads (2s leading-edge
throttle) so list-change signals can't drive a reload loop.
- nip-07 queue reserves one foreground slot; DM decryption demoted to LOW
so background DM backfill can't starve feed-driving lists. Test added.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
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]>
This commit replaces the duplicated d-tag extraction logic in `extractDTagValue` method of `js/app.js` with the standard utility function `getDTagValueFromTags` from `js/nostr/nip71.js`. This improves code reuse and maintainability.
The fallback logic for `extractDTagValue` now delegates to `getDTagValueFromTags` if `similarContentController` is not present, ensuring consistent behavior.
Verified with unit tests and linting.
Co-authored-by: PR0M3TH3AN <[email protected]>
- Move `initializeUserInterface()` earlier in `app.init()` to ensure button listeners (like Upload) are bound before potentially slow network operations (Access Control, Relay connection) block or delay responsiveness.
- Make `UploadModal.open()` async and await `load()` to ensure the DOM is ready before removing the `hidden` class.
- Stabilize `notification-system.spec.ts` by adding a wait for app settlement.
- Update visual test baselines.
Co-authored-by: PR0M3TH3AN <[email protected]>
- Updates `.github/workflows/ci.yml` to install `firefox` for the `e2e-headless` job, resolving the "Executable doesn't exist" error.
- Updates `js/app.js` to make `_initAccessControl` async (returning a Promise) and adds a 15-second timeout safeguard. This ensures the app waits for ACLs to load (fixing the race condition) but doesn't hang indefinitely if the network is slow or unresponsive (fixing E2E timeouts).
Co-authored-by: PR0M3TH3AN <[email protected]>
Extracts video modal event handling logic from `js/app.js` to
`js/ui/videoModalController.js`.
- Moves `_bindVideoModalEvents` logic to `VideoModalController.bindEvents`.
- Adds unit tests for the controller.
- Follows AGENTS.md refactor guidelines.
Co-authored-by: PR0M3TH3AN <[email protected]>
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
- Implemented `hydrateVideoHistoryBatch` in `NostrClient` to fetch missing root events and sparse histories in a single `pool.list` request.
- Refactored `hydrateVideoHistory` to use shared helper methods `getLocalVideoHistory` and `_processAndCacheEvent`.
- Added `resolveVideoPostedAtBatch` to `PlaybackCoordinator` which delegates to the batched client method.
- Updated `VideoListView` to collect videos needing timestamp resolution and call the batch method, falling back to individual calls if necessary.
- Added regression test `tests/performance/resolvePostedAt.test.mjs` to verify reduced network calls.
Co-authored-by: PR0M3TH3AN <[email protected]>
Added a new configuration variable `ENABLE_NIP17_RELAY_WARNING` to `config/instance-config.js` (defaulting to `false`) to control the display of NIP-17 relay fallback warnings. Updated `js/ui/profileModalController.js` and `js/app.js` to respect this setting.
This change allows instance operators to disable "Privacy warning: direct messages are using your default relays because no NIP-17 relay list is available" and related notifications.
Co-authored-by: PR0M3TH3AN <[email protected]>
- Introduces a `setup()` method in `Application` class to handle complex initialization and side effects.
- Updates `constructor` to be lightweight, only storing configuration options.
- Updates `js/bootstrap.js`, `js/embed.js`, and relevant tests to call `app.setup()` explicitly after instantiation.
- Resolves "Heavy Constructor" code health issue.
- Fixes `tests/video-modal-zap.test.mjs` environment issue with `globalThis.navigator`.
Co-authored-by: PR0M3TH3AN <[email protected]>
Replaced the explicit `typeof` check for `viewLoader` with a simpler assignment using the logical OR operator (`|| null`). This removes excessive defensive coding while preserving the behavior of defaulting to `null` when the argument is undefined or falsy.
Verified with a reproduction test case that confirms `loadView` is correctly assigned or defaulted to `null`.
Co-authored-by: PR0M3TH3AN <[email protected]>
The NIP-07 login flow was extremely slow because list decryption (blocks,
subscriptions, hashtag preferences) started before the signer was ready.
On page refresh, all three services failed with "permission-required" and
scheduled 3-second retry delays, adding 6-10 seconds to every login.
Key fixes:
- List loading now waits for the permission pre-grant to complete before
attempting decryption, ensuring the signer is available on first try
- Extension wait timeout increased from 3s to 5s for slower extensions
- Decrypt timeouts reduced from 20s to 10s (signer is guaranteed ready)
- Auto-login now forwards authType/providerId from saved profiles
- Storage tab unlock falls back to ensureActiveSignerForPubkey when the
global signer is not yet registered
- Watch history feed registration adds defensive re-registration and
try/catch fallback to prevent "Feed not registered" errors
- Decryption scheme cache TTL extended from 10 minutes to 2 hours
- ensureExtensionPermissions waits for extension injection when there
are outstanding permissions to request (was returning immediately)
https://claude.ai/code/session_01T4wwPu7m5qp1Nxx3H2v57j
Removed a duplicate/malformed code block in `js/app.js` that was causing `SyntaxError: Unexpected token '.'` during module loading. This error was breaking tests in `tests/app/channel-profile-moderation.test.mjs` and others.
Also restored `_initNostr` and `_initSessionActor` methods which were partially clobbered or malformed in the file structure.
Verified by running the affected unit tests locally.
Co-authored-by: PR0M3TH3AN <[email protected]>
The `feedCoordinator` logic assumes `this.refreshFeed` exists on the Application instance (because methods are bound via `bindCoordinator`). However, `Application` previously only implemented `refreshRecentFeed`, `refreshForYouFeed`, etc., but not the generic `refreshFeed`.
This caused a `TypeError: this.refreshFeed is not a function` when `loadFeedVideos` (which calls `refreshFeed`) was invoked.
This commit adds `async refreshFeed(...args)` to `Application` in `js/app.js`, delegating to `this._feed.refreshFeed(...args)`.
Co-authored-by: PR0M3TH3AN <[email protected]>
- Removed orphaned code block in `js/app.js` that caused syntax errors during module loading.
- Updated `tests/helpers/moderation-test-helpers.mjs` to implement `resumePendingModeratedPlayback` and decoration logic in `MockApplication`, fixing `tests/app/moderation-overrides.test.mjs` and `tests/app/moderation-settings-refresh.test.mjs` failures.
Co-authored-by: PR0M3TH3AN <[email protected]>
- Restored missing `_initNostr` method in `js/app.js` which was causing `SyntaxError: Unexpected token '.'` and `nostrClient.init is not a function` errors.
- Removed dangling code block that was causing the syntax error (orphaned `accessControl.refresh()...` block).
- This fixes unit tests (e.g. `tests/app/channel-profile-moderation.test.mjs`) and application initialization.
Co-authored-by: PR0M3TH3AN <[email protected]>
Removes a malformed code block that was causing an "Unexpected token '.'" syntax error and restores the `_initNostr` method which correctly initializes the Nostr client.
Fixes: #1083 (SyntaxError in app.js)
Co-authored-by: PR0M3TH3AN <[email protected]>
- Extracted logic into `_init*` private helper methods.
- Preserved execution order and async behavior.
- Cleaned up the main `init` method to act as an orchestrator.
Co-authored-by: PR0M3TH3AN <[email protected]>
- Unify `refreshForYouFeed`, `refreshKidsFeed`, `refreshExploreFeed`, and `refreshRecentFeed` into `refreshFeed(feedType, options)`.
- Update `js/app/feedCoordinator.js` to implement `refreshFeed` and use it in `loadFeedVideos`.
- Update `js/app.js` to delegate specific methods to `refreshFeed`.
- Add `FEED_TYPES` import to `js/app.js`.
- Update `tests/app/feedCoordinator.test.mjs` to match the refactoring.
Co-authored-by: PR0M3TH3AN <[email protected]>
Removes commented-out `await Promise.all` and refactors the associated
`accessControlPromise` and `adminPanePromise` variables in `js/app.js`.
The promises were only assigned for use in the commented-out line.
This change:
- Converts `accessControl.refresh()` to a direct expression statement.
- Simplifies `adminPanePromise` logic into a direct `if` block, removing
the unnecessary `else { Promise.resolve(null) }` branch.
- Preserves the existing fire-and-forget behavior for these initialization tasks.
Co-authored-by: PR0M3TH3AN <[email protected]>
The `loadFeedVideos` method was missing from the `Application` class in `js/app.js`, but was being called by `loadVideos` inside the feed coordinator (which is bound to the application instance). This caused a `TypeError: this.loadFeedVideos is not a function` at runtime.
This change adds the missing delegation method to `Application` in `js/app.js`, forwarding the call to `this._feed.loadFeedVideos`.
Co-authored-by: PR0M3TH3AN <[email protected]>
Replaces the sequential loop in `forceRefreshAllProfiles` with `batchFetchProfiles` to reduce network requests.
Updates `batchFetchProfilesFromRelays` to support `forceRefresh`, ensuring fresh data is requested while maintaining optimistic UI updates.
Adds unit tests for `forceRefresh` behavior.
Co-authored-by: PR0M3TH3AN <[email protected]>
Moves the repeatedly defined HEX64_REGEX constant to a shared utility
module to improve maintainability and consistency. Updates all consumers
to import from the new location.
Signed-off-by: Jules <[email protected]>
Co-authored-by: PR0M3TH3AN <[email protected]>
Removes the unused `MAX_WALLET_DEFAULT_ZAP` import from `js/app.js` to improve code health and reduce confusion. This variable was imported from `config.js` but never used within `app.js`.
Verification:
- Confirmed `MAX_WALLET_DEFAULT_ZAP` is not present in `js/app.js`.
- Ran `tests/app/is-user-logged-in.test.mjs` to ensure `js/app.js` still loads and functions correctly.
Co-authored-by: PR0M3TH3AN <[email protected]>
- Remove undefined hashtagPreferencesPromise reference in handleAuthLogin
that caused the feed sync Promise.allSettled to silently fail, preventing
proper list/profile synchronization before grid refresh
- Add missing getModerationSettings and getActiveProfilePubkey dependency
injection to authSessionCoordinator, fixing crashes during logout
moderation reset and profile switch pubkey resolution
- Parallelize NIP-07 permission request with relay readiness instead of
gating list loading behind it; parallelize subscriptions and hashtag
preference loading (independent of each other, only blocks must sequence)
- Add native run() alias to feedEngine so callers don't rely solely on
the applicationBootstrap compatibility shim
- Remove redundant refreshVisibleModerationUi call from renderVideoList
that re-decorated every video immediately after decoration
- Avoid re-normalizing moderation settings on every getActiveModerationThresholds
call when settings are already an object
https://claude.ai/code/session_01FCosieUXjEn95KtUeyGemY
Move cohesive method groups out of the monolithic Application class
(js/app.js, ~10K lines) into five coordinator modules under js/app/:
- feedCoordinator: feed registration, runtime building, refresh,
video loading, URL-health helpers, and feed telemetry
- playbackCoordinator: URL-first + magnet fallback pipeline, probing,
torrent status intervals, and video timestamp resolution
- authSessionCoordinator: signer lifecycle, profile switching,
relay/block/wallet management, and cleanup
- modalCoordinator: modal open/close, view-count subscriptions,
watch-history metadata, and playback logging
- moderationCoordinator: moderation settings, video decoration,
override/block/hide action routing
Each coordinator factory receives injected dependencies instead of
importing globals. Application retains thin delegator methods so all
public call-sites remain unchanged. Coordinator initialisation is
idempotent (via _initCoordinators) and supports both constructor-based
and Object.create()-based instantiation used by test harnesses.
All existing unit tests, lint checks, and syntax validation pass.
https://claude.ai/code/session_01C3PKJTCdavsGbk5jjC85EB