Developer overview
Editory is a monorepo of six services around one shared package. This page is the map; it’s enough to get the stack running and to know which service owns the thing you’re about to change.
The services
Section titled “The services”| Service | Port | Stack | Owns |
|---|---|---|---|
client |
3000 | Next.js 14, React 18, Redux Toolkit | Web app, API routes, auth, Studio chat, the MCP endpoint |
server |
8000 | Flask, LangGraph, Gemini, Azure OpenAI | Script generation, editing agents, fact-check, source discovery |
video-editor |
7001 | Flask, Whisper, MediaPipe, ONNX | The video pipeline, its AI agents, capability endpoints |
remotion-renderer |
7002 | Node 20, Remotion, Chromium | Bundling and encoding; uploads the result |
bias_calculator |
8080 | Flask, PyTorch | Bias scoring |
redis |
6379 | Redis | Cache and session state |
packages/video is the single source of truth for the video schema. The
same Zod-validated VideoDefinition is used by the editor UI, the pipeline and
the renderer — which is why it has to be built before anything else compiles.
flowchart TD Browser["Browser<br/><i>Next.js · Redux · Remotion Player</i>"] API["Next.js API routes<br/><i>:3000</i>"] Server["server<br/><i>Flask :8000</i>"] Editor["video-editor<br/><i>Flask :7001</i>"] Renderer["remotion-renderer<br/><i>Node :7002</i>"] Pkg["@stringer/video<br/><i>schema + components</i>"] DB[("Postgres<br/><i>pgvector</i>")] Blob[("Blob storage")]
Browser --> API API --> Server API --> Editor API --> DB Editor --> Renderer Editor --> DB Renderer --> Blob Pkg -.->|shared schema| Editor Pkg -.->|shared schema| Renderer Pkg -.->|shared schema| BrowserRun it locally
Section titled “Run it locally”The supported path is Docker. Give Docker Desktop at least 8 GB of RAM — the video services run Chromium and local ML models, and 4 GB fails in ways that look like random crashes.
-
Start the stack
Section titled “Start the stack”Terminal window docker compose --profile video up --buildThe
videoprofile is the one you want for any video work. Without it you get the web app, the Flask API and Redis; with it you also getvideo-editor,remotion-rendererandvideo-builder(a watcher that rebuilds@stringer/videoon change). -
Wait out the first build
Section titled “Wait out the first build”10–20 minutes.
remotion-rendererpulls a full Chromium andvideo-editordownloads the matting model weights. Every start after that is seconds. Hot reload is on for all services. -
Open the app
Section titled “Open the app”The app is at
http://localhost:3000.
Everyday commands
Section titled “Everyday commands”docker compose --profile video up -d # detacheddocker compose logs -f client # tail one servicedocker compose --profile video down # stopdocker compose --profile video down -v # ...and wipe volumesdocker compose build --no-cache video-editor # force-rebuild one imageWithout Docker
Section titled “Without Docker”Slower to set up, faster to iterate. Node 20 and Yarn 1 classic; install at the repo root because it’s a workspace monorepo.
yarn install --ignore-engines
# Build the shared package FIRST. Its dist/ is gitignored and both the client# and the renderer import from it — nothing compiles until this runs.yarn workspace @stringer/video build
yarn dev:video # terminal 1 — rebuild the package on changeyarn dev:client # terminal 2 — the web appyarn dev:renderer # terminal 3 — only if you're renderingPython services run on 3.11 (server, video-editor) and 3.9
(bias_calculator).
Testing
Section titled “Testing”Every change ships with tests. New routes, agents, utilities or schema fields without coverage get rejected at review.
scripts/test/run-tests.sh all # every service, ~8s wall-clockscripts/test/summary.sh # grep-friendly rollup
scripts/test/run-tests.sh clientscripts/test/run-tests.sh packages-videoscripts/test/run-tests.sh video-editorOutput is one line per service —
TESTS: <service> passed=X failed=Y skipped=Z duration=Ws status=PASS|FAIL —
with JUnit XML at <service>/test-results/junit.xml.
Where a new test belongs:
| You added | Test it in |
|---|---|
| A Next.js API route | client/src/pages/api/**/__tests__/ (mock the DB, fetch, storage) |
| A Flask route | A pytest route test via the Flask test client |
| A Python utility | The service’s own tests/ directory |
| A schema field or Zod type | packages/video/src/schema/__tests__/ — round-trip and integrity |
| A graphics category or Remotion component | Vitest in packages/video and remotion-renderer |
| An agent or agent node | A unit test with mocked LLM responses |
Branches and CI
Section titled “Branches and CI”feature/* ──PR──> development ──PR──> staging ──PR──> production (light gate) (build images) (deploy)The pipeline is change-detected — only services touched by the diff get tested and built.
| Trigger | What runs |
|---|---|
PR → development |
Detect + unit tests |
Push to development |
Nothing — the PR already validated the commit |
Push to staging |
Unit tests + image build + smoke, then push to the registry |
Push to production |
Retag the staging image and roll the resource |
Promotion doesn’t require a PR; pushing to staging or production triggers
the same pipeline.
A note on the name
Section titled “A note on the name”Editory grew out of Stringer, a consumer news platform. The repository, some
package names (@stringer/video, stringer-monorepo) and a few storage buckets
still carry the old name, and parts of that original codebase are still in the
tree but dormant — the consumer news reader, the bias pages, the scrapers under
jobs/, and everything under legacy/.
That’s cosmetic. The product is Editory.