The Prompt Was On a Different Page Than the Video That Promised It
We built a gated training portal for a client. Clients log in, watch video lessons, and follow along with transcripts and AI prompts. Simple enough shape: one page per tenant (listing all modules), one page per module (video, transcript, the whole lesson).
The first pass had prompts sitting at the tenant level. You'd visit the module index and see a wall of prompts there, associated with the tenant rather than with any specific lesson.
In video two, the trainer says "I'll get you guys that prompt" at 4:20. The client watches the video, looks around the page for the prompt, and finds nothing. The prompt is on the index page, a navigation step away, with no obvious connection to the video that just referenced it.
That's a bad client experience. The prompt belongs on the lesson page, between the video and the transcript.
The fix was straightforward: move prompts from trainingSchema (tenant level) to trainingModuleSchema (lesson level), update the module page to render them between the video and the transcript, and update the index to show a prompt count on each card instead of all the bodies.
One schema field, two page updates.
The Part That Would Have Bitten Us
Zod strips unknown keys silently. That sentence matters.
Our schemas use z.object() without .strict(), which is the default. If you have a key in your JSON that the schema doesn't know about, Zod just drops it. No error. No warning. The parse succeeds, the build passes, tsc is clean, and you get back an object with that key missing.
After the migration, the prompts live on each module object in the JSON. But if someone adds a prompt to the wrong level, say they put a prompts array directly on the tenant object instead of on a module, Zod will silently strip it on parse. The page renders without the prompt. No test fails. The client just never finds the thing.
We needed a test that catches that specific failure mode before it reaches production.
it("keeps every prompt attached to a module, none stranded at tenant level", () => {
const raw = JSON.parse(
readFileSync("content/training/freedom-ag.json", "utf-8"),
);
expect(raw.prompts, "prompts belong on the module now").toBeUndefined();
const t = getTraining("freedom-ag");
expect(t.modules.flatMap((m) => m.prompts).length).toBeGreaterThan(0);
});
The key move: read the raw JSON directly, before Zod touches it, and assert there is no prompts key at the top level. If someone puts a prompt in the wrong place, this test fails. The other nine training tests still pass, so it discriminates rather than just always-failing on a schema error.
We also verified the test mutation-checks correctly: add prompts: [] to the raw JSON at the tenant level, run the test, watch it fail. Remove it, green. The test earns its place.
Why This Pattern Matters
Content-driven apps with Zod schemas are a great combination. The schema validates your JSON at build time, so a malformed content file fails the build instead of silently rendering a blank page to a client.
But Zod's silent-strip behavior means a valid schema shape isn't the same as a correct data location. A prompt in the wrong object is still a valid prompt. Zod happily parses it and throws away the result.
The tests that prove your schema has the right shape don't prove your content is in the right place.
Reading raw JSON in a test, before the schema runs, is the way to check placement. It's not beautiful, but it's the only thing that would have caught this one.