Welcome to Flint! This guide will help you set up your development environment and understand the codebase structure.
- Prerequisites
- Initial Setup
- Project Structure Overview
- Development Workflow
- Understanding the Architecture
- Making Your First Changes
- Testing
- Common Tasks
- Troubleshooting
Before you begin, ensure you have:
- Node.js 18+ installed
- npm 9+ (comes with Node.js)
- Git for version control
- A code editor (VS Code recommended)
- Basic knowledge of:
- TypeScript
- Svelte (Svelte 5 preferred)
- Electron basics
git clone <repository-url>
cd flint-uinpm installThis will install all dependencies for the Electron app and the integrated note server.
# Run type checking
npm run typecheck
# Run linting
npm run lint
# Run tests
npm run test:runIf all commands pass, you're ready to develop!
npm run devThis will:
- Start the Electron application in development mode
- Enable hot reload for the Svelte UI
- Watch for changes in main process code
flint-ui/
├── src/
│ ├── main/ # Electron main process (Node.js)
│ ├── preload/ # IPC bridge (secure)
│ ├── renderer/ # Svelte UI (browser)
│ └── server/ # Note server library
├── tests/ # Vitest test suite
├── docs/ # Documentation
└── out/ # Build output (gitignored)
The main process is the heart of the Electron application:
Key Files:
index.ts- Application entry point, window management, IPC handlersai-service.ts- AI integration via Model Context Protocolnote-service.ts- Note CRUD operations wrappertool-service.ts- Agent tool systemworkflow-service.ts- Workflow automationstorage-service.ts- Base file storagesecure-storage-service.ts- API key managementvault-data-storage-service.ts- Vault-specific datasettings-storage-service.ts- Global settings
Service Initialization Order:
- Note Service
- Secure Storage Service
- AI Service (depends on Note Service and Secure Storage)
- Tool Service (depends on AI Service)
- Workflow Service
The preload script creates a secure bridge between main and renderer:
- Security: Uses
contextBridgeto expose limited API - Type Safety: Provides TypeScript interfaces
- IPC Methods: Exposes all backend functionality to renderer
The Svelte 5 UI application:
Directory Structure:
renderer/src/
├── components/ # Svelte components
├── stores/ # Reactive state management
├── services/ # Frontend service layer
├── utils/ # Utility functions
├── assets/ # CSS, fonts, images
├── config/ # Configuration files
└── lib/ # Shared libraries
Key Components:
App.svelte- Root componentLeftSidebar.svelte- NavigationMainView.svelte- Note editorRightSidebar.svelte- AI assistantAgent.svelte- Chat interfaceCodeMirrorEditor.svelte- Note editor
Key Stores (Svelte 5 Runes):
notesStore.svelte.ts- Note data (not persisted)activeNoteStore.svelte.ts- Active note trackingunifiedChatStore.svelte.ts- AI conversationstemporaryTabsStore.svelte.ts- Arc-style tabsworkflowStore.svelte.ts- Workflow statecustomFunctionsStore.svelte.ts- User-defined functionssettingsStore.svelte.ts- App settingsmodelStore.svelte.ts- AI model selection
The integrated note server library:
server/
├── api/ # FlintNoteApi class
├── core/ # Core business logic
│ ├── note-manager.ts
│ ├── note-type-manager.ts
│ └── workspace-manager.ts
├── database/ # SQLite operations
└── types/ # TypeScript definitions
Flint uses Svelte 5's latest features. Here are the key patterns:
// ✅ Correct - Modern Svelte 5
let count = $state(0);
let doubled = $derived(count * 2);
$effect(() => {
console.log('Count changed:', count);
});
// ❌ Avoid - Legacy Svelte 4
import { writable } from 'svelte/store';
const count = writable(0);// ✅ Correct - Modern Svelte 5
let { activeNote, onClose }: Props = $props();
// ❌ Avoid - Legacy Svelte 4
export let activeNote;
export let onClose;// ✅ Correct - Native events
<button onclick={handleClick}>Click</button>
// ❌ Avoid - Legacy syntax
<button on:click={handleClick}>Click</button>// ✅ Correct - Props for callbacks
interface Props {
onSave: (data: string) => void;
}
let { onSave }: Props = $props();
// ❌ Avoid - createEventDispatcher
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();Always use $state.snapshot() when sending reactive data through IPC:
// ✅ Correct
const data = $state({ notes: [], active: null });
await window.api?.saveData($state.snapshot(data));
// ❌ Wrong - Will fail with "object could not be cloned"
await window.api?.saveData(data);- Svelte Components:
ComponentName.svelte - TypeScript Files:
fileName.ts - Svelte TypeScript Files:
fileName.svelte.ts(can use runes) - Test Files:
fileName.test.tsorfileName.spec.ts
Always format before committing:
npm run formatThis ensures consistent code style across the project.
┌─────────────────────────────────────────────────┐
│ Renderer Process (Svelte) │
│ ┌──────────┐ ┌────────┐ ┌─────────────────┐ │
│ │Components│→ │ Stores │→ │ Frontend Services│ │
│ └──────────┘ └────────┘ └─────────────────┘ │
└───────────────────────┬─────────────────────────┘
│ IPC (via preload)
▼
┌─────────────────────────────────────────────────┐
│ Main Process (Node.js) │
│ ┌──────────────┐ ┌──────────────────────────┐ │
│ │ IPC Handlers│→ │ Services (AI, Note, etc.)│ │
│ └──────────────┘ └──────────────────────────┘ │
└───────────────────────┬─────────────────────────┘
│
▼
┌─────────────────────────────────────────────────┐
│ External Services │
│ ┌──────────────┐ ┌───────────┐ ┌──────────┐ │
│ │ AI Providers │ │ File System│ │ SQLite │ │
│ └──────────────┘ └───────────┘ └──────────┘ │
└─────────────────────────────────────────────────┘
All application state is persisted to the file system:
File System Structure:
{userData}/
├── secure/ # Encrypted API keys
├── settings/ # Global settings
│ ├── app-settings.json
│ ├── model-preferences.json
│ └── slash-commands.json
└── vault-data/ # Vault-specific data
├── default/
│ ├── conversations.json
│ ├── temporary-tabs.json
│ └── active-note.json
└── vault-{id}/
└── ...
Store Pattern:
class PersistedStore {
private data = $state(defaultData);
private isLoading = $state(true);
constructor() {
this.initialize();
}
private async initialize() {
this.isLoading = true;
const stored = await window.api?.loadData();
if (stored) this.data = stored;
this.isLoading = false;
}
async update(newData) {
this.data = newData;
await window.api?.saveData($state.snapshot(newData));
}
}- Create the component
touch src/renderer/src/components/MyNewComponent.svelte- Implement using Svelte 5
<script lang="ts">
interface Props {
title: string;
onAction: () => void;
}
let { title, onAction }: Props = $props();
let count = $state(0);
</script>
<div class="my-component">
<h2>{title}</h2>
<p>Count: {count}</p>
<button onclick={() => count++}>Increment</button>
<button onclick={onAction}>Action</button>
</div>
<style>
.my-component {
padding: 1rem;
}
</style>- Use in parent component
<script lang="ts">
import MyNewComponent from './MyNewComponent.svelte';
</script>
<MyNewComponent title="Hello" onAction={() => console.log('Action!')} />- Add handler in main process (
src/main/index.ts)
ipcMain.handle('my-new-operation', async (_event, data: string) => {
// Process data
const result = await someService.process(data);
return result;
});- Expose in preload (
src/preload/index.ts)
const api = {
// ... existing methods
myNewOperation: (data: string): Promise<Result> =>
ipcRenderer.invoke('my-new-operation', data)
};- Use in renderer
const result = await window.api?.myNewOperation('some data');Flint uses Vitest for testing:
# Watch mode (interactive)
npm run test
# Single run with coverage
npm run test:runTests are located in tests/ directory:
// tests/server/core/note-manager.test.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { NoteManager } from '../../../src/server/core/note-manager';
describe('NoteManager', () => {
let noteManager: NoteManager;
beforeEach(async () => {
// Setup isolated test environment
noteManager = new NoteManager(testWorkspace);
await noteManager.initialize();
});
it('should create a note', async () => {
const note = await noteManager.createNote({
type: 'general',
title: 'test-note',
content: '# Test'
});
expect(note.id).toBe('general/test-note.md');
expect(note.title).toBe('test-note');
});
});TestApiSetup- Provides isolated test environments- Automatic cleanup of temporary files and databases
- Global test functions (no imports needed)
- Plan the feature - Document in
docs/if significant - Create necessary files - Components, stores, services
- Implement backend - Add services, IPC handlers if needed
- Implement frontend - Create UI components
- Add tests - Unit tests for logic, integration tests for flows
- Update documentation - Update relevant docs
- Test manually - Run the app and verify
- Run checks -
npm run check
Use Chrome DevTools:
- Press
Cmd+Option+I(macOS) orCtrl+Shift+I(Windows/Linux) - Or add
debugger;statements in renderer code
Add breakpoints in VS Code:
- Add
"node": ["--inspect=5858"]toelectron-vite.config.ts - Use VS Code's debugger with Node.js configuration
- Or use
console.log()statements
When changing the database schema:
- Create migration in
src/server/database/migration-manager.ts - Increment version number
- Test migration with existing data
- Document breaking changes
Example migration:
{
version: 5,
name: 'add-new-column',
up: async (db) => {
await db.run('ALTER TABLE notes ADD COLUMN new_field TEXT');
},
down: async (db) => {
// Optional: rollback logic
}
}Workflows are AI-assisted automation sequences:
Location: src/main/workflow-service.ts
Frontend Store: src/renderer/src/stores/workflowStore.svelte.ts
Example Workflow:
{
id: 'daily-review',
name: 'Daily Review',
description: 'Review and summarize today\'s notes',
steps: [
{ type: 'search', query: 'created:today' },
{ type: 'ai-process', instruction: 'Summarize these notes' },
{ type: 'create-note', type: 'summary' }
]
}Users can define JavaScript functions for AI agents:
Location: src/main/tool-service.ts
Frontend Store: src/renderer/src/stores/customFunctionsStore.svelte.ts
Functions are evaluated in a secure sandbox using QuickJS.
Problem: TypeScript errors after pulling changes
# Clean and reinstall
npm run clean
rm -rf node_modules
npm installProblem: Electron won't start
# Rebuild native modules
npm rebuildProblem: IPC methods not working
- Check preload script exposes the method
- Verify IPC handler exists in main process
- Check for typos in method names
Problem: State not persisting
- Ensure you're using
$state.snapshot()for IPC - Check file permissions in userData directory
- Verify storage service is initialized
Problem: AI features not working
- Check API keys are set in settings
- Verify MCP server is running (check logs)
- Ensure note service initialized before AI service
- Check documentation -
docs/directory - Read code comments - Many files have detailed comments
- Search issues - GitHub issues may have answers
- Ask questions - GitHub Discussions or team chat
Now that you're set up:
- Explore the codebase - Read through key files mentioned above
- Read architecture docs -
docs/architecture/directory - Try making small changes - Fix a bug or add a small feature
- Read existing code - Learn patterns from well-written components
- Contribute - Pick up an issue labeled "good first issue"
- Architecture - System architecture
- Design - UI design guidelines
- Svelte 5 Docs - Official Svelte documentation
- Electron Docs - Official Electron documentation
Welcome to Flint development! We're excited to have you here. 🎉