Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Chalk - agentic whiteboard explainer videos

Chalk turns one educational prompt into a narrated, hand-drawn explainer video. A set of AI agents researches and scripts the lesson, designs a diagram for each scene, chooses or generates matching illustrations, and hands a typed scene plan to a deterministic renderer. The renderer owns placement, collision repair, animation timing, audio synchronization, HLS preview, and the final downloadable MP4.

Chalk prompt-to-video studio

Zerops-hosted architecture

In the submission topology, Zerops runs the two application workloads that make Chalk usable: the public web application and the CPU/memory-intensive renderer. The browser never contacts the renderer directly, and the renderer has no public port. Both sides coordinate through Convex so the web tier can remain stateless and the worker can perform long-running jobs without holding open a browser request.

flowchart LR
    Browser["Judge or learner<br/>desktop / mobile browser"]

    subgraph Zerops["Zerops project: chalk-zerops-challenge"]
        direction TB
        Web["chalkweb<br/>Next.js 16 + React 19<br/>public HTTPS, port 5173"]
        subgraph WorkerService["chalkworker: private Zerops service"]
            direction TB
            Poller["Outbound queue poller<br/>protocol + claims + lease heartbeats"]
            Engine["Video engine<br/>agents + Kokoro/assets + SVG/Sharp + FFmpeg"]
            Poller --> Engine
        end
    end

    Convex["Convex production<br/>auth + database + queue + subscriptions + media storage"]
    Providers["Model providers<br/>Google Cloud OR NVIDIA + Cloudflare"]

    Browser -->|"pages and application assets"| Web
    Browser <-->|"sign-in, queries, mutations, live progress, protected media"| Convex
    Web -->|"server-side /health probe"| Convex
    Poller -->|"outbound protocol check, poll, heartbeat, progress and uploads"| Convex
    Convex -->|"authenticated responses, claimed job data and upload URLs"| Poller
    Engine <-->|"text, vision, TTS or embedding requests"| Providers
    Engine -->|"HLS segments, MP4, WebVTT, thumbnail and timings"| Poller
Loading
Component Where it runs Exposure Responsibility
chalkweb Zerops Node.js 20 Public HTTPS Serves /, /chalk, /showcase, and /health; ships the React studio; identifies the deployed release; probes Convex before Zerops marks it healthy.
chalkworker Zerops Node.js 20 No public port Polls and claims leased jobs, runs agents and local media processing, uploads progressive/final artifacts, and reports progress or safe failures.
Convex Convex Cloud Public health + authenticated APIs Owns identity, job state, leases, subscriptions, ownership checks, protected HLS metadata, final file references, and watchdog recovery.
Providers External APIs Worker egress only Supply text/vision and, in Google mode, narration/embeddings. Provider credentials never enter the browser or chalkweb.

The reviewed deployment manifests are zerops.yaml and zerops-project-import.yaml. The import creates a free Lightweight-core project with the two service names expected by zerops.yaml; the latter defines exactly how each service is built, packaged, started, and monitored.

Public application routes

Route Access Purpose
/ Public Product explanation, generated sample, captions, and entry points to the studio or showcase.
/showcase Public Backend-independent, no-sign-in fallback containing a checked-in captioned lesson, transcript, architecture evidence, and download.
/chalk Sign-in required Reactive studio, prompt submission, progress, protected playback, personal history, owner-only Resume/Regenerate/Delete, transcript, and final MP4 download.
/health Public probe Zerops readiness response containing platform/release identity, generation mode, Convex reachability, and worker-protocol compatibility. It is not worker liveness.

Generated example

This is the same water-cycle video embedded on Chalk's landing page. It was generated by Chalk's production pipeline, not assembled in an editor.

Watch Chalk's generated water-cycle explainer

Download or watch the MP4 directly, or inspect its WebVTT captions.

Project story

Inspiration

Most generative-video demos optimize for cinematic shots, advertisements, or short visual effects. Educational video has a different job: it must preserve a coherent explanation, reveal ideas in the order they are taught, keep labels readable, and show relationships accurately. A visually impressive clip is not useful if a learner cannot follow the concept.

