This document provides guidance for AI agents working on the Outline codebase.
Outline is a fast, collaborative knowledge base built for teams. It is a TypeScript monorepo containing:
- Frontend: React web application with MobX state management
- Backend: Koa API server with Sequelize ORM, PostgreSQL, and Redis
- Real-time: WebSocket-based collaboration using Y.js
- Editor: Prosemirror-based rich text editor
| Package | Purpose |
|---|---|
react |
UI framework |
mobx / mobx-react |
State management |
koa |
Backend web framework |
sequelize |
PostgreSQL ORM |
ioredis |
Redis client |
prosemirror-* |
Rich text editor |
yjs / y-prosemirror |
Real-time collaboration |
styled-components |
CSS-in-JS styling |
zod |
Schema validation |
bull |
Background job queue |
The frontend is a React application compiled with Vite, using MobX for state management and styled-components for styling.
| Directory | Purpose |
|---|---|
app/actions/ |
Reusable actions (navigating, opening, creating) |
app/components/ |
Reusable UI components |
app/editor/ |
Editor-specific React components |
app/hooks/ |
Custom React hooks |
app/menus/ |
Context menus |
app/models/ |
MobX observable state models |
app/routes/ |
Route definitions (async loaded with suspense) |
app/scenes/ |
Full-page view components |
app/stores/ |
Collections of models and fetch logic |
app/types/ |
TypeScript types |
app/utils/ |
Frontend utilities |
The API server is built on Koa with Sequelize ORM. Authorization uses cancan policies, and background jobs are managed with Bull queues.
| Directory | Purpose |
|---|---|
server/routes/api/ |
API route handlers |
server/routes/auth/ |
Authentication routes |
server/commands/ |
Business logic commands |
server/config/ |
Database configuration |
server/emails/ |
Transactional email templates |
server/middlewares/ |
Koa middleware |
server/migrations/ |
Database migrations |
server/models/ |
Sequelize database models |
server/onboarding/ |
Onboarding document templates |
server/policies/ |
Authorization logic (cancan) |
server/presenters/ |
API response formatters |
server/queues/ |
Async queue definitions |
server/services/ |
Application service definitions |
server/test/ |
Test helpers and fixtures |
server/utils/ |
Backend utilities |
Code shared between frontend and backend.
| Directory | Purpose |
|---|---|
shared/components/ |
Shared React components |
shared/editor/ |
Prosemirror editor components |
shared/i18n/ |
Internationalization |
shared/styles/ |
Global styles and colors |
shared/utils/ |
Shared utility methods |
shared/types.ts |
Common TypeScript types |
shared/validations.ts |
Validation schemas |
Plugin system for extending functionality.
- No
anytype: Avoid usingany; use proper types or generics - Avoid
unknown: Only use when absolutely necessary - Prefer
interface: Useinterfaceovertypefor object shapes - Avoid type assertions: Minimize use of
asand!operators - Strict null checks: Always handle null/undefined cases
- Consistent type imports: Use
import typefor type-only imports
// ✓ Correct - use consistent type imports
import type { User } from "@server/models/User";
// ✗ Incorrect
import { User } from "@server/models/User"; // when only using as a typeUse path aliases instead of relative imports:
// ✓ Correct
import { User } from "@server/models/User";
import { formatDate } from "@shared/utils/date";
import { Button } from "~/components/Button";
// ✗ Incorrect - avoid deep relative imports
import { User } from "../../../server/models/User";| Alias | Maps To |
|---|---|
@server/* |
./server/* |
@shared/* |
./shared/* |
~/* |
./app/* |
// ✗ Wrong - mutating observable directly
user.name = "New Name";
// ✓ Correct - use MobX action
@action
updateName(name: string) {
this.name = name;
}- Always use
@actiondecorators for state mutations - Use
@computedfor derived state - Keep stores focused and single-purpose
- Co-locate state logic with components when not global
- Functional components: Always use functional components with hooks
- Event handler naming: Prefix with "handle" (e.g.,
handleClick,handleSubmit) - Styling: Use styled-components for component styles
- No React import: JSX transform is enabled, no need to import React
- Performance: Use
React.memo,useMemo,useCallbackto avoid unnecessary re-renders - Self-closing tags: Always use self-closing tags for empty elements
// ✗ Wrong - unnecessary React import
import React from "react";
function Component() {
return <div>Hello</div>;
}
// ✓ Correct - no React import needed with JSX transform
function Component() {
return <div>Hello</div>;
}// ✗ Wrong - non-self-closing empty element
<div className="spacer"></div>
// ✓ Correct - self-closing
<div className="spacer" />- Validation: Use validation middleware for request data
- Presenters: Always format API responses through presenters
- Policies: Check authorization via cancan policies
- Error handling: Handle errors gracefully with proper error types
- Commands: Use commands for complex business logic across models
- Editor components are in
shared/editor/ - Use Y.js for real-time collaboration
- Follow existing node and mark patterns
Tests use Jest with the following coverage thresholds:
| Metric | Threshold |
|---|---|
| Statements | 50% |
| Branches | 40% |
| Functions | 50% |
| Lines | 50% |
Tests are colocated with source files using .test.ts or .test.tsx extension:
server/models/User.ts
server/models/User.test.ts
Do not create new test directories - tests belong next to their source files.
# Run a specific test file (preferred)
yarn test path/to/file.test.ts
# Run all tests
yarn test
# Run test suites
yarn test:app # Frontend tests (jsdom environment)
yarn test:server # Backend tests (node environment)
yarn test:shared # Shared code tests (both environments)- Use Jest for all tests
- Mock external dependencies in
__mocks__/folders - Focus on critical paths and business logic
- Frontend tests run in jsdom environment
- Backend tests run in node environment
- Shared tests run in both environments
| Change Type | Testing Requirements |
|---|---|
| New API endpoint | Unit tests for route handler, integration tests |
| New React component | Component rendering tests, interaction tests |
| Business logic | Unit tests for commands/utilities |
| Database model | Model validation tests, association tests |
| Bug fix | Regression test proving the fix |
- No use of
anytype - Avoid
unknownunless necessary - Prefer
interfaceovertypefor object shapes - Avoid type assertions (
as,!) - Strict null checks are respected
- Consistent type imports used
- Use functional components with hooks
- Event handlers prefixed with "handle"
- Use styled-components for styling
- Ensure accessibility (ARIA roles, semantic HTML)
- Avoid unnecessary re-renders
- Self-closing tags for empty elements
- Use early returns for readability
- Always use curly braces for if statements
- Named exports for components and classes
- JSDoc for all public/exported functions
- No
console.login production code - Arrow body style follows "as-needed" convention
- Strict equality (
===) used instead of loose equality
- Validate request data with validation middleware
- Use presenters for response formatting
- Check user authorization via policies
- Handle errors gracefully
- Migrations are backward compatible
- Rollback migration is provided
- Indexes added for frequently queried columns
- Foreign key constraints properly defined
- Tests added for new functionality
- Coverage thresholds maintained
- Tests colocated with source files
// ✗ Wrong - mutating observable directly
user.name = "New Name";
// ✓ Correct - use MobX action
@action
updateName(name: string) {
this.name = name;
}// ✗ Wrong - unhandled promise
async function fetchData() {
const data = await api.get("/data");
return data;
}
// ✓ Correct - proper error handling
async function fetchData() {
try {
const data = await api.get("/data");
return data;
} catch (error) {
Logger.error("Failed to fetch data", error);
throw error;
}
}// ✗ Wrong - unnecessary React import
import React from "react";
function Component() {
return <div>Hello</div>;
}
// ✓ Correct - no React import needed with JSX transform
function Component() {
return <div>Hello</div>;
}// ✗ Wrong
import { helper } from "../../../shared/utils/helper";
// ✓ Correct
import { helper } from "@shared/utils/helper";// ✗ Wrong - no documentation
export function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
// ✓ Correct - proper JSDoc
/**
* Calculates the total price of all items.
*
* @param items - the items to sum.
* @returns the total price.
*/
export function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}// ✗ Wrong - loose equality
if (value == null) { ... }
// ✓ Correct - strict equality
if (value === null || value === undefined) { ... }// ✗ Wrong - no curly braces
if (condition) return value;
// ✓ Correct - always use curly braces
if (condition) {
return value;
}// ✗ Wrong
<div className="spacer"></div>
<Component prop="value"></Component>
// ✓ Correct
<div className="spacer" />
<Component prop="value" />- Node.js (>=20.12 <21 or 22)
- Yarn 4.x (package manager)
- PostgreSQL
- Redis
# Install dependencies
yarn install
# Start development server (frontend + backend)
yarn dev:watch
# Run linting
yarn lint
# Format code
yarn format
# Type check
yarn tsc
# Run all tests
yarn test
# Database migrations
yarn db:migrate # Run migrations
yarn db:rollback # Rollback last migration
yarn db:create-migration # Create new migrationUse conventional commit format:
feat: Add new featurefix: Resolve bug in componentrefactor: Improve code structuredocs: Update documentationtest: Add missing testschore: Update dependencies
Include:
- Summary: Brief description of changes
- Motivation: Why this change is needed
- Testing: How the changes were tested
- Screenshots: For UI changes (if applicable)
- Breaking Changes: Note any breaking changes
- Document breaking changes clearly in PR description
- Update migration guides if needed
- Ensure database migrations are backward compatible
- Consider feature flags for gradual rollout
- See
docs/ARCHITECTURE.mdfor detailed system architecture - API documentation: https://getoutline.com/developers
- Run
yarn lintbefore committing to catch issues early