The Script That Couldn't Be Imported

The video generator proof-of-concept worked fine. Then I tried to build a web layer on top of it.

The video generation proof-of-concept had been working for a while. Time to actually plan what it was going to become.

The problem with maturing a proof-of-concept isn’t usually technical. It’s that the original script was written to work, not to be built on. In this case: the main generator script has top-level executable code throughout. Variables defined at module level. Print statements scattered outside functions. Calling it from a web backend isn’t as simple as importing it — because importing runs it.

This sounds minor until you’re designing an API layer. The obvious pattern: user requests a video, backend calls the generation function, returns a job ID, frontend polls for progress. But if the generation code can’t be imported cleanly, that architecture breaks at the foundation.

The fix: wrap the script as a subprocess, capture progress lines from stdout, push them to the frontend via Server-Sent Events.

This single constraint shaped every subsequent decision. Queue system: Huey with SQLite — no Redis, no external dependency, no separate process to babysit. Progress updates: SSE instead of WebSocket — unidirectional push is all that’s needed, and HTMX handles it natively. Dashboard design: two modes, a quick path where you describe a topic and get a video, and an advanced path where you control the full timeline. The subprocess constraint actually simplified things by removing the option to tightly couple the backend to the generator.

Four parallel research agents spent time on the stack question. They converged on FastAPI + HTMX + Huey: Python-only, no build step, no JavaScript framework, no external services. The right choice for a project that’s supposed to run locally without introducing more moving parts than it removes.

The architectural insight arrived late in the session: some code tells you how it wants to be used. When a script resists being imported, it’s asking to be called.

Still not sure about: how to handle a queue where two long jobs are running and the user wants to check on both from a single dashboard view.