4.7 KiB
AGENTS.md
Overview
Privacy-first video editor, with a focus on simplicity and ease of use.
Lib vs Utils
lib/- domain logic (specific to this app)utils/- small helper utils (generic, could be copy-pasted into any other app)
Core Editor System
The editor uses a singleton EditorCore that manages all editor state through specialized managers.
Architecture
EditorCore (singleton)
├── playback: PlaybackManager
├── timeline: TimelineManager
├── scene: SceneManager
├── project: ProjectManager
├── media: MediaManager
└── renderer: RendererManager
When to Use What
In React Components
Always use the useEditor() hook:
import { useEditor } from '@/hooks/use-editor';
function MyComponent() {
const editor = useEditor();
const tracks = editor.timeline.getTracks();
// Call methods
editor.timeline.addTrack({ type: 'media' });
// Display data (auto re-renders on changes)
return <div>{tracks.length} tracks</div>;
}
The hook:
- Returns the singleton instance
- Subscribes to all manager changes
- Automatically re-renders when state changes
Outside React Components
Use EditorCore.getInstance() directly:
// In utilities, event handlers, or non-React code
import { EditorCore } from "@/core";
const editor = EditorCore.getInstance();
await editor.export({ format: "mp4", quality: "high" });
Actions System
Actions are the trigger layer for user-initiated operations. The single source of truth is @/lib/actions/definitions.ts.
To add a new action:
- Add it to
ACTIONSin@/lib/actions/definitions.ts:
export const ACTIONS = {
"my-action": {
description: "What the action does",
category: "editing",
defaultShortcuts: ["ctrl+m"],
},
// ...
};
- Add handler in
@/hooks/use-editor-actions.ts:
useActionHandler(
"my-action",
() => {
// implementation
},
undefined,
);
In components, use invokeAction() for user-triggered operations:
import { invokeAction } from '@/lib/actions';
// Good - uses action system
const handleSplit = () => invokeAction("split-selected");
// Avoid - bypasses UX layer (toasts, validation feedback)
const handleSplit = () => editor.timeline.splitElements({ ... });
Direct editor.xxx() calls are for internal use (commands, tests, complex multi-step operations).
Commands System
Commands handle undo/redo. They live in @/lib/commands/ organized by domain (timeline, media, scene).
Each command extends Command from @/lib/commands/base-command and implements:
execute()- saves current state, then does the mutationundo()- restores the saved state
Actions and commands work together: actions are "what triggered this", commands are "how to do it (and undo it)".
Cloud-specific instructions
Prerequisites
- Bun (v1.2.18) must be installed. The update script handles this via
~/.bun/bin/bun. - No Docker required for frontend-only development. Docker (PostgreSQL + Redis) is only needed for auth features, and no
docker-compose.yamlexists in the repo yet.
Environment variables
All env vars are validated by Zod at startup (packages/env/src/web.ts). Every field is required (no .optional()), so .env.local must exist under apps/web/ with valid-looking values for all variables. For local development without external services, placeholder values work for: MARBLE_WORKSPACE_KEY, FREESOUND_CLIENT_ID, FREESOUND_API_KEY, CLOUDFLARE_ACCOUNT_ID, R2_*, and MODAL_TRANSCRIPTION_URL (must be a valid URL like http://localhost:9999/transcription).
Running services
| Service | Command | Notes |
|---|---|---|
| Dev server | bun run dev (from apps/web) |
Runs Next.js 16 with Turbopack on port 3000 |
| Lint | bun run lint (from apps/web) |
Uses Biome. Pre-existing CSS/Tailwind directive warnings are expected. |
| Tests | bun test (from repo root) |
60 tests across 7 files (storage migrations + sticker-id) |
| Format | bun run format (from apps/web) |
Biome formatter |
Gotchas
@biomejs/biomeis a root devDependency needed for the lint/format scripts inapps/web. The lint script callsbiomedirectly.- The env schema at
packages/env/src/web.tsusesz.url()for several fields (NEXT_PUBLIC_SITE_URL,NEXT_PUBLIC_MARBLE_API_URL,UPSTASH_REDIS_REST_URL,MODAL_TRANSCRIPTION_URL), meaning they must be full valid URLs (with protocol). Plain strings will fail validation. - The repo's README references
docker-compose up -dbut nodocker-compose.yamlexists. Auth features (login, signup) won't work without PostgreSQL and Redis.