import { type Component, createSignal, onMount, onCleanup, createMemo, createEffect, lazy, Suspense, Show, untrack } from 'solid-js'; import { Layout } from './components/Layout'; import { EmbedLayout } from './components/EmbedLayout'; import { EmbedAuthWrapper } from './components/EmbedAuthWrapper'; import { CriticalView } from './views/CriticalView'; import { AuthCallback } from './components/Auth'; import { pb } from './lib/pocketbase'; import { initStore, setStore, store, advanceAIHelpTipSectionIndex } from './store'; import { generateAIHelpTip, hasFireworksApiKey } from './lib/ai-help'; // const CriticalView = lazy(() => import('./views/CriticalView').then(m => ({ default: m.CriticalView }))); const UrgencyView = lazy(() => import('./views/UrgencyView').then(m => ({ default: m.UrgencyView }))); const PriorityView = lazy(() => import('./views/PriorityView').then(m => ({ default: m.PriorityView }))); const MatrixView = lazy(() => import('./views/MatrixView').then(m => ({ default: m.MatrixView }))); const SettingsView = lazy(() => import('./views/SettingsView').then(m => ({ default: m.SettingsView }))); const SnowballView = lazy(() => import('./views/SnowballView').then(m => ({ default: m.SnowballView }))); const DigInView = lazy(() => import('./views/DigInView').then(m => ({ default: m.DigInView }))); const ProgressView = lazy(() => import('./views/ProgressView').then(m => ({ default: m.ProgressView }))); const HelpView = lazy(() => import('./views/HelpView').then(m => ({ default: m.HelpView }))); const AIHelpView = lazy(() => import('./views/AIHelpView').then(m => ({ default: m.AIHelpView }))); const NotepadView = lazy(() => import('./views/NotepadView').then(m => ({ default: m.NotepadView }))); const QuickEntryForm = lazy(() => import('./components/QuickEntryForm').then(m => ({ default: m.QuickEntryForm }))); const App: Component = () => { // Basic routing state const [currentView, setCurrentView] = createSignal("critical"); const [lastNonAIView, setLastNonAIView] = createSignal("critical"); const [isAuthenticated, setIsAuthenticated] = createSignal(pb.authStore.isValid); const [authUserId, setAuthUserId] = createSignal(pb.authStore.model?.id || null); const [location, setLocation] = createSignal(window.location.href); const [isDesktop, setIsDesktop] = createSignal(window.matchMedia("(min-width: 768px)").matches); const [aiHelpTip, setAiHelpTip] = createSignal(null); const [tipGeneratedForUserId, setTipGeneratedForUserId] = createSignal(null); createEffect(() => { const view = currentView(); if (view !== "help_ai") { setLastNonAIView(view); } }); const effectiveView = createMemo(() => currentView() === "help_ai" && isDesktop() ? lastNonAIView() : currentView() ); createEffect(() => { const userId = authUserId(); const authenticated = isAuthenticated(); const desktop = isDesktop(); const initializing = store.isInitializing; const hasApiKey = hasFireworksApiKey(); const alreadyGeneratedForUser = tipGeneratedForUserId() === userId; const prefsLoadedForUser = !!userId && store.prefId === userId; if (!authenticated || !userId || !desktop || initializing || !prefsLoadedForUser || !hasApiKey || alreadyGeneratedForUser) return; setTipGeneratedForUserId(userId); const sectionIndex = untrack(() => store.aiHelpTipSectionIndex); void (async () => { try { const nextTip = await generateAIHelpTip(sectionIndex); if (!nextTip) return; setAiHelpTip(nextTip); await advanceAIHelpTipSectionIndex(); } catch (err) { console.warn("Tip generation skipped", err); } })(); }); onMount(() => { const mediaQuery = window.matchMedia("(min-width: 768px)"); const openHelpGuide = (sectionId?: string) => { const nextHash = sectionId ? `#help/${sectionId}` : "#help"; if (window.location.hash !== nextHash) { window.location.hash = nextHash; } setStore('isNotepadMode', false); setCurrentView("help"); window.dispatchEvent(new CustomEvent("taskgrid:help-target", { detail: { sectionId } })); }; const openAIHelp = () => { setStore('isNotepadMode', false); setCurrentView("help_ai"); }; const handleMediaChange = (event: MediaQueryListEvent) => { setIsDesktop(event.matches); }; const handleLocChange = () => { setLocation(window.location.href); const hash = window.location.hash; if (hash === "#help" || hash.startsWith("#help/")) { setCurrentView("help"); } }; (window as any).setCurrentView = setCurrentView; (window as any).openTasGridHelp = openHelpGuide; (window as any).openTasGridAIHelp = openAIHelp; window.addEventListener('popstate', handleLocChange); window.addEventListener('hashchange', handleLocChange); mediaQuery.addEventListener('change', handleMediaChange); let isFirstRun = true; const removeListener = pb.authStore.onChange((token) => { const wasAuthenticated = isAuthenticated(); const previousUserId = authUserId(); const nextUserId = token ? (pb.authStore.model?.id || null) : null; setIsAuthenticated(!!token); setAuthUserId(nextUserId); if (!token || nextUserId !== previousUserId) { setTipGeneratedForUserId(null); setAiHelpTip(null); } if (token && (isFirstRun || !wasAuthenticated)) { initStore(); } isFirstRun = false; }, true); onCleanup(() => { removeListener(); delete (window as any).setCurrentView; delete (window as any).openTasGridHelp; delete (window as any).openTasGridAIHelp; window.removeEventListener('popstate', handleLocChange); window.removeEventListener('hashchange', handleLocChange); mediaQuery.removeEventListener('change', handleMediaChange); }); // Background preload of other views when idle const idleCallback = (window as any).requestIdleCallback || ((cb: any) => setTimeout(cb, 1000)); idleCallback(() => { // CriticalView is now static import('./views/UrgencyView'); import('./views/PriorityView'); import('./views/MatrixView'); import('./views/SettingsView'); import('./views/SnowballView'); import('./views/DigInView'); import('./views/ProgressView'); import('./views/HelpView'); import('./views/AIHelpView'); // Remove splash screen after high priority work is done const splash = document.getElementById('splash'); if (splash) { splash.style.opacity = '0'; setTimeout(() => splash.remove(), 500); } }); handleLocChange(); }); // Determine if we are in an embed route (check both path and hash for compatibility) const isEmbed = () => { const loc = location(); // Reactive dependency const url = new URL(loc); const path = url.pathname; const hash = url.hash; return path.startsWith('/embed/') || hash.startsWith('#/embed/'); }; const getEmbedView = () => { const loc = location(); // Reactive dependency const url = new URL(loc); const fullPath = url.pathname + url.hash; let view = null; if (fullPath.includes('/notes')) view = 'embed_notes'; else if (fullPath.includes('/quick-add')) view = 'embed_quick_add'; return view; }; return ( { }} />}> setCurrentView(currentView() === "help_ai" ? lastNonAIView() : "help_ai")} aiHelpTip={aiHelpTip()} hideQuickEntry={currentView() === "help_ai" && !isDesktop()} >
}> setCurrentView("settings")} />
} > {(() => { const isPublicNote = () => { const view = getEmbedView(); const urlObj = new URL(location()); const noteId = new URLSearchParams(urlObj.search).get('noteId') || (urlObj.hash.includes('?') ? new URLSearchParams(urlObj.hash.substring(urlObj.hash.indexOf('?'))).get('noteId') : null); return view === 'embed_notes' && !!noteId; }; return (
}> {(() => { const noteIdFromUrl = createMemo(() => { const urlObj = new URL(location()); let noteId = new URLSearchParams(urlObj.search).get('noteId'); if (!noteId && urlObj.hash.includes('?')) { const hashQueryString = urlObj.hash.substring(urlObj.hash.indexOf('?')); noteId = new URLSearchParams(hashQueryString).get('noteId'); } return noteId; }); return { }} hideNavigation={!!noteIdFromUrl()} />; })()}
); })()} ); }; export default App;