I love building educational tools, and whenever a new technology appears I ask how it can improve learning. After seeing impressive AI-generated ads and motion graphics that were not designed for teaching, I wanted to build an educational video agent that could create a lesson people could genuinely learn from while keeping the final presentation deterministic and inspectable.

Chalk combines two approaches:

  • Agentic planning handles the open-ended teaching decisions: research, narrative structure, analogies, scene selection, and visual intent.
  • Deterministic rendering handles the non-negotiable presentation rules: geometry, safe areas, text fitting, draw order, connector routing, timing, and output encoding.

This boundary makes the result creative where it should be and repeatable where it must be.

Challenges and lessons learned

  • Creativity versus reliability: agents are effective at research, explanation, and visual intent, but TypeScript must own final geometry, timing, validation, and export.
  • Synchronizing speech and visuals: timing became more dependable when narration was synthesized per beat and the renderer used measured audio durations instead of an unrelated animation clock.
  • Professional visual consistency: semantic icon retrieval, a video-wide visual bible, measured typography, layout repair, and quality gates matter as much as the initial scene idea.
  • Long-running work: video generation needs authenticated queue claims, leases, stale-attempt protection, progressive delivery, and an honest recovery path rather than a request-response API.

The result I am most proud of is the typed boundary between the teaching agents and the visual compiler: models can propose a lesson, but only sanitized, validated data can reach the renderer.

Product flow

  1. Create an account and enter a topic in the Chalk studio.
  2. Watch live planning and rendering progress; HLS playback becomes available while rendering continues.
  3. Review the completed lesson and download the final MP4.
  4. Return to personal history, browse the public gallery, or resume a recoverable failed render.
  5. Regenerate or delete a video only when signed in as its owner.
flowchart TD
    Start["User enters an educational topic"] --> Create["Browser creates an authenticated Convex job"]
    Create --> Queue["Convex stores status: queued"]
    Queue --> Claim["Zerops chalkworker polls and atomically claims a leased attempt"]
    Claim --> Plan["Research / outline / drawability edit / whole-video blueprint"]
    Plan --> Parallel["Opening scene design + prefix-first narration<br/>bounded tail work follows"]
    Parallel --> Validate["Sanitize JSON + validate typed storyboard + resolve assets"]
    Validate --> Render["Deterministic layout, SVG frames, raster audit and FFmpeg"]
    Render --> Prefix["Upload ordered private HLS segments while rendering continues"]
    Prefix --> Admit{"Convex has a safe contiguous playback runway?"}
    Admit -->|"not yet"| Render
    Admit -->|"yes"| Play["Browser receives protected HLS and begins buffered playback"]
    Play --> Finish["Upload final MP4, captions, thumbnail, transcript and timings"]
    Finish --> Complete["Convex marks the attempt complete and persists it in history"]

    Claim -.->|"heartbeat + progress"| State["Convex renews the lease<br/>and publishes reactive status"]
    Plan -.-> State
    Render -.-> State
    State -.-> Studio["Studio updates without polling"]
    Validate -.->|"safe failure"| Failed["Failed / recoverable job"]
    Render -.->|"safe failure"| Failed
    Failed --> Retry["Owner chooses Resume or Regenerate"]
    Retry --> Queue
Loading

The queue is intentionally pull-based. RENDER_WORKER_URL must remain unset in the submitted Convex deployment because chalkworker does not implement an inbound /render endpoint. Instead, it authenticates to the Convex /worker/jobs gateway with RENDER_WORKER_SECRET, verifies protocol version 2 and the required feature set, then begins its poll loop.

