Phase 2: API client, typed hooks, live probe

- Add api:types script (openapi-typescript against live FastAPI)
- Generate src/lib/api/types.ts (2972 lines, 55 paths, 16 schemas)
- lib/api/client.ts: typed apiRequest + ApiError + makeQueryClient
  (staleTime 5s, no refetchOnWindowFocus to preserve editor state)
- lib/providers.tsx: QueryClientProvider client component, useState
  singleton so App Router re-renders don't dump the cache
- lib/api/cases.ts: Case type + casesKeys + useCases hook (pragmatic
  hand-typed Case pending backend response-model annotations)
- layout.tsx: wrap children with <Providers>
- Smoke test: CasesLiveProbe component on home page hitting live FastAPI
  via /api/cases rewrite proxy

Phase 2 deliverable check: useCases() returns typed Case[] from the
production FastAPI through the Next.js proxy. End-to-end wiring proven.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chaim
2026-04-11 15:49:24 +00:00
parent 64724656af
commit 0ee8e723bd
9 changed files with 3260 additions and 80 deletions

View File

@@ -0,0 +1,54 @@
/**
* Cases domain hooks.
*
* Note on types: the FastAPI `/api/cases` endpoint doesn't declare a response
* model, so openapi-typescript emits `unknown` for its payload. Until the
* backend is annotated (see out-of-scope in the rewrite plan), we maintain a
* small local type that matches what the running API returns today. Any drift
* surfaces as a runtime TypeScript error the first time a property is touched.
*/
import { useQuery } from "@tanstack/react-query";
import { apiRequest } from "./client";
export type CaseStatus =
| "new"
| "uploading"
| "processing"
| "documents_ready"
| "outcome_set"
| "brainstorming"
| "direction_approved"
| "drafting"
| "qa_review"
| "drafted"
| "exported"
| "reviewed"
| "final";
export type Case = {
case_number: string;
title: string;
status: CaseStatus;
subject?: string | null;
expected_outcome?: string | null;
created_at?: string;
updated_at?: string;
};
export const casesKeys = {
all: ["cases"] as const,
list: (detail: boolean) => [...casesKeys.all, "list", { detail }] as const,
detail: (caseNumber: string) =>
[...casesKeys.all, "detail", caseNumber] as const,
};
export function useCases(detail = false) {
return useQuery({
queryKey: casesKeys.list(detail),
queryFn: ({ signal }) =>
apiRequest<Case[]>(`/api/cases${detail ? "?detail=true" : ""}`, {
signal,
}),
});
}