diff --git a/packages/core/src/loop/boringstack/gate-stages.ts b/packages/core/src/loop/boringstack/gate-stages.ts index f39c54cd..952c8f92 100644 --- a/packages/core/src/loop/boringstack/gate-stages.ts +++ b/packages/core/src/loop/boringstack/gate-stages.ts @@ -146,7 +146,7 @@ function structuredSteerMessage(message: string, file: string): string { // emits json+multipart+text for EVERY route, scaffold ones included) — NOT a route/schema bug. // The model burned ~60 turns (build15) chasing it on the API side; steer to the consumer. if (message.includes("Readable>\` is the api-client's NORMAL, UNIVERSAL response type — Elysia's swagger emits three media types (json/multipart/text) for EVERY route (the scaffold's own auth/dashboard routes are identical), so you CANNOT remove it by editing the route or the \`response:\` schema — do NOT try. Fix it on the CONSUMER: the 'not assignable to Promise' error means you annotated the \`.queries.ts\`/\`.mutations.ts\` fn \`: Promise\` and did a bare \`return data\` — the fix is to REMOVE that return-type annotation and let TS INFER it (never \`as\`-cast). Then return the payload for THIS route's response shape: if it wraps \`{ data: … }\` (scaffold auth pattern) read \`data?.data\`; if it returns the object/array directly, just \`return data\` — match your \`response:\` schema, don't blindly add \`.data\`.`; + return `${message}\n↳ \`Readable>\` is the api-client's NORMAL, UNIVERSAL response type — Elysia's swagger emits three media types (json/multipart/text) for EVERY route (the scaffold's own auth/dashboard routes are identical), so you CANNOT remove it by editing the route or the \`response:\` schema — do NOT try. Fix it on the CONSUMER by INFERRING, not annotating — it shows up two ways, BOTH fixed by removing an annotation (never \`as\`-cast): (a) \`not assignable to Promise\` = you annotated the \`.queries.ts\`/\`.mutations.ts\` fn \`: Promise\` with a bare \`return data\`; (b) \`UseMutationResult> not assignable to UseMutationResult\` (or \`UseQueryResult\`) = you annotated the HOOK generic/return (\`useMutation\` / \`: UseMutationResult\`). REMOVE the annotation on the fn AND the hook and let TS INFER both. Then return the payload for THIS route's response shape: if it wraps \`{ data: … }\` (scaffold auth pattern) read \`data?.data\`; if it returns the object/array directly, just \`return data\` — match your \`response:\` schema, don't blindly add \`.data\`.`; } // A `PathsWithMethod` error means the path STRING doesn't match any generated key — diff --git a/packages/core/src/loop/conventions.ts b/packages/core/src/loop/conventions.ts index 787baf85..63895d36 100644 --- a/packages/core/src/loop/conventions.ts +++ b/packages/core/src/loop/conventions.ts @@ -74,6 +74,9 @@ export const TOPIC_RULES: Readonly> = "no-conditional-expect", "no-real-network-in-unit-tests", "fake-timers-must-be-restored", + // build16's final blocker (9× in one row-hook test): empty placeholder callbacks. The + // testing guide teaches `vi.fn()` for these, so map the rule here to re-push it reactively. + "no-empty-function", ], // The audit-event rule is a boringstack-OWN eslint rule (not a tsforge meta-rule), so it // isn't keyed here for the reactive PUSH — the front-loaded guide is the delivery path. @@ -185,9 +188,13 @@ const GUIDES: Readonly> = { "stream) for EVERY route — Elysia's swagger emits three media types (json/multipart/text) so " + "openapi-fetch unions them. This is EXPECTED and UNIVERSAL (the scaffold's own auth/dashboard " + "routes are identical); you CANNOT remove it from the route/response schema, so do NOT try. FIX " + - "IT ON THE CONSUMER. The error `Readable<…> not assignable to Promise` means you " + - "annotated the query/mutation fn `: Promise` and did a bare `return data` — the " + - "UNIVERSAL fix is to REMOVE that return-type annotation and let TS INFER it (never `as`-cast). " + + "IT ON THE CONSUMER by INFERRING, not annotating. The error appears two ways, BOTH fixed by " + + "removing a type annotation so TS infers from the unwrapped payload (never `as`-cast): " + + "(a) `Readable<…> not assignable to Promise` — you annotated the query/mutation fn " + + "`: Promise` and did a bare `return data`; (b) `UseMutationResult> not " + + "assignable to UseMutationResult` (or `UseQueryResult<…>`) — you annotated the HOOK's " + + "generic/return type (`useMutation` / `: UseMutationResult`). Do NEITHER: " + + "leave the fn AND the hook UN-annotated and let TS INFER both. " + "Then return the payload for YOUR route's response SHAPE: if the response wraps `{ data: … }` " + '(the scaffold auth pattern), read `data?.data` — `const { data } = await apiClient.GET("/api/v1/' + 'supplier/"); return data?.data ?? [];`; if the route returns the object/array DIRECTLY (no ' + @@ -253,7 +260,9 @@ const GUIDES: Readonly> = { "• RULES the gate enforces: no `.only`/`.skip` (no-focused-tests); never put `expect` inside an " + "`if`/loop/`switch`/ternary — assert unconditionally (no-conditional-expect); if you use fake " + "timers, restore them in `afterEach` (fake-timers-must-be-restored); the test path/name mirrors " + - "its source (test-file-mirrors-source).\n" + + "its source (test-file-mirrors-source). A no-op/placeholder callback (e.g. an unused handler " + + "arg when testing ONE of a hook's callbacks) must be `vi.fn()`, NEVER an empty `() => {}` — an " + + "empty arrow/function body is a `no-empty-function` gate error (observed: 9 in one row-hook test).\n" + "• After you write a test the harness AUTO-FORMATS it (imports reordered, quotes/commas normalized), " + "so your next `edit` oldString won't match — RE-READ the file and copy oldString from its current " + "content; do NOT recreate the whole file.", diff --git a/packages/core/tests/boringstack-conventions.test.ts b/packages/core/tests/boringstack-conventions.test.ts index 2ed40e70..32242c7a 100644 --- a/packages/core/tests/boringstack-conventions.test.ts +++ b/packages/core/tests/boringstack-conventions.test.ts @@ -163,6 +163,10 @@ describe("convention registry", () => { expect(g).toContain("data?.data"); expect(g).toContain("EXPECTED and UNIVERSAL"); expect(g).toContain("not assignable to Promise"); + // build16: the SAME over-annotation habit also hits the HOOK generic (useMutation/useQuery), + // not just the fn return — the guide must name that facet too. + expect(g).toContain("UseMutationResult { expect(g).toContain("no-focused-tests"); expect(g).toContain("no-conditional-expect"); expect(g).toContain("fake-timers-must-be-restored"); + // build16 final blocker: empty placeholder callbacks in tests → no-empty-function; use vi.fn(). + expect(g).toContain("no-empty-function"); + expect(g).toContain("vi.fn()"); // The auto-reformat re-read (the not-found edit-reject churn). expect(g).toContain("AUTO-FORMATS"); }); @@ -209,7 +216,7 @@ describe("convention registry", () => { describe("topicForRule (testing)", () => { test("EVERY testing rule maps to the testing topic so its gate error pushes the guide", () => { - // Lock all six — a typo in any would silently disable the reactive PUSH for that error. + // Lock all — a typo in any would silently disable the reactive PUSH for that error. for (const rule of [ "test-sibling-required", "test-file-mirrors-source", @@ -217,6 +224,8 @@ describe("topicForRule (testing)", () => { "no-conditional-expect", "no-real-network-in-unit-tests", "fake-timers-must-be-restored", + // build16: the empty-placeholder-callback rule must re-push the testing guide too. + "no-empty-function", ]) { expect(topicForRule(rule)).toBe("testing"); } diff --git a/packages/core/tests/boringstack-gate-stages.test.ts b/packages/core/tests/boringstack-gate-stages.test.ts index a1a8bd79..8b5339bf 100644 --- a/packages/core/tests/boringstack-gate-stages.test.ts +++ b/packages/core/tests/boringstack-gate-stages.test.ts @@ -183,6 +183,10 @@ describe("signatureToError", () => { // Universal move is infer-don't-annotate; unwrap is shape-conditional (not blind .data). expect(err.message).toContain("let TS INFER"); expect(err.message).toContain("don't blindly add"); + // Steer must name BOTH annotation sites — the fn AND the useMutation/useQuery HOOK generic + // (build16 oscillated on the hook one; guide + steer must agree). + expect(err.message).toContain("UseMutationResult