Accessibility and user experience

  • A skip link, visible focus styles, labelled controls, keyboard-operable navigation, and semantic status/progress elements support keyboard and screen-reader use.
  • Generation updates use polite live regions; errors use alert semantics rather than relying on color alone.
  • Completed lessons include English WebVTT captions and a readable scene-by-scene transcript generated from the same beat boundaries as the visual timeline.
  • The studio adapts to desktop and narrow mobile layouts, including a focus-managed navigation dialog. Motion-heavy previews respect prefers-reduced-motion.
  • Owner deletion uses a Radix Alert Dialog with a title, description, cancel path, destructive action, and busy state instead of a browser confirmation popup.
  • Playwright runs the landing page and health route in desktop and mobile Chromium projects; the landing-page scenario includes automated axe checks and caption validation.

What is technically distinctive

  • Two-pass educational writing: an outline agent establishes the teaching arc and an editor turns it into concise, drawable narration.
  • Blueprint-led rolling scene direction: the editor first locks the complete teaching arc, narration, scene intents, beat order, whole-video map, and visual bible. Opening specialists own the high-priority provider lane through design and hosted QA; tail specialists fan out only after opening publication work starts, so they cannot occupy every slot during startup. Approved scene graphs, measured timing, and asset bindings are frozen before their complete HLS segments are published, so later work cannot rewrite media a viewer may already receive.
  • Quality-gated streaming delivery: the opening and each ordered tail window pass the same sanitization, layout, visual, asset, compile, and resolved-frame safeguards as the final video. The worker begins encoding the earliest complete 12-second prefix immediately and uploads it privately while unpublished tail design and narration continue. Later approved windows extend the same protected HLS event stream; Convex admits its playlist after a contiguous 48-second runway by default (or when a shorter stream finishes), and the hls.js/MSE player waits for 24 contiguous fetched seconds before attempting autoplay.
  • Typed visual contract: bounded agent JSON is sanitized and normalized, then the composed storyboard is validated with Zod before it reaches layout or rendering code.
  • Constraint-driven layout: safe regions, measured text, minimum gaps, collision repair, connector routing, simplification, and post-layout quality metrics protect readability. These systems reduce layout failures; final visual QA remains part of the release process.
  • Graceful quality policy: density, color, board coverage, component count, and scene-scale measurements remain advisory diagnostics. They never discard a playable video; only a genuinely empty settled frame can block raster output, while clipping, overlap, and text-legibility integrity checks remain enforced by the compiler.
  • Contextual illustration retrieval: the default Google path uses Gemini embeddings and a model reranker against a style-locked local icon library, with OpenMoji fallback and on-demand generation for missing concepts. If its image model returns 429, Chalk immediately binds the strongest checked-in semantic match. With OPEN_SOURCE=enabled, Cloudflare Qwen is the only embedding provider and queries separate 1,024-dimensional indexes for the same checked-in artwork; it never calls image generation or remote icon-download APIs. A missing index, quota failure, or transient embedding error degrades to local keyword matching instead of failing video generation. Resolution is parallel across scenes while uniqueness and output order remain deterministic inside each scene.
  • Speech-led, prefix-first timing: the default Google path uses Google Cloud TTS, while OPEN_SOURCE=enabled uses only the pre-warmed local Kokoro model and makes no hosted speech request. Opening beats are synthesized, measured, and checkpointed first so an approved prefix does not wait for the full narration. Tail beats continue against the same immutable script, and the final 24 kHz mono timeline reuses the opening clips rather than regenerating them.
  • Efficient frame production: the first playback window is hash-rasterized first, identical visual holds remain globally deduplicated, and upload N overlaps encode N+1 without changing codec settings or callback order. Accepted PNGs and normalized narration are cached once and reused by HLS and the final render. The worker finishes publishing live HLS before encoding the downloadable MP4, keeping streaming work on the critical path.
  • Quota-aware multi-provider transport: the unchanged Google path retains its adaptive Vertex limiter, and Google image-model 429s bypass retries for local semantic art. With OPEN_SOURCE=enabled, capability-aware text and vision routing uses NVIDIA Nemotron Super first for the global plan with Cloudflare Llama as its schema-constrained fallback, NVIDIA Nemotron Nano first for scene work and visual QA, and immediately hops providers on throttling, malformed domain output, or transient failure while carrying the exact same locked context. Opening calls use a shorter bounded deadline than tail calls so free-endpoint stalls cannot monopolize the playback critical path. Narration and artwork stay local; only semantic icon-query embeddings use Cloudflare Qwen, with bounded retry and a nonfatal keyword fallback.
  • Leased render attempts: authenticated workers atomically claim jobs with an attempt ID and lease. Claim request IDs make a lost HTTP response safe to retry, temporary control-plane outages do not terminate the service, late callbacks cannot overwrite a newer attempt, and a bounded watchdog marks genuinely silent attempts failed so the owner can choose Resume or Regenerate.
  • Protected progressive playback: only the owner receives an in-progress stream reference, and every HLS playlist and segment URL carries an unguessable per-job capability. The media endpoint authorizes possession of that capability rather than repeating account authentication for every segment.
  • Recoverable local work: a persistent worker keeps storyboard, narration, and timing checkpoints in its job output directory. Resume can skip completed model calls when that directory is still available; Convex stores shared queue state, progress, HLS metadata, and completed media rather than these local checkpoints.

