interactive intro to open social at-me.zzstoatzz.io

fix: make URL validation non-blocking and default to valid filter

- Set up identity click handler and filter panel immediately
- Run URL validation in background without blocking UI
- After validation completes, automatically hide invalid apps
- Users can now interact with PDS panel while domains resolve

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+25 -11
+11
src/view/filters.js
··· 111 111 }); 112 112 } 113 113 114 + // Apply the "valid" filter - hide invalid apps, show valid ones 115 + // Called after URL validation completes 116 + export function applyValidFilter() { 117 + if (!state.globalApps) return; 118 + if (state.invalidApps.size === 0) return; // Nothing to filter 119 + 120 + // Hide only invalid apps 121 + state.hiddenApps = new Set(state.invalidApps); 122 + applyFilters(); 123 + } 124 + 114 125 export function initFilterPanel() { 115 126 loadHiddenApps(); 116 127
+1 -1
src/view/main.js
··· 136 136 // Hide status 137 137 statusEl.style.display = 'none'; 138 138 139 - // Render visualization (await to ensure URL validation completes) 139 + // Render visualization (UI is interactive immediately, validation runs in background) 140 140 await renderVisualization(apps, profile); 141 141 142 142 // Initialize UI components
+13 -10
src/view/visualization.js
··· 10 10 validateAppUrls, 11 11 listRecords 12 12 } from './atproto.js'; 13 - import { initFilterPanel, repositionAppCircles } from './filters.js'; 13 + import { initFilterPanel, repositionAppCircles, applyValidFilter } from './filters.js'; 14 14 import { loadMSTStructure } from './mst.js'; 15 15 16 16 export async function renderVisualization(apps, profile) { ··· 98 98 // Add all divs to field 99 99 appDivs.forEach(({ div }) => field.appendChild(div)); 100 100 101 - // Fetch avatars asynchronously 101 + // Set up identity click handler immediately (non-blocking) 102 + setupIdentityClickHandler(allCollections, appCount, profile); 103 + 104 + // Set up filter panel immediately (non-blocking) 105 + initFilterPanel(); 106 + 107 + // Fetch avatars asynchronously (non-blocking) 102 108 fetchAppAvatars(appNames).then(avatarMap => { 103 109 appDivs.forEach(({ div, namespace }) => { 104 110 const avatarUrl = avatarMap[namespace]; ··· 109 115 }); 110 116 }); 111 117 112 - // Validate app URLs (must complete before filter panel setup) 113 - await validateAppUrls(appDivs); 114 - 115 - // Set up identity click handler 116 - setupIdentityClickHandler(allCollections, appCount, profile); 117 - 118 - // Set up filter panel 119 - initFilterPanel(); 118 + // Validate app URLs in background, then apply "valid" filter by default 119 + validateAppUrls(appDivs).then(() => { 120 + // After validation completes, default to hiding invalid apps 121 + applyValidFilter(); 122 + }); 120 123 121 124 // Handle window resize 122 125 let resizeTimeout;