We now have two halves that don’t meet. Lesson 02 proved the OCR model works from a script; lesson 03 built a service with nothing behind it. In this lesson we join them, so a page you upload in the browser is read by a model on your own machine. Checkpoint: redact-04-connect-ocr, after redact-03-local-service

What actually travels?

When you upload a PDF or screenshot, the starter app already turns every page into a PNG image in the browser. Those images are what the service will read. Each page goes as its own request: the PNG bytes encoded as base64 text, the page’s width and height in pixels, and your privacy settings. Sending pages one at a time keeps every request small and lets the screen say Reading page 2 of 5 locally… as it goes. It also means one bad page can’t take the whole document down with it. The service checks the image before the model sees it. It decodes the PNG, measures it, and refuses it if the size doesn’t match what the browser said. That matters later: OCR’s boxes are only meaningful against the exact image it read.

What if there’s no model?

The service might not be running, the model might fail to load, or the machine might be offline on first use. None of that should stop someone from redacting a document by hand, which the app could already do in lesson 01. So every failure in this lesson ends in the same place: the review screen, with a short message and the manual tools. This rule runs through the rest of the course: AI is an aid, never a gate. More on it: About Redact’s trust boundary.

1. Get this lesson’s code

Stop the app and the service if they’re running. From the repository root, redact-course, bring in this lesson’s commit:
No packages change, so there’s nothing to install. To see every change, open the comparison on GitHub.

2. Read the analyzer

Open qvac-service/qvac-analyzer.ts. It implements lesson 03’s DocumentAnalyzer with QVAC. It loads the model once, on first use.
The settings are lesson 02’s. loading ??= means that if two pages arrive while the model is still loading, both wait for the same load instead of starting two. If loading fails, loading is cleared so the next request tries again. It checks the page before reading it.
The first check catches base64 that doesn’t decode cleanly. The second reads the real PNG size with sharp and compares it with what the browser claimed. It reads the page and checks the answer.
This time the image is the bytes from the request, not a file path. The blocks are parsed with a schema too: if the model ever returned something unexpected, it would fail here, not in the browser. The result goes back with a receipt: an id, start and finish times, the model name and ocrMs, how long OCR took.

3. Read how the service starts QVAC

Open qvac-service/index.ts:
QVAC_CONFIG_PATH is lesson 02’s trick: keep the models in redact/.qvac/models. SNAP_USER_COMMON gives QVAC’s background worker its own home folder inside the app, so it doesn’t share ~/.qvac with anything else (Where QVAC writes files). The runtime object is a thin wrapper around the SDK; it’s what lets the tests swap in a fake.

4. Read the browser side

Open src/features/redact/RedactApp.tsx and find prepare. After the pages are rasterized, it asks for health and then sends every page:
And whatever goes wrong, the review still opens:

5. Start the service and the app

In one terminal:
Wait for Redact QVAC service listening on http://127.0.0.1:4317. In a second terminal, in the same redact folder:
Open localhost:3101.

6. Upload a page

Choose public/test-files/redact/chat-private.png, select Private, and select Prepare for review. The screen shows Preparing locally and Reading page 1 of 1 locally…. There’s also a Loading the local OCR model and reading each page… message in between, but it usually flashes past too quickly to read. The model loads from .qvac/models, which you filled in lesson 02, so this takes about 20–30 seconds.

7. See the evidence

The review screen opens. In the right-hand column a new panel reads 12 text regions found locally. It lists each line with its recognition score, for example Maya Chen and maya.chen@example com, each followed by something like 93% recognition. Notice what hasn’t changed: Safe-share preview is still uncovered, and there are no outlines on the page yet. OCR found text; nothing has been decided. Check the service from a third terminal:
The OCR model’s state is now ready. The model stays loaded, so the next file skips the loading step and only the reading takes time.

8. Cover something by hand

Drag across Maya Chen on Original. The mark appears and the preview turns black there, just as in lesson 01. Select Done, then Save copy, and open the file to confirm.

9. Turn the service off

Stop the service with Ctrl+C in its terminal. It unloads the model and exits without printing an error. In the app, select Redact another document, upload chat-private.png again, choose Private, and select Prepare for review. The review screen still opens, with the message Local analysis is unavailable. You can still redact by hand. There’s no OCR panel this time. Draw a mark, select Done and Save copy: the copy is saved as before.

If something goes wrong

  • The service exits with Redact QVAC service failed: while starting: read the rest of the line. If it mentions the model, run npm run qvac:ocr-smoke from lesson 02 to check the download on its own.
  • The service prints Redact QVAC analyze-page failed: Raster dimensions do not match the request: the browser and service disagree about the page size, which shouldn’t happen with the test files. Upload a fresh file rather than reusing one mid-analysis.
  • The app always shows “Local analysis is unavailable”: check the service is running on 4317 with curl http://127.0.0.1:4317/health.

10. Check your work

Stop the dev server, then run:
npm test reports Tests 46 passed (46). qvac-analyzer.test.ts drives the analyzer with a fake runtime: it checks that the model loads once, that a size mismatch is refused, and that malformed OCR blocks never leave the service.

So what just happened?

A page left the browser, crossed to a process on your own machine, was read by QVAC, and came back as text with positions. When that process was missing, the app said so in one line and let you carry on by hand.

Try it yourself

Upload public/test-files/redact/statement-multipage.pdf. Watch the message count through the pages, then use Next → on the review screen to see each page’s own OCR panel.

Where to next

Next: Line OCR boxes up with the page (lesson 05)