Generation and progressive-delivery engine

After chalkworker claims a job, generative models are allowed to make teaching and visual-intent decisions, but they never draw final frames. Their bounded outputs pass through sanitizers, a typed storyboard contract, deterministic geometry, visual-integrity checks, and the same renderer regardless of provider mode.

flowchart TD
    Job["Claimed job<br/>prompt + attempt ID + lease"] --> Mode{"Worker provider mode"}

    Mode -->|"default"| Google["Google path<br/>Vertex text / vision / embeddings<br/>Cloud TTS + optional novel imagery"]
    Mode -->|"OPEN_SOURCE=enabled"| Open["Open-source-provider path<br/>NVIDIA + Cloudflare text / vision"]
    Open --> Kokoro["Cache-only local Kokoro narration"]
    Open --> OpenAssets["Cloudflare Qwen semantic search<br/>checked-in icon library + OpenMoji"]

    Google --> Blueprint["Authoritative teaching arc<br/>fixed narration + whole-video map + visual bible"]
    Open --> Blueprint
    Blueprint --> Scenes["Priority opening scene design<br/>then bounded tail fan-out"]
    Blueprint --> Speech["Prefix-first per-beat narration"]
    Google --> Speech
    Kokoro --> Speech

    Scenes --> Contract["Normalize + sanitize + Zod storyboard contract"]
    Google --> Assets["Contextual asset resolution"]
    OpenAssets --> Assets
    Contract --> Assets
    Assets --> Layout["Measured typography + deterministic layout<br/>collision repair + connector routing + quality gates"]
    Speech --> Timing["Measured audio timeline + exact beat cues"]
    Layout --> Frames["Immutable SVG windows + Sharp rasterization"]
    Timing --> Frames
    Frames --> HLS["FFmpeg ordered six-second HLS segments"]
    HLS --> Stream["Authenticated upload to Convex<br/>append-only protected event stream"]
    Frames --> Final["Final MP4 + WebVTT + transcript<br/>thumbnail + timings.json"]
    Final --> Storage["Convex storage and completed job record"]
Loading

The same attempt identity protects every progress update, segment, upload, completion, and failure callback. Convex accepts a callback only from the current worker and attempt while its lease is valid. A late or restarted worker therefore cannot overwrite a newer retry, and unattached uploads are discarded when an attempt loses ownership.

Progressive playback flow

flowchart LR
    Opening["Opening narration and scenes approved"] --> Prefix["Worker selects earliest complete prefix<br/>default target: 12 seconds"]
    Prefix --> Encode["Rasterize and encode complete<br/>six-second segments first"]
    Encode --> Private["Upload privately to Convex<br/>published scene data becomes immutable"]
    Private --> Runway{"Contiguous stream reaches 48 seconds<br/>or a shorter stream finishes?"}
    Runway -->|"no"| Tail["Design, narrate and render next approved window"]
    Tail --> Encode
    Runway -->|"yes"| Playlist["Convex exposes the capability-protected playlist"]
    Playlist --> Buffer["hls.js/MSE path fetches a 24-second contiguous buffer"]
    Buffer --> Playback["Player attempts autoplay while tail rendering continues<br/>native HLS and manual controls remain available"]
    Playback --> Endlist["Worker closes HLS, uploads final artifacts<br/>and marks the attempt complete"]
