Four issues caused login to be extremely slow and lists to fail syncing:
1. Subscription list decryption excluded NIP-04 when NIP-44 was available,
preventing decryption of NIP-04-encrypted lists entirely. Removed the
allowNip04 gate so NIP-04 is always a fallback (matching userBlocks.js
and hashtagPreferencesService.js which already worked correctly).
2. When the NIP-07 extension permission pre-grant failed (common on page
refresh when the extension injects slowly), all list services were called
with allowPermissionPrompt:false and could never recover. Now list
services are allowed to prompt for permissions themselves when the
pre-grant fails, avoiding 6-10 second retry delays.
3. The profile modal storage tab was never refreshed on login or logout,
so it stayed locked even after authentication succeeded. Added
populateStoragePane() calls to handleAuthLogin and handleAuthLogout.
4. The watch-history feed engine.run() call was missing await, so rejected
promises from unregistered feeds escaped the try/catch and propagated
as unhandled errors instead of falling through to the service fallback.
https://claude.ai/code/session_013puYzbf1cC7a59HLcGQUdJ
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
Three performance bottlenecks were causing slow list loading after NIP-07 login:
1. Each list service (blocks, subscriptions, hashtag preferences) maintained
its own isolated decryptionSuccessCache. When blocks decrypted with nip44_v2,
subscriptions and hashtag preferences had no way to know — they each had to
independently probe all schemes through the NIP-07 extension queue, tripling
the number of extension calls during the critical login path.
2. List loading was gated on both relaysReadyPromise AND permissionPromise in
the coordinator. Since the NIP-07 extension already grants decrypt permissions
during loginWithExtension(), the redundant pre-grant was adding 2-5 seconds
of unnecessary latency before any list fetching could begin.
3. The DECRYPT_RETRY_DELAY_MS was 10 seconds across all services. When a decrypt
attempt failed transiently (extension still initializing), the retry waited
far too long before trying again.
Changes:
- Create shared decryptionSchemeCache module used by all three list services
- Add fast-path: when shared cache knows the correct scheme, try ONLY that
scheme before falling back to parallel Promise.any() probe
- Remove permissionPromise from the list-loading gate in authSessionCoordinator
- Reduce DECRYPT_RETRY_DELAY_MS from 10s to 3s in all three services
- Clear shared cache on logout
https://claude.ai/code/session_013QrGMnxtMTsGTPqaP35jMj
When hashtag preferences decryption fails with "permission-required" during
background loading (allowPermissionPrompt=false), the service silently returned
without setting loaded state, scheduling a retry, or emitting a change event.
This left the UI stuck showing empty hashtag lists. The subscriptions module
already handled this case correctly.
Also fix error scheme mapping in Promise.any() catch blocks for both hashtag
preferences and subscriptions — the index into the original order array was
wrong after filtering out deduplicated scheme families.
https://claude.ai/code/session_016xYxLQ1U2XVVY5rFuCRKfc
Login with NIP-07 extensions was extremely slow because list loading
(blocks, subscriptions, hashtag preferences) happened sequentially and
each list's decryption tried encryption schemes one at a time.
Changes:
- Load blocks, subscriptions, and hashtags in parallel instead of
sequentially (blocks no longer blocks other lists from starting)
- Use Promise.any() to try all decryption schemes simultaneously
instead of trying nip44_v2 → nip44 → nip04 one-by-one
- Deduplicate same-family schemes (nip44/nip44_v2) to avoid redundant
extension calls
- Increase NIP-07 request queue concurrency from 3 to 5 to support
parallel decrypt operations during login
- Raise no-prompt decrypt timeout from 3s/5s to 8s since extensions
that have already granted permission still need time to decrypt
- Increase feed sync timeout from 8s to 12s to give lists more time
to settle before rendering unfiltered content
https://claude.ai/code/session_01V6m1gTecsppPrBHQnUyPnF
- 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
The user's block/mute list (NIP-51) was not being checked when
processing direct messages, allowing blocked users' messages to appear
in conversations, trigger notifications, and increment unseen counts.
This adds block filtering at four layers:
- nostrService.applyDirectMessage: drops incoming DMs from blocked users
before they enter the in-memory message cache
- nostrService.persistDirectMessageRecord: prevents blocked DM senders
from being persisted to IndexedDB or triggering notifications
- profileModalController.buildDmConversationData: filters blocked users
from the conversation list UI (catches pre-block persisted messages)
- applicationBootstrap: excludes blocked-user conversations from the
unread DM badge count
The block checker is injected via setDmBlockChecker() to keep
nostrService decoupled from userBlocks, wired up in app.js init().
https://claude.ai/code/session_01EAVLtAM14GN99Eik8otLg2
The subscription list was failing to load correctly during login because:
1. Permission-required decryption errors silently returned without setting
loaded=true or scheduling a retry, leaving subscriptions permanently
stuck in a loading state.
2. Generic decryption failures (all schemes exhausted) had no retry path,
unlike timeout errors which already scheduled retries.
3. The ensureLoaded timeout (30s) was too tight for the worst-case
pipeline of relay fetch (12s) + decryption (20s) = 32s, causing
premature timeouts that left loaded=false.
4. The loadingPromise was cleared in a finally block even when the loader
was still in-flight after a timeout, allowing duplicate concurrent
loads that could race against each other.
Changes:
- Handle permission-required errors like timeout errors: set loaded=true,
schedule a retry (with allowPermissionPrompt: true), emit change event
- Schedule decrypt retries for all decrypt failure paths
- Increase ensureLoaded timeout from 30s to 45s
- Mark loaded=true on ensureLoaded timeout so the UI can proceed
- Clean up loadingPromise via loader.then() instead of finally block,
preventing duplicate loads when the first is still in-flight
https://claude.ai/code/session_015MhPiSWNa7dm4UaRuq5x4U
The login-to-feed pipeline had several issues causing lists (blocks,
subscriptions, hashtag preferences) to load too slowly or not reflect
in the feed:
1. All list loading was chained after profileStatePromise, adding
2.5-6s of unnecessary delay. Lists only need relay URLs, not profile
data. Now they chain off relaysReadyPromise instead, loading in
parallel with profile fetching.
2. The video grid refreshed immediately without waiting for any lists,
so the feed briefly displayed unfiltered content. Now the grid refresh
waits (up to 8s) for critical feed-filtering lists to settle.
3. Hashtag preferences weren't cached into the snapshot before the feed
rendered, so interests/disinterests were empty on first paint.
4. Subscription ensureLoaded had a 6s timeout that was too aggressive
for relay fetch (12s) + decryption (3-12s), causing silent failures.
Increased to 30s.
5. Subscription relay fetches for user and session actor ran sequentially.
Now they run in parallel via Promise.all.
https://claude.ai/code/session_01UKmTWb83AnQPRVQo3bKZqm