Skip to content

Commit b8f994d

Browse files
committed
feat: code splitting, dead code removal, Socket.IO elimination (Phase 15.14)
- Add route-level code splitting: 19 page components lazy-loaded via React.lazy() with Suspense in Layout - Remove Socket.IO entirely: server service, client hooks/service, socket.io + socket.io-client deps - Convert database refresh/calculate-generations from fire-and-forget to synchronous endpoints - Remove dead code: TreeView, ConnectionLine, batchInsert() (SQL injection risk), getCanonicalDbId() - Fix DELETE route headers-already-sent bug in database.routes.ts - Remove dead /socket.io Vite proxy config - Clean up canonicalDbId identity aliases in favorites.service.ts
1 parent a8e28b3 commit b8f994d

File tree

21 files changed

+88
-1005
lines changed

21 files changed

+88
-1005
lines changed

.changelog/NEXT.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
## Changed
1313

14+
- Route-level code splitting: all 19 page components lazy-loaded via `React.lazy()` with Suspense fallback in Layout
15+
- Dashboard refresh/calculate-generations now return data synchronously instead of fire-and-forget with Socket.IO
1416
- Split 1457-line `augmentation.service.ts` god file into focused services: `platform-linking.service.ts` (URL parsing, scrapers, link* functions), `augmentation-photo.service.ts` (photo paths, fetch-from-platform), `provider-mapping.service.ts` (provider mapping CRUD)
1517
- Extracted shared `fetchHtml` and `normalizePhotoUrl` utilities; consolidated duplicate `normalizePhotoUrl` from `multi-platform-comparison.service.ts`
1618
- Extracted `ensureAncestryLoggedIn` and `extractAncestryPhotoFromPage` helpers to dedupe Ancestry login + srcset logic
@@ -26,5 +28,10 @@
2628

2729
- Platform comparison now treats equivalent date formats as matches (e.g., "1979-07-31" vs "31 JUL 1979")
2830
- `isLegacyFormat` augmentation type guard no longer crashes on string/null input
31+
- DELETE database route no longer sends success response on error (headers-already-sent crash)
2932

3033
## Removed
34+
35+
- Socket.IO: removed server (`socket.service.ts`, `socket.io` dep) and client (`socket.ts`, `useSocket.ts` hooks), replaced with synchronous API + existing SSE
36+
- Dead code: `TreeView` component, `ConnectionLine` component, `batchInsert()` (had SQL injection risk), `getCanonicalDbId()` identity function
37+
- Dead `/socket.io` Vite proxy config

PLAN.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -531,17 +531,17 @@ Code audit identified DRY/YAGNI/performance issues to address before Phase 16:
531531
- [ ] **Optimize `buildPersonFromSqlite()`**: Replace 6 sequential queries with JOINs or batched queries
532532
- [ ] **Add composite DB index**: `vital_event(person_id, place)` for search query performance
533533
- [ ] **Convert LRUCache class to factory function**: Match project's functional style in `cache.service.ts`
534-
- [ ] **Add route-level code splitting**: Wrap page components in `React.lazy()` + `Suspense` in `App.tsx` (all pages eagerly imported today)
534+
- [x] ~~**Add route-level code splitting**: Wrap page components in `React.lazy()` + `Suspense` in `App.tsx` (all pages eagerly imported today)~~ (Done: all 19 page components lazy-loaded with Suspense + spinner fallback)
535535

536536
#### Dead Code Removal
537-
- [ ] **Remove Socket.IO service + hooks** (298 lines): `client/src/services/socket.ts` and `client/src/hooks/useSocket.ts` — never imported, all real-time uses SSE
538-
- [ ] **Remove dead `TreeView` component** (167 lines): `client/src/components/tree/TreeView.tsx` — superseded by `AncestryTreeView`, not referenced
539-
- [ ] **Remove dead `ConnectionLine` components** (193 lines): `client/src/components/ancestry-tree/ConnectionLine.tsx` — exported but never imported
540-
- [ ] **Remove dead `batchInsert()`**: Unused function in `sqlite.service.ts` with SQL injection risk (table/column names interpolated)
537+
- [x] ~~**Remove Socket.IO service + hooks**: `client/src/services/socket.ts` and `client/src/hooks/useSocket.ts`, `server/src/services/socket.service.ts` — replaced with synchronous API responses in `database.routes.ts`, removed `socket.io` server dep~~ (Done: converted refresh/calculate-generations to sync endpoints, removed Socket.IO from server + client hooks, cleaned browserSseManager)
538+
- [x] ~~**Remove dead `TreeView` component** (167 lines): `client/src/components/tree/TreeView.tsx` — superseded by `AncestryTreeView`, not referenced~~ (Done)
539+
- [x] ~~**Remove dead `ConnectionLine` components** (193 lines): `client/src/components/ancestry-tree/ConnectionLine.tsx` — exported but never imported~~ (Done)
540+
- [x] ~~**Remove dead `batchInsert()`**: Unused function in `sqlite.service.ts` with SQL injection risk (table/column names interpolated)~~ (Done)
541541

542542
#### Cleanup & Security
543543
- [ ] **Audit legacy migration code**: Determine if `LegacyAugmentation` in `augmentation.service.ts:22-95` is still needed
544-
- [ ] **Remove identity function**: `getCanonicalDbId()` in `database.service.ts:133-135` just returns its input
544+
- [x] ~~**Remove identity function**: `getCanonicalDbId()` in `database.service.ts:133-135` just returns its input~~ (Done: inlined in favorites.service.ts)
545545
- [ ] **Deprecate legacy favorites routes**: `GET/POST/PUT/DELETE /:personId` duplicates db-scoped endpoints
546546
- [ ] **Add route param validation**: `personId` used directly in file paths without sanitization (path traversal risk)
547547
- [ ] **Validate browser navigate URL**: `/browser/navigate` accepts arbitrary URLs (SSRF risk) — restrict to genealogy domains

client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
"zustand": "^5.0.12"
2323
},
2424
"devDependencies": {
25-
"@testing-library/react": "^16.3.2",
2625
"@tailwindcss/vite": "^4.2.2",
26+
"@testing-library/react": "^16.3.2",
2727
"@types/d3": "^7.4.3",
2828
"@types/leaflet": "^1.9.21",
2929
"@types/react": "^18.3.28",

client/src/App.tsx

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,32 @@
1+
import { lazy } from 'react';
12
import { Routes, Route, Navigate } from 'react-router-dom';
23
import { Layout } from './components/layout/Layout';
3-
import { Dashboard } from './components/Dashboard';
4-
import { AncestryTreeView } from './components/ancestry-tree';
5-
import { PersonDetail } from './components/person/PersonDetail';
6-
import { SearchPage } from './components/search/SearchPage';
7-
import { PathFinder } from './components/path/PathFinder';
8-
import { IndexerPage } from './components/indexer/IndexerPage';
9-
import { AIProvidersPage } from './pages/AIProviders';
10-
import { GenealogyProvidersPage } from './pages/GenealogyProviders';
11-
import { GedcomPage } from './pages/GedcomPage';
12-
import { BrowserSettingsPage } from './pages/BrowserSettingsPage';
13-
import { ReportsPage } from './pages/ReportsPage';
14-
import { FavoritesPage } from './components/favorites/FavoritesPage';
15-
import { SparseTreePage } from './components/favorites/SparseTreePage';
16-
import { SparseTreeMapPage } from './components/favorites/SparseTreeMapPage';
17-
import { DatabaseFavoritesPage } from './components/favorites/DatabaseFavoritesPage';
18-
import { IntegrityPage } from './components/integrity/IntegrityPage';
19-
import { AuditPage } from './components/audit/AuditPage';
20-
import { AncestryUpdatePage } from './components/ancestry-update';
21-
import { TreeStatsPage } from './components/stats/TreeStatsPage';
4+
5+
const Dashboard = lazy(() => import('./components/Dashboard').then(m => ({ default: m.Dashboard })));
6+
const AncestryTreeView = lazy(() => import('./components/ancestry-tree/AncestryTreeView').then(m => ({ default: m.AncestryTreeView })));
7+
const PersonDetail = lazy(() => import('./components/person/PersonDetail').then(m => ({ default: m.PersonDetail })));
8+
const SearchPage = lazy(() => import('./components/search/SearchPage').then(m => ({ default: m.SearchPage })));
9+
const PathFinder = lazy(() => import('./components/path/PathFinder').then(m => ({ default: m.PathFinder })));
10+
const IndexerPage = lazy(() => import('./components/indexer/IndexerPage').then(m => ({ default: m.IndexerPage })));
11+
const AIProvidersPage = lazy(() => import('./pages/AIProviders').then(m => ({ default: m.AIProvidersPage })));
12+
const GenealogyProvidersPage = lazy(() => import('./pages/GenealogyProviders').then(m => ({ default: m.GenealogyProvidersPage })));
13+
const GedcomPage = lazy(() => import('./pages/GedcomPage').then(m => ({ default: m.GedcomPage })));
14+
const BrowserSettingsPage = lazy(() => import('./pages/BrowserSettingsPage').then(m => ({ default: m.BrowserSettingsPage })));
15+
const ReportsPage = lazy(() => import('./pages/ReportsPage').then(m => ({ default: m.ReportsPage })));
16+
const FavoritesPage = lazy(() => import('./components/favorites/FavoritesPage').then(m => ({ default: m.FavoritesPage })));
17+
const SparseTreePage = lazy(() => import('./components/favorites/SparseTreePage').then(m => ({ default: m.SparseTreePage })));
18+
const SparseTreeMapPage = lazy(() => import('./components/favorites/SparseTreeMapPage').then(m => ({ default: m.SparseTreeMapPage })));
19+
const DatabaseFavoritesPage = lazy(() => import('./components/favorites/DatabaseFavoritesPage').then(m => ({ default: m.DatabaseFavoritesPage })));
20+
const IntegrityPage = lazy(() => import('./components/integrity/IntegrityPage').then(m => ({ default: m.IntegrityPage })));
21+
const AuditPage = lazy(() => import('./components/audit/AuditPage').then(m => ({ default: m.AuditPage })));
22+
const AncestryUpdatePage = lazy(() => import('./components/ancestry-update').then(m => ({ default: m.AncestryUpdatePage })));
23+
const TreeStatsPage = lazy(() => import('./components/stats/TreeStatsPage').then(m => ({ default: m.TreeStatsPage })));
2224

2325
function App() {
2426
return (
2527
<Routes>
2628
<Route path="/" element={<Layout />}>
2729
<Route index element={<Dashboard />} />
28-
{/* Tree routes with view mode support */}
2930
<Route path="tree/:dbId" element={<AncestryTreeView />} />
3031
<Route path="tree/:dbId/:personId" element={<AncestryTreeView />} />
3132
<Route path="tree/:dbId/:personId/:viewMode" element={<AncestryTreeView />} />
@@ -35,7 +36,6 @@ function App() {
3536
<Route path="indexer" element={<IndexerPage />} />
3637
<Route path="providers" element={<AIProvidersPage />} />
3738
<Route path="providers/genealogy" element={<GenealogyProvidersPage />} />
38-
{/* Redirects for removed routes */}
3939
<Route path="providers/genealogy/new" element={<Navigate to="/providers/genealogy" replace />} />
4040
<Route path="providers/genealogy/:id/edit" element={<Navigate to="/providers/genealogy" replace />} />
4141
<Route path="providers/scraper" element={<Navigate to="/providers/genealogy" replace />} />

client/src/components/Dashboard.tsx

Lines changed: 17 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { useEffect, useState, useCallback } from 'react';
1+
import { useEffect, useState } from 'react';
22
import { Link } from 'react-router-dom';
33
import { Trash2, Users, GitBranch, Search, Route, Loader2, Database, FlaskConical, Eye, EyeOff, RefreshCw, Calculator, Download, BarChart3 } from 'lucide-react';
44
import { CopyButton } from './ui/CopyButton';
55
import toast from 'react-hot-toast';
66
import type { DatabaseInfo } from '@fsf/shared';
77
import { api } from '../services/api';
8-
import { useSocketConnection, useSocketEvent } from '../hooks/useSocket';
98

109
// Platform badge colors
1110
const platformColors: Record<string, { bg: string; text: string }> = {
@@ -84,37 +83,6 @@ export function Dashboard() {
8483
return stored === null ? true : stored === 'true';
8584
});
8685

87-
// Connect to socket for real-time updates
88-
useSocketConnection();
89-
90-
// Handle database refresh events via socket
91-
const handleRefreshEvent = useCallback((data: { dbId: string; status: string; personCount?: number; data?: DatabaseInfo; message?: string }) => {
92-
if (data.status === 'complete' && data.data) {
93-
setDatabases(prev => prev.map(d => d.id === data.dbId ? data.data! : d));
94-
toast.success(`Updated count: ${data.personCount?.toLocaleString()} parents`);
95-
setRefreshingId(null);
96-
} else if (data.status === 'error') {
97-
toast.error(`Failed to refresh: ${data.message || 'Unknown error'}`);
98-
setRefreshingId(null);
99-
}
100-
}, []);
101-
102-
useSocketEvent('database:refresh', handleRefreshEvent);
103-
104-
// Handle generation calculation events via socket
105-
const handleGenerationsEvent = useCallback((data: { dbId: string; status: string; maxGenerations?: number; data?: DatabaseInfo; message?: string }) => {
106-
if (data.status === 'complete' && data.data) {
107-
setDatabases(prev => prev.map(d => d.id === data.dbId ? data.data! : d));
108-
toast.success(`Calculated: ${data.maxGenerations} generations`);
109-
setCalculatingGenId(null);
110-
} else if (data.status === 'error') {
111-
toast.error(`Failed to calculate generations: ${data.message || 'Unknown error'}`);
112-
setCalculatingGenId(null);
113-
}
114-
}, []);
115-
116-
useSocketEvent('database:generations', handleGenerationsEvent);
117-
11886
useEffect(() => {
11987
api.listDatabases()
12088
.then(setDatabases)
@@ -156,22 +124,28 @@ export function Dashboard() {
156124

157125
const handleRefresh = async (db: DatabaseInfo) => {
158126
setRefreshingId(db.id);
159-
160-
// Trigger refresh via API - socket will receive the result
161-
await api.refreshRootCount(db.id).catch(err => {
162-
toast.error(`Failed to start refresh: ${err.message}`);
163-
setRefreshingId(null);
127+
const result = await api.refreshRootCount(db.id).catch(err => {
128+
toast.error(`Failed to refresh: ${err.message}`);
129+
return null;
164130
});
131+
if (result) {
132+
setDatabases(prev => prev.map(d => d.id === db.id ? result : d));
133+
toast.success(`Updated count: ${result.personCount?.toLocaleString()} ancestors`);
134+
}
135+
setRefreshingId(null);
165136
};
166137

167138
const handleCalculateGenerations = async (db: DatabaseInfo) => {
168139
setCalculatingGenId(db.id);
169-
170-
// Trigger calculation via API - socket will receive the result
171-
await api.calculateGenerations(db.id).catch(err => {
172-
toast.error(`Failed to start generation calculation: ${err.message}`);
173-
setCalculatingGenId(null);
140+
const result = await api.calculateGenerations(db.id).catch(err => {
141+
toast.error(`Failed to calculate generations: ${err.message}`);
142+
return null;
174143
});
144+
if (result) {
145+
setDatabases(prev => prev.map(d => d.id === db.id ? result : d));
146+
toast.success(`Calculated: ${result.maxGenerations} generations`);
147+
}
148+
setCalculatingGenId(null);
175149
};
176150

177151
if (loading) {

client/src/components/ancestry-tree/ConnectionLine.tsx

Lines changed: 0 additions & 192 deletions
This file was deleted.

0 commit comments

Comments
 (0)