Loading

These are separate safeguards: the worker's 12-second target starts useful rendering early, Convex's 48-second admission threshold normally prevents a public stream with too little runway, and the hls.js/MSE path's 24-second local buffer reduces the chance of an early stall. Native HLS, reduced-motion preferences, autoplay policy, and explicit user controls can produce a different playback path. Rolling streaming is the default but can be disabled or skipped, and an HLS failure remains nonfatal because the final MP4 pipeline continues. Only complete segments are published, and a scene already exposed in HLS is never redesigned later.

The browser and chalkweb never generate source frames or run FFmpeg, and Convex never performs the media pipeline. The browser only decodes and displays the delivered video. Frame generation and encoding are the CPU-, memory-, and disk-intensive chalkworker workload running on Zerops.

Repository map

Path Responsibility
app/ Next.js routes, landing page, and application composition
components/ Accessible UI primitives and whiteboard studio components
convex/ Schema, authentication, ownership rules, queue functions, HTTP media routes, and watchdog cron
worker/ Provider-routed agents, retrieval, TTS, rendering, progress, and the queue worker
shared/ Scene grammar, geometry, layout repair, quality metrics, SVG renderer, and timing contracts
scripts/ Local rendering, asset ingestion, visual auditing, and benchmark tooling
assets/ Runtime fonts, the generated house icon library, OpenMoji fallback assets, and search indexes
benchmarks/ Versioned visual-quality benchmark manifest and instructions

Technology

Layer Technology
Hosting Zerops Lightweight project with separate chalkweb and chalkworker services
Frontend TypeScript, Next.js 16, React 19, Tailwind CSS 4, Radix UI, Lucide
Backend Convex database, functions, subscriptions, scheduled jobs, and file storage
Worker runtime Zerops Node.js 20, tsx, Sharp, FFmpeg, Fontconfig, and local scratch storage
Agent layer Google Vertex AI by default; opt-in NVIDIA NIM + Cloudflare Workers AI pool
Semantic retrieval Gemini embeddings by default; isolated Cloudflare Qwen indexes in open-source mode
Narration Google TTS by default; cache-only local Kokoro in open-source mode
Render engine Typed storyboards, deterministic SVG, Sharp, FFmpeg, HLS, MP4, WebVTT
Validation and tests Zod, TypeScript, Node test runner through tsx, ESLint, Prettier, Playwright

Chalk does not call the OpenAI service at runtime. The NVIDIA and Cloudflare adapters use their documented OpenAI-compatible request format.

Key implementation paths

Concern Representative implementation
Zerops service builds and runtime zerops.yaml, zerops-project-import.yaml
Web readiness and release identity app/health/route.ts
Teaching agents and orchestration worker/agents.ts, worker/pipeline.ts
Agent-output sanitization shared/sceneGraph.ts
Typed storyboard validation shared/storyboard.ts
Layout, repair, and quality shared/layout.ts, shared/layoutRepair.ts, shared/renderQuality.ts
SVG and media rendering shared/svgFrame.ts, worker/render.ts
Per-beat narration timing worker/narration.ts, shared/speechTiming.ts
Queue, leases, and ownership convex/jobs.ts, convex/auth.ts
Authenticated worker gateway convex/http.ts, worker/convexWorker.ts
Download and ownership tests scripts/videoDownload.test.ts, scripts/videoOwnership.test.ts

Run locally

Prerequisites

  • Node.js 20 or newer
  • FFmpeg available on PATH
  • A Convex account
  • Either a Google Cloud project with Vertex AI and Cloud TTS, or free NVIDIA NIM and Cloudflare Workers AI API credentials

Install

git clone https://github.com/Avinash1286/explainer.git
cd explainer
npm ci
cp .env.example .env.local

