Development Guide
This guide covers everything you need to know to develop on the Sightline codebase.
New to Sightline? Start with the Quickstart Guide to get up and running quickly. For production deployment, see the Deployment Guide.
Prerequisites
- Node.js 20.x or later
- pnpm 9.x (recommended) or npm/yarn/bun
- Mapbox Account - For the map tiles (sign up free)
- Git - For version control
Getting Started
1. Clone the Repository
git clone https://github.com/disclosure-foundation/sightline.git
cd sightline2. Install Dependencies
pnpm installnpm installyarn installbun install3. Set Up Environment Variables
cp .env.example .env.localEdit .env.local and add your Mapbox token:
NEXT_PUBLIC_MAPBOX_TOKEN=pk.your_mapbox_token_here4. Start Development Server
pnpm devnpm run devyarn devbun devOpen http://localhost:3001 to see the map with demo data.
Available Scripts
Development
| Command | Description |
|---|---|
pnpm dev | Start development server with hot reload |
pnpm dev:turbo | Start dev server with Turbopack (faster) |
pnpm build | Create production build |
pnpm start | Start production server |
Code Quality
| Command | Description |
|---|---|
pnpm type-check | Run TypeScript type checking |
pnpm lint | Run ESLint |
pnpm lint:fix | Run ESLint with auto-fix |
pnpm format | Format code with Prettier |
pnpm format:check | Check code formatting |
Testing
| Command | Description |
|---|---|
pnpm test | Run tests in watch mode |
pnpm test:run | Run tests once |
pnpm test:coverage | Run tests with coverage report |
pnpm test:e2e | Run Playwright E2E tests |
pnpm test:e2e:ui | Run E2E tests with interactive UI |
pnpm test:e2e:report | Show E2E test report |
See Testing Guide for detailed testing documentation.
Project Structure
See Architecture for detailed structure documentation.
Key directories:
src/
├── core/ # Core types, config, context, map engine
├── adapters/ # Data source adapters
├── controls/ # UI control components
├── examples/ # Example custom renderers
├── hooks/ # Custom React hooks
├── ui/ # Shared UI components
├── lib/ # Utility functions
└── app/ # Next.js pages and API routesDevelopment Workflow
Making Changes
-
Create a feature branch:
git checkout -b feature/my-feature -
Make your changes
-
Run checks before committing:
pnpm type-check pnpm lint -
Commit with a descriptive message:
git commit -m "feat: add new filter option" -
Push and create a pull request
Branch Naming
feature/- New featuresfix/- Bug fixesdocs/- Documentation updatesrefactor/- Code refactoringchore/- Maintenance tasks
Commit Messages
Follow Conventional Commits:
feat:- New featurefix:- Bug fixdocs:- Documentationstyle:- Formatting (no code change)refactor:- Code restructuringtest:- Adding testschore:- Maintenance
Working with Data
Demo Data
The demo data is in public/data/demo.json. To test with different data:
- Create a new JSON file following the schema in Data Specification
- Update
map.config.tsto point to your file:
dataSources: [
{
id: 'my-data',
name: 'My Test Data',
adapter: 'static',
adapterConfig: { path: '/data/my-data.json' },
enabled: true,
},
],Using Supabase
To develop with a Supabase database:
- Create a Supabase project
- Run the migrations from
db/(if using the mono-repo schema) - Add credentials to
.env.local:
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...- Update
map.config.ts:
dataSources: [
{
id: 'supabase',
name: 'My Database',
adapter: 'supabase',
adapterConfig: {},
enabled: true,
},
],Adding Features
Adding a New Control
- Create the component:
'use client';
import { useData } from '@/core';
interface MyControlProps {
// props
}
export function MyControl({ }: MyControlProps) {
const { incidents, filters } = useData();
return (
<div className="bg-card/95 backdrop-blur-md border border-border rounded-lg p-4">
{/* Your control UI */}
</div>
);
}
export default MyControl;- Export from controls index:
export { MyControl } from './my-control';- Add to page.tsx:
import { MyControl } from '@/controls/my-control';
// In the JSX:
<MyControl />- Add config option (optional):
controls: {
myControl: { enabled: true, position: 'sidebar' },
}Adding a Data Adapter
- Create the adapter:
import type { DataAdapter, FilterOptions } from './types';
import type { Incident, IncidentFilters, DataSourceConfig } from '@disclosureos/sightline-core';
import { applyIncidentFilters } from './utils';
export class MyAdapter implements DataAdapter {
readonly id: string;
readonly name: string;
private endpoint: string;
constructor(config: DataSourceConfig) {
this.id = config.id;
this.name = config.name;
this.endpoint = config.adapterConfig.endpoint as string;
}
async getIncidents(filters?: IncidentFilters): Promise<Incident[]> {
const response = await fetch(this.endpoint);
const data = await response.json();
// Transform to Incident format
const incidents = data.map((item: any) => ({
id: item.id,
location: { name: item.location, country: item.country },
// ... map all fields
}));
// Apply filters if provided
return filters ? applyIncidentFilters(incidents, filters) : incidents;
}
async getIncidentById(id: string): Promise<Incident | null> {
const incidents = await this.getIncidents();
return incidents.find((i) => i.id === id) || null;
}
async getFilterOptions(): Promise<FilterOptions> {
const incidents = await this.getIncidents();
return {
countries: [...new Set(incidents.map((i) => i.location.country))].toSorted(),
siteTypes: [...new Set(incidents.map((i) => i.location.siteType))].toSorted(),
// Note: Check both location and classification for sensitivity
locationSensitivities: [
...new Set(incidents.map((i) =>
i.location.locationSensitivity || i.classification?.locationSensitivity
).filter(Boolean))
],
incidentTypes: [
...new Set(incidents.map((i) => i.classification?.incidentType).filter(Boolean))
],
};
}
}- Register in factory:
import { MyAdapter } from './my-adapter';
export function createAdapter(config: DataSourceConfig): DataAdapter {
switch (config.adapter) {
case 'my-adapter':
return new MyAdapter(config);
// ... other cases
}
}- Use in config:
dataSources: [
{
id: 'my-source',
name: 'My Source',
adapter: 'my-adapter',
adapterConfig: { endpoint: 'https://api.example.com' },
enabled: true,
},
],Adding Custom Renderers
You can customize how incidents appear by providing render props:
import { MapEngine } from '@/core';
import { IncidentFeed } from '@/controls';
{/* Map with custom renderers */}
<MapEngine
incidents={filteredIncidents}
selectedIncident={selectedIncident}
onIncidentSelect={setSelectedIncident}
currentDate={currentDate}
sidebarOpen={sidebarOpen}
renderPopup={(props) => <CustomPopup {...props} />}
renderMarker={(props) => <CustomMarker {...props} />}
renderCluster={(props) => <CustomCluster {...props} />}
/>
{/* Feed with custom item renderer */}
<IncidentFeed
incidents={filteredIncidents}
selectedIncident={selectedIncident}
onIncidentSelect={setSelectedIncident}
currentDate={currentDate}
renderFeedItem={(props) => <CustomFeedItem {...props} />}
/>See src/examples/renderers/ for example implementations.
Debugging
Browser DevTools
- React DevTools - Inspect component tree and state
- Network tab - Monitor API calls
- Console - Check for errors and logs
Common Issues
Map not loading:
- Check Mapbox token is set in
.env.local - Check browser console for Mapbox errors
- Verify token has correct scopes
Data not appearing:
- Check Network tab for fetch errors
- Verify JSON format matches DATA_SPEC.md
- Check browser console for parsing errors
Type errors:
- Run
pnpm type-checkto see all errors - Ensure imports match exports
- Check interface definitions in
core/types.ts
Code Style
TypeScript
- Use strict mode (enabled in tsconfig)
- Prefer interfaces over type aliases for objects
- Use explicit return types for exported functions
- Avoid
any- useunknownif type is uncertain
React
- Use functional components with hooks
- Prefer composition over inheritance
- Keep components focused (single responsibility)
- Use
useCallbackfor functions passed as props - Use
useMemofor expensive computations
CSS/Tailwind
- Use Tailwind utilities over custom CSS
- Follow existing patterns for consistency
- Use CSS variables for theme colors
- Keep responsive design in mind (
sm:,md:, etc.)
IDE Setup
VS Code (Recommended)
Install these extensions:
- ESLint
- Tailwind CSS IntelliSense
- TypeScript and JavaScript Language Features
Workspace settings:
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"typescript.tsdk": "node_modules/typescript/lib"
}Related
- Architecture Overview — System design and structure
- Testing Guide — How to write and run tests
- Style Guide — Code style conventions
- Troubleshooting — Common development issues
- Environment Variables — All env vars
- Performance Guide — Optimization tips