This guide walks you through setting up a local development environment for Lockdn.
| Software | Version | Purpose |
|---|---|---|
| Node.js | 20.x | Runtime |
| npm | 10.x+ | Package manager |
| Git | 2.x+ | Version control |
| Tool | Purpose |
|---|---|
| VS Code | IDE with excellent TypeScript support |
| nvm | Node version management |
| React DevTools | Browser extension for debugging |
git clone https://github.com/lockdn/lockdn.git
cd lockdnLockdn requires Node.js 20:
# Using nvm (recommended)
nvm install 20
nvm use 20
# Verify version
node --version # Should show v20.x.xnpm installnpm run devThe app will be available at http://localhost:5173
To test with realistic data, import the sample data file:
- Start the app and complete the onboarding
- Go to Settings → Data → Import
- Select
dev/sample-data.jsonfrom the repository - Click Import
The sample data includes:
- 4 courses (PSY 101, MATH 152, CS 201, ENG 102)
- 15 assignments with various due dates and weights
- 4 notes linked to courses
- 2 study materials
- 5 study sessions
- Analytics data with streaks
- User preferences
This gives you a populated app to test against without manually creating data.
lockdn/
├── src/
│ ├── components/ # React components
│ │ ├── ui/ # Reusable UI components (shadcn/ui)
│ │ ├── analytics/ # Analytics visualizations
│ │ ├── courses/ # Course-related components
│ │ ├── onboarding/ # Onboarding wizard
│ │ ├── settings/ # Settings forms
│ │ ├── syllabus/ # Syllabus upload/review
│ │ ├── sync/ # Device sync components
│ │ └── tutor/ # AI tutor interface
│ │
│ ├── pages/ # Page components (routes)
│ │ ├── DashboardPage.tsx
│ │ ├── CoursesPage.tsx
│ │ ├── CalendarPage.tsx
│ │ └── ...
│ │
│ ├── lib/ # Business logic and utilities
│ │ ├── ai/ # AI provider implementations
│ │ ├── sync/ # P2P sync protocol
│ │ ├── tutor.ts # Tutor system prompts
│ │ ├── studyPlanner.ts
│ │ ├── gradeCalculator.ts
│ │ ├── crypto.ts # Encryption utilities
│ │ └── ...
│ │
│ ├── db/ # Database layer
│ │ ├── index.ts # Dexie database definition
│ │ └── hooks.ts # React hooks for data access
│ │
│ ├── types/ # TypeScript type definitions
│ ├── hooks/ # Custom React hooks
│ ├── router.tsx # Route definitions
│ ├── main.tsx # App entry point
│ └── index.css # Global styles
│
├── public/ # Static assets
├── e2e/ # End-to-end tests (Playwright)
├── docs/ # Documentation
├── dev/ # Developer resources
│ └── sample-data.json # Test data for development
├── signaling/ # Signaling server (PartyKit)
└── scripts/ # Build scripts
| Command | Purpose |
|---|---|
npm run dev |
Start development server |
npm run build |
Build for production |
npm run preview |
Preview production build |
npm test |
Run unit tests |
npm run test:ui |
Run tests with UI |
npm run test:coverage |
Run tests with coverage report |
npm run test:e2e |
Run E2E tests |
npm run test:e2e:ui |
Run E2E tests with UI |
npm run lint |
Run ESLint |
-
Create a feature branch:
git checkout -b feature/my-feature
-
Start the dev server:
npm run dev
-
Make changes — the browser will hot-reload
-
Run tests:
npm test -
Commit changes:
git add . git commit -m "feat: add my feature"
Unit tests:
npm test # Run all tests
npm test -- --watch # Watch mode
npm test src/lib/foo.test.ts # Run specific testE2E tests:
npm run test:e2e # Run all E2E tests
npm run test:e2e:headed # Run with browser visible
npm run test:e2e:ui # Run with Playwright UIBrowser DevTools:
- React DevTools for component inspection
- Network tab for API calls
- Application tab for IndexedDB inspection
VS Code:
- Use the debugger with breakpoints
- Install recommended extensions
Lockdn uses Dexie.js for IndexedDB access:
// Access the database
import { db } from '@/db';
// Query courses
const courses = await db.courses.toArray();
// Add a course
const id = await db.courses.add({
name: 'Physics 101',
code: 'PHY101',
color: '#3b82f6',
createdAt: new Date(),
updatedAt: new Date()
});
// Use live queries in components
import { useLiveQuery } from 'dexie-react-hooks';
function CourseList() {
const courses = useLiveQuery(() => db.courses.toArray());
// Re-renders automatically when data changes
}Most data access should use the custom hooks:
import { useCourses, useAssignments, useNotes } from '@/db/hooks';
function MyComponent() {
const courses = useCourses();
const assignments = useAssignments(courseId);
// ...
}AI provider abstraction:
import { getConfiguredProvider } from '@/lib/ai';
async function askTutor(question: string) {
const provider = await getConfiguredProvider();
if (!provider) return null;
const response = await provider.sendMessage({
messages: [{ role: 'user', content: question }],
systemPrompt: tutorSystemPrompt
});
return response;
}Sync uses WebRTC for P2P communication:
import { syncProvider } from '@/lib/sync/provider';
// Start sync
await syncProvider.connect();
// Subscribe to sync events
syncProvider.onData((data) => {
// Handle incoming sync data
});// src/pages/NewPage.tsx
export function NewPage() {
return (
<div>
<h1>New Page</h1>
</div>
);
}
// Add to router.tsx
import { NewPage } from './pages/NewPage';
// In routes array:
{ path: '/new', element: <NewPage /> }// src/db/index.ts
// Add to schema version
this.version(7).stores({
// ... existing tables
newTable: '++id, field1, [field1+field2]'
});
// Add type
interface NewRecord {
id?: number;
field1: string;
field2: string;
}// src/lib/newFeature.ts
import { getConfiguredProvider } from '@/lib/ai';
export async function newAIFeature(input: string): Promise<string> {
const provider = await getConfiguredProvider();
if (!provider) throw new Error('No AI provider configured');
const response = await provider.sendMessage({
messages: [{ role: 'user', content: input }],
systemPrompt: 'Your system prompt here...'
});
return response.content;
}Lockdn uses shadcn/ui components:
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
function MyComponent() {
return (
<Card>
<CardHeader>Title</CardHeader>
<CardContent>
<Input placeholder="Enter text" />
<Button>Submit</Button>
</CardContent>
</Card>
);
}// src/lib/myFunction.test.ts
import { describe, it, expect } from 'vitest';
import { myFunction } from './myFunction';
describe('myFunction', () => {
it('should return expected value', () => {
expect(myFunction('input')).toBe('expected output');
});
it('should handle edge case', () => {
expect(myFunction('')).toBeUndefined();
});
});Use date-fns for date manipulation:
import { format, addDays, isBefore } from 'date-fns';
const formatted = format(new Date(), 'MMM d, yyyy');
const nextWeek = addDays(new Date(), 7);
const isPast = isBefore(dueDate, new Date());Create .env.local for local overrides:
# Optional: Custom signaling server
VITE_SIGNALING_URL=wss://your-signaling-server.com# Clear node_modules and reinstall
rm -rf node_modules
npm install# Check TypeScript errors
npx tsc --noEmit# Run tests in isolation
npm test -- --run --reporter=verboseTests use fake-indexeddb. If you encounter issues:
// In your test file
import 'fake-indexeddb/auto';Recommended extensions (in .vscode/extensions.json):
- ESLint
- Prettier
- Tailwind CSS IntelliSense
- TypeScript Vue Plugin
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"typescript.preferences.importModuleSpecifier": "relative"
}- Read the Architecture Guide for system design
- Review CONTRIBUTING.md for contribution guidelines
- Check the issue tracker for work items
- Join GitHub Discussions for questions