Configure .env.local using the comments in .env.example. At minimum, generation needs the Convex URL and the same 32+ character RENDER_WORKER_SECRET configured on that deployment. Leave OPEN_SOURCE unset/disabled for the existing Google pipeline. To use the isolated open-source pipeline, set OPEN_SOURCE=enabled and add CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN, and NVIDIA_API_KEY; existing Google credentials may remain in the file and will not be called in this mode. Run npm run tts:warm once on every persistent worker to cache the required keyless Kokoro narration model before serving requests. These provider variables belong only on the persistent worker, never in the frontend or Convex.

Start the three processes

# terminal 1 - provisions or connects the Convex development backend
npm run convex:dev

# terminal 2 - outbound render worker
npm run worker

# terminal 3 - Next.js app at http://localhost:5173
npm run dev

For each Convex deployment, set RENDER_WORKER_SECRET once with npx convex env set RENDER_WORKER_SECRET "YOUR_32_PLUS_CHARACTER_SECRET", and place the identical value in the worker's local or hosted environment. Do not expose it through a NEXT_PUBLIC_ variable.

The checked-in asset indexes and runtime SVGs make a fresh clone render-ready. The assets:openmoji and assets:icons commands rebuild those assets and may consume model quota; they are not required for ordinary local startup.

To exercise the pipeline without the browser:

npm run render:local "Explain how a password manager works"

Quality checks

npm run lint          # Next.js/TypeScript ESLint rules
npm run format:check  # formatting gate for release documentation and config
npm run typecheck     # frontend TypeScript project
npm run check:worker  # worker, shared engine, and scripts TypeScript project
npm test              # deterministic unit, integration, regression, and edge-case tests
npm run test:convex   # Convex ownership, capacity, and stale-attempt integration tests
npm run test:coverage # the same suite with Node's V8 coverage report
npm run build         # production Next.js build
npm run test:browser  # responsive/a11y smoke tests; requires Playwright Chromium

# lint, formatting, both typechecks, core + Convex tests, and production build
npm run check

The automated suite covers geometry, layout repair, spatial composition, visual grammars, icon resolution and deduplication, speech timing, SVG rendering, quality gates, MP4 download behavior, and video ownership rules. A real convex-test suite exercises owner-only reads, per-user active-job capacity, protected HLS access, transactional rejected-upload cleanup, and stale worker-attempt rejection. Playwright runs the landing and health scenarios in desktop and mobile Chromium projects with axe accessibility checks. External model and media services remain offline during deterministic verification, so the checks do not require cloud credentials. Zerops then performs the clean remote build for each submitted release; the portable worker image can also be checked independently with docker build --tag chalk-worker:submission ..

The current local verification on 2026-08-09 passed ESLint, formatting, both TypeScript projects, all 419 core tests, all 8 Convex integration tests, and the production Next.js build. Playwright also passed all 10 desktop/mobile Chromium cases, and the production dependency audit reported no known vulnerability. The most recent V8 report measured 86.69% line, 86.42% branch, and 83.47% function coverage across modules loaded by the deterministic suite. The final Zerops pipeline logs and recorded smoke test provide clean-build and runtime evidence for the submitted commit.

Visual quality is also tested against a versioned golden-set manifest:

npm run benchmark:videos

Benchmark source media stays local and generated reports are intentionally ignored by Git; only the repeatable harness and manifest are versioned.

Scaling model and current boundaries

