Implementation-oriented setup and workflow details for AfterFrame contributors.
apps/desktop/ Electron + React desktop app (Vite, Tailwind)
services/sidecar/ Python backend — catalog, metadata, AI repaint, background jobs
tests/ Smoke tests and workflow checks
data/ Sample catalogs and working datasets
RESOURCES/ AI style prompt libraries, design mockups
docs/ Screenshots and developer docs
AfterFrame is an Electron app with a Python sidecar process:
- Frontend: React (JSX, no TypeScript), Tailwind CSS, Vite bundler
- Backend: Python CLI invoked via
child_processfrom Electron main process - Storage: SQLite database inside
.afcatalogbundles - IPC: Electron main ↔ renderer via
ipcMain.handle/ipcRenderer.invoke - Sidecar communication: JSON over stdout, one CLI invocation per request
The catalog (.afcatalog directory) is the primary data object. It contains the SQLite database, preview caches, job logs, and derived artifacts. Source images are never copied — the catalog stores references to files on disk.
cd apps/desktop
npm install
npm startBy default, the app opens the last used catalog. To force a specific catalog:
MEDIA_WORKSPACE_CATALOG=../../data/default.afcatalog npm startRun the standard repository gate from the repository root:
npm run check # lint + Python/Node/Renderer unit tests + production build
npm test # all fast unit tests
npm run test:e2e # full Electron Playwright suite (macOS)The root commands set the sidecar PYTHONPATH automatically. ESLint's checked-in
bulk-suppression file records existing warnings by file and rule, so new lint or
React Hooks violations still fail locally and in CI.
The desktop app calls the sidecar CLI automatically. For manual testing:
PYTHONPATH=services/sidecar/src python3 -m media_workspace --catalog data/default.afcatalog browse-exports --limit 10Common commands:
# Initialize a new catalog
python3 -m media_workspace init-catalog --catalog data/new.afcatalog
# Scan RAW files
python3 -m media_workspace scan-raw --catalog data/default.afcatalog --raw-dir /path/to/raws
# Generate previews
python3 -m media_workspace generate-previews --catalog data/default.afcatalog --kind preview --asset-type export# 1. Build sidecar binary
cd services/sidecar
pyinstaller media-workspace.spec --distpath dist --noconfirm
# 2. Package desktop app
cd apps/desktop
npm run dist:macOutput: apps/desktop/release/AfterFrame-<version>-arm64.dmg
- SQLite with
assets,asset_files,collections,jobstables app_ratingpopulated from Lightroom XMP ratings on import- Server-side sorting and pagination for virtual-scroll gallery
- EXIF parsing via Pillow
- XMP rating extraction from embedded XML
- Camera, lens, exposure, GPS metadata
- BYOK model — user provides their own API keys
- Supports Gemini, OpenAI, Jimeng, and OpenAI-compatible endpoints
- API keys stored encrypted via Electron safeStorage
- Style prompts stored in
~/Library/Application Support/afterframe/ai-styles.json
- Import pipeline: index images → extract metadata → match RAW sources
- Enrichment: backfill full metadata for scanned assets
- Preview generation: cached thumbnails inside the catalog
Global settings (not per-catalog):
~/Library/Application Support/afterframe/settings.json— theme, sidebar width, last catalog path, AI provider config~/Library/Application Support/afterframe/ai-styles.json— AI repaint style prompts
services/sidecar/src/media_workspace/schema.py is the only source of the
catalog schema version. When changing persistent structure:
- Increment
SCHEMA_VERSION. - Add the next ordered migration to
db/migrations.py. - Add an old-catalog upgrade case to
tests/test_schema_migrations.py.
All migrations run in one transaction. Do not update catalog_info.schema_version
from feature/domain modules or rely only on CREATE TABLE IF NOT EXISTS for an
existing table change.
- The app is not code-signed. Users need to bypass Gatekeeper on first launch.
- Only Apple Silicon builds are tested. Intel may work but is not verified.
- The sidecar binary is bundled inside the
.appvia electron-builderextraResources.