[fix] nopy: find the prompt label through zod's wrappers

Closes DOCS-AUDIT §2.3.

The cube contract says each schema field is `.describe()`d and that the
description is the prompt label. Whether it was depended on the order the
manifest happened to chain in: zod 4 keys a description to the schema
*instance* and `.default()` returns a new `ZodDefault` around the described
type rather than mutating it, so the wrapper carries no description of its own
and the prompt, reading the outer node, fell back to the bare key.

    z.boolean().describe('Update package cache').default(false)  ->  'UPDATE'
    z.boolean().default(false).describe('Update package cache')  ->  the sentence

15 of the 22 core cubes are written the first way, so most prompts showed a key.
`promptLabel()` walks down through `default` / `optional` / `nullable` looking
for a description, which makes the two orders equivalent — the answer that
cannot regress, where re-ordering every manifest and hoping the next one written
gets it right can. It discriminates on `zodKind`, not `instanceof`, for the
reason recorded on that helper: a manifest built by a different zod copy fails
every `instanceof` in the module.

The mocked test asserts all four shapes, including a doubly-wrapped
`describe().optional().default()` and a field with no description at all. The
pty test is the one that carries the weight: its probe schema is written in the
losing order, and it now waits for `First value` on a real enquirer render, so
removing the unwrapping fails a test that talks to an actual terminal rather
than to a mocked `Form`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCzYTAm9QUhvLNr2EpdagJ
This commit is contained in:
Benjamin Diedrichsen
2026-09-01 12:48:16 +02:00
co-authored by Claude Opus 5
parent bb8b1bfa5c
commit 09554b6785
3 changed files with 75 additions and 6 deletions
+5 -1
View File
@@ -31,8 +31,12 @@ const hasPython = spawnSync('python3', ['--version']).status === 0;
/** Down arrow — how the form moves from one field to the next. */
const DOWN = '\u001b[B';
// Waits for the *description*, not the key. The probe schema is written
// `.describe(…).default(…)` — the order that used to lose the label — so this
// step also witnesses the zod-wrapper unwrapping through a real enquirer render
// rather than through the mocked Form in `prompts.test.ts`.
const STEPS = [
{ expect: 'ALPHA', send: 'alpha-typed', settle: 0.6 },
{ expect: 'First value', send: 'alpha-typed', settle: 0.6 },
{ send: DOWN, settle: 0.4 },
{ send: 'beta-typed', settle: 0.4 },
{ send: '\r', settle: 1.2 },