Chalk is designed to scale the expensive render tier independently from the web application:

  • Convex serializes queue claims, while each worker processes one video at a time.
  • Additional worker replicas are a future/manual scale-out option for increasing video throughput without exposing inbound worker ports; the submitted import deliberately runs one worker container.
  • Scene design and model reranking use bounded provider concurrency. Open-source narration gives the opening exclusive access to its first local Kokoro lane; after that frontier, an explicitly benchmarked worker may synthesize tail scenes across two independent model instances with OPEN_SOURCE_KOKORO_SCENE_CONCURRENCY=2. Frame rasterization remains independently CPU-bound; provider-aware jittered backoff prevents synchronized text/vision retry waves, and Vertex concurrency adapts to quota pressure in Google mode.
  • The selected job remains reactive, but gallery data is subscribed only while the gallery is open; sidebar rows avoid media URL work. Worker progress is coalesced while an independent heartbeat renews the lease.
  • Jobs record render start, first segment, playable-buffer, and completion timestamps. Successful jobs also upload the private timings.json stage trace to Convex storage before local scratch files are removed.
  • Worker-local storyboard and narration checkpoints can avoid repeating completed model calls when a persistent output directory is reused. They are not yet durable across arbitrary worker replicas.

The current deployment is optimized for hackathon-scale traffic, not unbounded public load. Worker replica count is configured manually, queue-depth autoscaling is not yet included, and long-term history pagination and a hosted latency dashboard remain production follow-ups. Bounded watchdog batches, leased attempts, coalesced progress, and per-user active-job limits protect the current queue. A larger public deployment should move stream segments and runtime telemetry into dedicated tables, export p50/p95 generation metrics, add cursor-based history pagination, and autoscale workers against queue depth.

Deployment on Zerops

The submission topology consists of two Zerops services and one external managed backend:

  1. chalkweb on Zerops is the public, judge-facing Next.js application.
  2. chalkworker on Zerops is the persistent compute service that performs the expensive agent, narration, asset, raster, and FFmpeg work.
  3. Convex Cloud supplies authentication, reactive data, queue coordination, ownership enforcement, and media storage.

This split is deliberate: Zerops owns both the user-facing runtime and the core rendering workload, while Convex gives those independently scalable services a durable coordination boundary.

Release and boot flow

flowchart TD
    Release["Reviewed public Git commit<br/>+ immutable submission tag"]
    Release --> ConvexDeploy["Deploy Convex schema, functions and HTTP actions"]
    Release --> Git["Connect the same repository/tag to Zerops"]

    Vars["Zerops project variables<br/>Convex URLs + demo mode + release SHA"] --> WebBuild
    WorkerSecrets["chalkworker secret variables<br/>worker bearer secret + provider credentials"] --> WorkerRun
    ConvexEnv["Convex production environment<br/>matching bearer secret; no RENDER_WORKER_URL"] --> Protocol

    Git --> WebBuild["chalkweb build<br/>npm ci, then Next.js build, then prune dev dependencies"]
    Git --> WorkerBuild["chalkworker build<br/>npm ci, type-check, warm Kokoro cache, then prune"]
    WebBuild --> WebRun["Deploy .next, public assets and production dependencies<br/>start Next.js on 0.0.0.0:5173"]
    WorkerBuild --> WorkerRun["Deploy worker/shared/assets/cache<br/>install FFmpeg + Fontconfig, then start poller"]
    Vars --> WorkerRun

    ConvexDeploy --> Health["chalkweb /health probes Convex /health<br/>and requires protocol v2+"]
    WebRun --> Health
    Health -->|"HTTP 200"| Public["Public Zerops URL becomes ready"]

    ConvexDeploy --> Protocol["Authenticated worker protocol + feature handshake"]
    WorkerRun --> Protocol
    Protocol -->|"compatible"| Poll["Worker begins outbound claim loop"]
    Protocol -->|"incompatible"| Refuse["Fail startup; deploy matching Convex release"]

    Public --> Smoke["Incognito sign-in + real prompt-to-video smoke test"]
    Poll --> Smoke
    Smoke --> Ready["Record deployment IDs, URL, SHA, timings and evidence"]
Loading

Project variables must be set before the first chalkweb build because /chalk embeds its public Convex URL and generation mode during static prerendering. Both Zerops services and Convex must then be deployed from the same release tag.

What Zerops builds and operates

