[!TIP] Audience: people building local image / OCR / translation pipelines, and anyone who wants to audit a tool’s experience. Core goal: how the five-stage pipeline works, and why a UX walkthrough with zero code changes found 6 issues that reading the code couldn’t. Problem-to-solution map:

  • Cold-start concurrent import torch crash → pre-import torch on the main thread
  • Where machine translation is wrong, humans must be able to fix it → pages.json + in-app proofreading
  • “Export didn’t work” illusion → only “Export All” writes the final deliverable

This comic translation tool turns a volume of Japanese manga into a translated copy, through five stages: Japanese detection → OCR → translation → inpainting → re-render the translation. Input and output share the same format — folder / ZIP / CBZ / EPUB / PDF in, same kind out (RAR isn’t writable, so it converts to CBZ). Three translation engines: sakura (Japanese sources only), llm (Qwen3-4B, multilingual), and mock (offline self-testing).

The batch page runs dozens of volumes in one go: tasks execute strictly one after another (a single GPU can only run one model task at a time), advance with zero dialogs, and after a stop-and-restart, finished tasks don’t re-run while stopped or failed ones continue from their own checkpoints.

1. The Model Bill, and Not Downloading Twice

Each stage eats its own model: detection about 170MB, OCR about 450MB, inpainting about 200MB, plus Sakura needs about 4.3GB more. The first-run wizard can point the model directory at an existing pool — like the one I already downloaded for the subtitle project — and reuse it without downloading again. Model paths are tracked per-manifest with repo / filename / exact byte count, judged across four states (ready / missing / broken path / size mismatch); a broken binding fails loudly, never silently falling back to downloading a replacement.

2. Where the Machine Is Wrong, Humans Can Edit

No matter how good the translation is, some pages need a human. Every page’s result lands in the work directory’s pages.json: original text, translation, coordinates, plus each model’s actual path and per-stage timing. Proofreading happens in-app, without touching any file: every detected box on the canvas is clickable, the original text shows on the right, edit the translation, press Ctrl+Enter to save. The “needs attention” list filters by low detection confidence / OCR found no text / missing translation / whole-page failure, so you never have to read the whole book.

One trap is easy to trip over: only “Export All” writes your edits into the final deliverable. “Re-render this page” only updates the image in the work directory — the final package stays stale. Users think “I edited it, so it’s done” but get handed the old output. The classic “UI looks done, output didn’t follow” problem.

3. A UX Walkthrough That Changed No Code

Once the pipeline ran, I did a full-surface UX audit: single task / batch / proofread / export / model management / settings / diagnostics / first run, each decomposed into “before → in progress → after → re-entry”. The verification method was 10 offscreen real runs: real windows under QT_QPA_PLATFORM=offscreen, real detect/OCR/inpaint models with mock translation, covering folder / EPUB / PDF inputs, including cancel, resume, zero-dialog batch progress, and cross-session re-rendering. Analysis only — not one line of code changed.

The story worth telling: cold-start concurrent import torch crashes. The re-render path skips the first two stages, so a worker thread’s first import torch happens while constructing the inpaint model — colliding with the startup diagnostics thread’s own import and tripping torch 2.6’s circular import (partially initialized module 'torch' ...). Reproduced 2/2 times; after pre-importing torch on the main thread, 1/1 passed; a pure Runner (no Qt, no threads) passed 1/1. On failure the user got a full screen of English Python stack trace — violating “failures should speak human”.

The rest were the same kind of problem: export re-packages to the default path instead of the task’s actual output location (measured: original book_out.epub, export produced book_translated.epub), so users think export didn’t take effect; model-management / diagnostics buttons stay clickable but silently dead while a task runs — the project’s only “clicked, nothing happened” spot; model downloads log only start and end, with no byte progress or ETA, violating “anything over 10 seconds must show progress”.

The most valuable output of the audit was one sentence: what reading the code concluded and what actually running concluded differed in 6 places. That isn’t the audit method’s credit — it’s that “ran it” and “read it” are two different kinds of information.

4. Stability Cleanup

Version 0.2.0 closed a backlog: three unbounded growths — comicgen.log truncated per launch, run.log reset per run, retired_threads cleared on exit; the line-balancing binary search cut from 16 iterations to 10, giving -24.7% CPU layout time on dense pages (the extra iterations were pure waste — 10 already reaches sub-pixel precision); the offline switch changed from a process-level persistent state to a “this run” scope that restores itself afterwards; four paths that silently swallowed exceptions got logging; and a ruff + mypy gate landed.

Summary

The deepest lesson from this pipeline: getting the machine to run all five stages is just the first step; “making people want to use it” is the real hard part. And that comes not from a smarter model, but from filling in what the machine does poorly — proofreading entry points, export semantics, failure feedback — until it’s usable. I’m keeping that “6 differences” sentence; the next time I evaluate any tool’s experience, I’ll run it first.