- Introduced custodian_hub_v3.mmd, custodian_hub_v4_final.mmd, and custodian_hub_v5_FINAL.mmd for Mermaid representation. - Created custodian_hub_FINAL.puml and custodian_hub_v3.puml for PlantUML representation. - Defined entities such as CustodianReconstruction, Identifier, TimeSpan, Agent, CustodianName, CustodianObservation, ReconstructionActivity, Appellation, ConfidenceMeasure, Custodian, LanguageCode, and SourceDocument. - Established relationships and associations between entities, including temporal extents, observations, and reconstruction activities. - Incorporated enumerations for various types, statuses, and classifications relevant to custodians and their activities.
20 KiB
GLAM Frontend Quick Start Guide
🚀 Get Started in 15 Minutes
This guide will help you set up and start developing the GLAM frontend immediately.
Prerequisites Check
Before starting, ensure you have:
# Check Node.js version (need 18+)
node --version # Should show v18.x.x or higher
# Check npm version
npm --version # Should show 9.x.x or higher
# Check Git
git --version # Any recent version
If any are missing:
- Install Node.js: https://nodejs.org/ (LTS version)
- npm comes with Node.js
- Install Git: https://git-scm.com/
Step 1: Project Initialization (5 minutes)
# Navigate to the GLAM project
cd /Users/kempersc/apps/glam
# Create frontend directory
mkdir -p frontend
cd frontend
# Initialize Vite project with React + TypeScript
npm create vite@latest . -- --template react-ts
# Install dependencies (this will take 2-3 minutes)
npm install
Verify Installation
# Start the dev server
npm run dev
# You should see:
# ➜ Local: http://localhost:5173/
# ➜ press h to show help
Open http://localhost:5173/ in your browser. You should see the Vite + React welcome page. 🎉
Stop the server (Ctrl+C) before continuing.
Step 2: Project Structure Setup (3 minutes)
# Create the directory structure
mkdir -p src/{components,lib,hooks,stores,types,utils,pages,styles}
mkdir -p src/components/{layout,visualizations,forms,ui}
mkdir -p src/lib/{rdf,storage,api,utils}
mkdir -p src/components/visualizations/{GraphView,Timeline,Map,Hierarchy}
mkdir -p tests/{unit,integration,e2e}
mkdir -p public/assets
# Verify structure
tree -L 3 src/ # or use 'ls -R src/' if tree not installed
Expected output:
src/
├── components/
│ ├── forms/
│ ├── layout/
│ ├── ui/
│ └── visualizations/
│ ├── GraphView/
│ ├── Hierarchy/
│ ├── Map/
│ └── Timeline/
├── hooks/
├── lib/
│ ├── api/
│ ├── rdf/
│ ├── storage/
│ └── utils/
├── pages/
├── stores/
├── styles/
├── types/
└── utils/
Step 3: Install Core Dependencies (5 minutes)
# Core dependencies
npm install d3 @types/d3 n3 zustand @tanstack/react-query
npm install react-router-dom axios
npm install leaflet @types/leaflet
npm install date-fns lodash @types/lodash
# Dev dependencies
npm install -D vitest @vitest/ui @testing-library/react
npm install -D @testing-library/jest-dom @testing-library/user-event
npm install -D @playwright/test
npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
npm install -D prettier eslint-config-prettier eslint-plugin-prettier
npm install -D @types/node
# Install Playwright browsers (takes 1-2 minutes)
npx playwright install
Verify Dependencies
# Check package.json
cat package.json | grep -A 20 '"dependencies"'
Step 4: Configuration Files (2 minutes)
TypeScript Configuration
cat > tsconfig.json << 'EOF'
{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"strict": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
/* Path aliases */
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
EOF
Vite Configuration
cat > vite.config.ts << 'EOF'
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
},
},
},
})
EOF
Vitest Configuration
cat > vitest.config.ts << 'EOF'
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import path from 'path'
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: './tests/setup.ts',
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: [
'node_modules/',
'tests/',
'**/*.test.{ts,tsx}',
'**/*.spec.{ts,tsx}',
],
},
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
})
EOF
ESLint Configuration
cat > .eslintrc.json << 'EOF'
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"prettier"
],
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "react", "react-hooks"],
"rules": {
"react/react-in-jsx-scope": "off",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
},
"settings": {
"react": {
"version": "detect"
}
}
}
EOF
Prettier Configuration
cat > .prettierrc << 'EOF'
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2,
"useTabs": false
}
EOF
Step 5: First Component - IndexedDB Manager
Let's migrate the first utility from example_ld.
Create Type Definitions
cat > src/types/database.ts << 'EOF'
export interface RdfData {
id: string;
data: string;
format: RdfFormat;
metadata: RdfMetadata;
timestamp: Date;
}
export interface RdfMetadata {
tripleCount: number;
source: string;
description?: string;
}
export type RdfFormat =
| 'text/turtle'
| 'application/n-triples'
| 'application/ld+json'
| 'application/rdf+xml';
export interface DatabaseConfig {
name: string;
version: number;
stores: StoreConfig[];
}
export interface StoreConfig {
name: string;
keyPath: string;
indexes?: IndexConfig[];
}
export interface IndexConfig {
name: string;
keyPath: string;
unique: boolean;
}
EOF
Create IndexedDB Manager
cat > src/lib/storage/indexed-db.ts << 'EOF'
import type { RdfData, DatabaseConfig } from '@/types/database';
export class IndexedDBManager {
private db: IDBDatabase | null = null;
private readonly config: DatabaseConfig = {
name: 'GLAM_RDF_DB',
version: 1,
stores: [
{
name: 'rdf_data',
keyPath: 'id',
indexes: [
{ name: 'timestamp', keyPath: 'timestamp', unique: false },
{ name: 'format', keyPath: 'format', unique: false },
],
},
],
};
async initialize(): Promise<void> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.config.name, this.config.version);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve();
};
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
this.config.stores.forEach((storeConfig) => {
if (!db.objectStoreNames.contains(storeConfig.name)) {
const store = db.createObjectStore(storeConfig.name, {
keyPath: storeConfig.keyPath,
});
storeConfig.indexes?.forEach((index) => {
store.createIndex(index.name, index.keyPath, {
unique: index.unique,
});
});
}
});
};
});
}
async save(data: RdfData): Promise<void> {
if (!this.db) throw new Error('Database not initialized');
return new Promise((resolve, reject) => {
const transaction = this.db!.transaction(['rdf_data'], 'readwrite');
const store = transaction.objectStore('rdf_data');
const request = store.put(data);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
async get(id: string): Promise<RdfData | null> {
if (!this.db) throw new Error('Database not initialized');
return new Promise((resolve, reject) => {
const transaction = this.db!.transaction(['rdf_data'], 'readonly');
const store = transaction.objectStore('rdf_data');
const request = store.get(id);
request.onsuccess = () => resolve(request.result || null);
request.onerror = () => reject(request.error);
});
}
async getAll(): Promise<RdfData[]> {
if (!this.db) throw new Error('Database not initialized');
return new Promise((resolve, reject) => {
const transaction = this.db!.transaction(['rdf_data'], 'readonly');
const store = transaction.objectStore('rdf_data');
const request = store.getAll();
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async delete(id: string): Promise<void> {
if (!this.db) throw new Error('Database not initialized');
return new Promise((resolve, reject) => {
const transaction = this.db!.transaction(['rdf_data'], 'readwrite');
const store = transaction.objectStore('rdf_data');
const request = store.delete(id);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
async clear(): Promise<void> {
if (!this.db) throw new Error('Database not initialized');
return new Promise((resolve, reject) => {
const transaction = this.db!.transaction(['rdf_data'], 'readwrite');
const store = transaction.objectStore('rdf_data');
const request = store.clear();
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
}
// Singleton instance
export const db = new IndexedDBManager();
EOF
Create React Hook
cat > src/hooks/useDatabase.ts << 'EOF'
import { useState, useEffect } from 'react';
import { db } from '@/lib/storage/indexed-db';
import type { RdfData } from '@/types/database';
export const useDatabase = () => {
const [isReady, setIsReady] = useState(false);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
db.initialize()
.then(() => setIsReady(true))
.catch(setError);
}, []);
const saveData = async (data: RdfData) => {
try {
await db.save(data);
} catch (err) {
setError(err as Error);
throw err;
}
};
const getData = async (id: string) => {
try {
return await db.get(id);
} catch (err) {
setError(err as Error);
throw err;
}
};
const getAllData = async () => {
try {
return await db.getAll();
} catch (err) {
setError(err as Error);
throw err;
}
};
const deleteData = async (id: string) => {
try {
await db.delete(id);
} catch (err) {
setError(err as Error);
throw err;
}
};
const clearAll = async () => {
try {
await db.clear();
} catch (err) {
setError(err as Error);
throw err;
}
};
return {
isReady,
error,
saveData,
getData,
getAllData,
deleteData,
clearAll,
};
};
EOF
Write Tests
# Create test setup
cat > tests/setup.ts << 'EOF'
import '@testing-library/jest-dom';
import { expect, afterEach } from 'vitest';
import { cleanup } from '@testing-library/react';
afterEach(() => {
cleanup();
});
EOF
# Create test for IndexedDB manager
cat > src/lib/storage/indexed-db.test.ts << 'EOF'
import { describe, it, expect, beforeEach } from 'vitest';
import { IndexedDBManager } from './indexed-db';
import type { RdfData } from '@/types/database';
describe('IndexedDBManager', () => {
let dbManager: IndexedDBManager;
beforeEach(async () => {
dbManager = new IndexedDBManager();
await dbManager.initialize();
await dbManager.clear();
});
it('should initialize database', async () => {
expect(dbManager).toBeDefined();
});
it('should save and retrieve data', async () => {
const testData: RdfData = {
id: 'test-1',
data: '<http://example.org/s> <http://example.org/p> <http://example.org/o> .',
format: 'text/turtle',
metadata: {
tripleCount: 1,
source: 'test',
},
timestamp: new Date(),
};
await dbManager.save(testData);
const retrieved = await dbManager.get('test-1');
expect(retrieved).toBeDefined();
expect(retrieved?.id).toBe('test-1');
expect(retrieved?.format).toBe('text/turtle');
});
it('should delete data', async () => {
const testData: RdfData = {
id: 'test-2',
data: 'test',
format: 'text/turtle',
metadata: { tripleCount: 0, source: 'test' },
timestamp: new Date(),
};
await dbManager.save(testData);
await dbManager.delete('test-2');
const retrieved = await dbManager.get('test-2');
expect(retrieved).toBeNull();
});
it('should get all data', async () => {
const testData1: RdfData = {
id: 'test-3',
data: 'test1',
format: 'text/turtle',
metadata: { tripleCount: 0, source: 'test' },
timestamp: new Date(),
};
const testData2: RdfData = {
id: 'test-4',
data: 'test2',
format: 'application/n-triples',
metadata: { tripleCount: 0, source: 'test' },
timestamp: new Date(),
};
await dbManager.save(testData1);
await dbManager.save(testData2);
const all = await dbManager.getAll();
expect(all).toHaveLength(2);
});
});
EOF
Run Tests
# Run the test
npm test
# You should see output like:
# ✓ src/lib/storage/indexed-db.test.ts (4 tests)
# ✓ IndexedDBManager
# ✓ should initialize database
# ✓ should save and retrieve data
# ✓ should delete data
# ✓ should get all data
Step 6: Create First Page Component
cat > src/pages/Home.tsx << 'EOF'
import { useDatabase } from '@/hooks/useDatabase';
export const Home = () => {
const { isReady, error, getAllData } = useDatabase();
const handleLoadData = async () => {
try {
const data = await getAllData();
console.log('Loaded data:', data);
alert(`Loaded ${data.length} RDF datasets`);
} catch (err) {
console.error('Error loading data:', err);
}
};
if (error) {
return (
<div style={{ padding: '2rem' }}>
<h1>Error</h1>
<p>{error.message}</p>
</div>
);
}
if (!isReady) {
return (
<div style={{ padding: '2rem' }}>
<h1>Loading...</h1>
<p>Initializing database...</p>
</div>
);
}
return (
<div style={{ padding: '2rem' }}>
<h1>GLAM Heritage Custodian Ontology</h1>
<p>Welcome to the RDF visualization platform.</p>
<div style={{ marginTop: '2rem' }}>
<button
onClick={handleLoadData}
style={{
padding: '0.5rem 1rem',
fontSize: '1rem',
cursor: 'pointer',
}}
>
Load Data
</button>
</div>
<div style={{ marginTop: '2rem' }}>
<h2>Quick Stats</h2>
<ul>
<li>Database Status: {isReady ? '✅ Ready' : '❌ Not Ready'}</li>
<li>RDF Schemas: Available at /schemas/20251121/rdf/</li>
<li>UML Diagrams: Available at /schemas/20251121/uml/</li>
</ul>
</div>
</div>
);
};
EOF
Update App.tsx
cat > src/App.tsx << 'EOF'
import { Home } from './pages/Home';
function App() {
return <Home />;
}
export default App;
EOF
Step 7: Test Your Application
# Start the development server
npm run dev
Visit http://localhost:5173/ and you should see:
- "GLAM Heritage Custodian Ontology" heading
- "Database Status: ✅ Ready"
- A "Load Data" button
Click the button - it should show an alert with "Loaded 0 RDF datasets" (since we haven't added any data yet).
Step 8: Update package.json Scripts
# Add these scripts to package.json (merge with existing)
cat >> package.json << 'EOF'
Add these to your "scripts" section:
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage",
"test:e2e": "playwright test",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "eslint src --ext ts,tsx --fix",
"format": "prettier --write \"src/**/*.{ts,tsx,json,css,md}\"",
"type-check": "tsc --noEmit"
}
EOF
✅ Success! You're Ready to Develop
You now have:
- ✅ A working Vite + React + TypeScript project
- ✅ Complete directory structure
- ✅ All core dependencies installed
- ✅ TypeScript, ESLint, Prettier configured
- ✅ First utility (IndexedDB manager) migrated from example_ld
- ✅ First React hook (useDatabase)
- ✅ First page component (Home)
- ✅ Working tests with Vitest
Next Steps
Option 1: Continue with Phase 1 (Recommended)
Follow the master checklist at docs/plan/frontend/05-master-checklist.md:
- Day 6-7: Complete RDF Parser migration
- Day 8-9: Utilities migration
- Day 10: API client migration
Option 2: Jump to Visualizations
If you want to see something visual quickly:
- Skip to Phase 3 (Week 5) in the master checklist
- Start building the D3 force-directed graph
- Load actual RDF data from
/schemas/20251121/rdf/
Option 3: Explore Example LD
Study the original implementation:
cd /Users/kempersc/apps/example_ld
cat js/db.js # Compare with your implementation
cat js/rdfParser.js # Next migration target
Useful Commands Reference
# Development
npm run dev # Start dev server
npm run build # Production build
npm run preview # Preview production build
# Testing
npm test # Run tests in watch mode
npm run test:ui # Visual test interface
npm run test:coverage # Coverage report
# Code Quality
npm run lint # Check for errors
npm run lint:fix # Auto-fix errors
npm run format # Format code
npm run type-check # TypeScript check
# Debugging
npm run dev -- --debug # Debug mode
npm run dev -- --host # Expose to network
Troubleshooting
Problem: "Cannot find module '@/...'"
Solution: Make sure tsconfig.json has the path alias configured and restart your IDE.
Problem: IndexedDB tests fail
Solution: Tests run in jsdom environment which has limited IndexedDB support. Consider using fake-indexeddb package for testing.
Problem: Port 5173 already in use
Solution:
# Kill the process using the port
lsof -ti:5173 | xargs kill -9
# Or use a different port
npm run dev -- --port 3000
Problem: Type errors with D3
Solution: Make sure you installed both d3 and @types/d3:
npm install d3 @types/d3
Getting Help
- Documentation:
docs/plan/frontend/directory - Master Checklist:
docs/plan/frontend/05-master-checklist.md - Visual Roadmap:
docs/plan/frontend/06-visual-roadmap.md - Example LD Reference:
/Users/kempersc/apps/example_ld
Pro Tips
- Use TypeScript Strict Mode: It catches bugs early
- Write Tests First: TDD leads to better design
- Keep Components Small: Easy to test and maintain
- Use React Query: Simplifies server state management
- Profile Performance: Use React DevTools Profiler
Congratulations! 🎉 You're now ready to build the GLAM frontend.
Estimated Time to Full Implementation: 16 weeks following the master checklist.
Next milestone: Complete Phase 1 (Core Libraries) in 2 weeks.
Last Updated: 2025-11-22
Version: 1.0
Guide Completion Time: ~15 minutes