Concern chalkweb chalkworker
Base ubuntu/nodejs@20 ubuntu/nodejs@20
Build Install locked dependencies, run the production Next.js build, remove development-only packages. Install locked dependencies, type-check worker code, pre-warm Kokoro, retain production tsx, package assets/cache.
Runtime npm start on 0.0.0.0:5173. Install FFmpeg/Fontconfig, then run the persistent npm run worker poll loop.
Public access HTTPS through the Zerops subdomain/domain route. None; only outbound HTTPS is required.
Health Zerops readiness and continuous health checks call /health. Startup protocol handshake, continuous heartbeats, logs, and the real generation smoke test.
Scaling boundary Stateless web containers may be replicated when traffic requires it. Exactly one container and one video at a time; RENDER_CONCURRENCY only bounds frame-raster work inside that job.

The project import selects the Zerops Lightweight core, exposes only chalkweb, and pins chalkworker to one container. Runtime CPU, RAM, and disk limits remain an operator choice so the worker can be sized against available challenge credit and observed FFmpeg/Kokoro usage.

Configuration and trust boundaries

Scope Values
Zerops project/build variables NEXT_PUBLIC_CONVEX_URL, VITE_CONVEX_URL, CONVEX_URL, CONVEX_SITE_URL, NEXT_PUBLIC_DEMO=on, DEPLOYMENT_RELEASE=<full SHA>
Generated by zerops.yaml DEPLOYMENT_PLATFORM=zerops, production Node mode, port, worker ID, cache-only Kokoro path, bounded render concurrency
Convex production environment RENDER_WORKER_SECRET; optional worker allowlist matching the stable Zerops WORKER_ID
chalkworker-only configuration The matching RENDER_WORKER_SECRET, provider secrets, and either Google mode or OPEN_SOURCE=enabled with NVIDIA/Cloudflare
Never public Worker bearer secret, provider API keys, cloud service-account material, private account identifiers

The web health endpoint verifies that the configured Convex deployment is reachable, identifies itself as chalk-convex, and advertises a compatible protocol. It does not prove that a worker is currently polling, that provider quota is available, or that FFmpeg can finish a job. Those properties are established by the worker startup log and the required end-to-end generation test.

RENDER_WORKER_URL must be absent from the submitted Convex environment. Setting it activates a legacy push path for an inbound /render service that the Zerops worker intentionally does not expose.

The reviewable definitions are zerops.yaml and zerops-project-import.yaml. See DEPLOY.md for the exact deployment order, environment placement, resource baseline, protocol check, smoke test, rollback, and judge-day operations.

The release is judge-ready only when the public Zerops URL reports the submitted SHA, the worker logs a successful protocol connection, and a fresh signed-in user completes a real prompt-to-video job through the Zerops worker.

Known limitations

  • Generation latency and cost depend on video length, model quotas, TTS, and worker capacity.
  • Automatic geometry and quality gates substantially reduce collisions but cannot replace release QA across every possible topic.
  • Storyboard, narration, timing, and temporary render artifacts are worker-local. Resume reuses them only when the same persistent job directory is available; cross-worker checkpoint durability is a future improvement.
  • Every completed generation currently appears in the public gallery. Owner-only regenerate/delete rules are enforced, but a per-video private/publish control is not yet available.
  • The same-origin attachment proxy provides a forced-download response for completed MP4s up to 19 MiB. Larger files fall back to their storage URL, where the browser may open playback and require the user to save the file manually.
  • The house illustration library is intentionally curated. In Google mode, novel concepts may use image generation or OpenMoji; open-source mode uses Cloudflare semantic retrieval only to select from checked-in local assets and falls back to keyword matching when embeddings are unavailable.
  • The repository does not contain private comparison videos, generated benchmark reports, local outputs, or credentials.

License and attribution

Project source code is available under the MIT License. Vendored icons and fonts keep their upstream licenses: OpenMoji is CC BY-SA 4.0, Comic Neue, Itim, and Patrick Hand use the SIL Open Font License 1.1, and Chewy and Permanent Marker use Apache License 2.0. The generated house illustration library is original project material. See NOTICE.md for file-level attribution and redistribution terms.

About

Chalk turns one educational prompt into a narrated, hand-drawn explainer video.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages