group conversations with models and local files
0

Configure Feed

Select the types of activity you want to include in your feed.

Persist active project in URL hash so reload restores it

dietrich ayala (May 26, 2026, 5:19 PM +0200) 2a42357e 8e74a801

+58 -2
+58 -2
src/App.tsx
··· 157 157 } 158 158 } 159 159 160 + // Active-project URL state. We use `#p=<id>` (not a path segment) so 161 + // no server route handling is needed and main.tsx's import-fragment 162 + // handlers still get first crack at the hash on boot. 163 + function readProjectIdFromHash(): string | null { 164 + const m = /^#p=([A-Za-z0-9_-]+)$/.exec(window.location.hash) 165 + return m ? m[1] : null 166 + } 167 + function writeProjectIdToHash(id: string | null): void { 168 + const next = id ? `#p=${id}` : '' 169 + if (window.location.hash === next) return 170 + if (next) { 171 + history.replaceState({}, '', window.location.pathname + window.location.search + next) 172 + } else { 173 + history.replaceState({}, '', window.location.pathname + window.location.search) 174 + } 175 + } 176 + 160 177 export function App(): React.ReactElement { 161 178 const [phase, setPhase] = useState<Phase>('booting') 162 179 const [identity, setIdentity] = useState<Identity | null>(null) ··· 168 185 const [providerKeys, setProviderKeys] = useState<ProviderKeys>({}) 169 186 const [settingsOpen, setSettingsOpen] = useState(false) 170 187 const outboxRef = useRef<OutboxClient | null>(null) 188 + // Read once at mount: `#p=<id>` survives reload. ProjectList consumes 189 + // this on first list-fetch and opens the matching project. 190 + const initialProjectIdRef = useRef<string | null>( 191 + readProjectIdFromHash(), 192 + ) 171 193 172 194 useEffect(() => { 173 195 fetchMe() ··· 191 213 outboxRef.current = null 192 214 } 193 215 }, [phase]) 216 + 217 + // Keep the URL hash in sync with the active project so a reload 218 + // lands the user back where they were. 219 + useEffect(() => { 220 + writeProjectIdToHash(activeProject?.id ?? null) 221 + }, [activeProject]) 222 + 223 + // Browser back/forward — update activeProject if the hash changed. 224 + useEffect(() => { 225 + const onPop = (): void => { 226 + const id = readProjectIdFromHash() 227 + if (id === (activeProject?.id ?? null)) return 228 + if (!id) { 229 + setActiveProject(null) 230 + } 231 + // If id changed to a new value, ProjectList isn't mounted to 232 + // resolve it; user can re-navigate. Keep it simple. 233 + } 234 + window.addEventListener('popstate', onPop) 235 + return () => window.removeEventListener('popstate', onPop) 236 + }, [activeProject]) 194 237 195 238 // Load provider keys. Server is authoritative when reachable (for 196 239 // cross-device sync); falls back to the local IndexedDB cache if ··· 319 362 onOpen={setActiveProject} 320 363 onSignOut={signOut} 321 364 onOpenSettings={() => setSettingsOpen(true)} 365 + autoOpenProjectId={initialProjectIdRef.current} 322 366 /> 323 367 ) : ( 324 368 <ProjectView ··· 703 747 onOpen, 704 748 onSignOut, 705 749 onOpenSettings, 750 + autoOpenProjectId, 706 751 }: { 707 752 identity: Identity 708 753 prf: PrfSecret 709 754 onOpen: (p: ProjectMeta) => void 710 755 onSignOut: () => void 711 756 onOpenSettings: () => void 757 + // If set on first render, ProjectList will auto-open that project 758 + // once the list arrives. Used to restore #p=<id> across reloads. 759 + autoOpenProjectId: string | null 712 760 }): React.ReactElement { 713 761 const [projects, setProjects] = useState<ProjectMeta[]>([]) 714 762 const [name, setName] = useState('') 715 763 const [busy, setBusy] = useState(false) 716 764 const [error, setError] = useState<string | null>(null) 765 + const autoOpenRef = useRef(autoOpenProjectId) 717 766 718 767 const refresh = useCallback(async () => { 719 768 try { 720 - setProjects(await projectsApi.list()) 769 + const list = await projectsApi.list() 770 + setProjects(list) 771 + const target = autoOpenRef.current 772 + if (target) { 773 + autoOpenRef.current = null 774 + const match = list.find((p) => p.id === target) 775 + if (match) onOpen(match) 776 + } 721 777 } catch (err) { 722 778 setError(err instanceof Error ? err.message : String(err)) 723 779 } 724 - }, []) 780 + }, [onOpen]) 725 781 useEffect(() => { 726 782 refresh() 727 783 }, [refresh])