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.
Browser ─────▶ Next.js API routes :3000 Next.js · Redux │ Remotion Player │ ┌──────────────┬────┴──────────────┐ ▼ ▼ ▼ server :8000 video-editor :7001 Postgres Flask Flask · pipeline pgvector │ │ ▲ │ └─────┘ ▼ remotion-renderer :7002 ──▶ Blob storage Node · Remotion · Chromium
@stringer/video ····▶ Browser · video-editor · remotion-rendererSolid lines are calls over the network. The dotted one is a package import: the same TypeScript, compiled into three different runtimes.
Run 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.