A channel's message slots are named from the client, on every backend

The IR called them react-message and django-message, so a FastAPI channel had
to declare a DjangoMessage. They are client-message and server-message now,
and the direction words hold wherever a channel is declared: Params /
ClientMessage / ServerMessage, with mizan-core deriving <Pascal>Params and
friends so no backend names a type itself. Django's ReactChannel and
FastAPI's ReactChannel are both Channel.

mizan-fastapi never registered a channels extension, so build_ir() emitted no
channel at all and every payload type was invisible to codegen. It registers
one now. RegistryExtension is an ABC requiring all(), which is what the IR
reads — an extension that cannot enumerate its registrations no longer exists.

The gate that should have caught the rename could not: tests/afi registered no
channel because mizan-rust had no channel registry to register one in, so a
five-package rename of the wire contract passed byte-parity without a channel
byte crossing it. mizan-rust grows ChannelSlotKind, a CHANNELS slice, a
#[mizan::channel] macro, and KDL emission whose wire_to_pascal matches Python's
split; the AFI fixture now carries a channel with every slot and one with a
single slot, so all three backends prove the contract byte for byte.

MizanChannel held three Option<String> beside three has_*() predicates and
unwrapped them with defaults; it holds an ordered slot vector, so an absent
slot is absent rather than defaulted. The channels target emitted a React
hooks file that a stage1-only consumer could not compile — react emits that
now. The codegen's parity tests byte-compared emitted source against baselines
without ever compiling it: they compile the generated crate and run its tests,
import the generated Python package and call every method, and typecheck each
TypeScript target against a consumer.

Also fixed at source: app_visitor printed its import diagnostic to stdout, the
stream export_mizan_ir writes KDL to, so a failed import silently corrupted the
IR; the apps root was hardcoded to "apps"; _default_literal crashed build_ir on
any non-JSON-serializable field default; Django and mizan-core derived Pascal
names two different ways, disagreeing on every dotted channel name.

ir.py builds a document and renders templates/ir/document.kdl.j2 rather than
appending KDL strings with hand-tracked indentation, and named types resolve to
a fixed point — a model reachable only through a union branch was referenced by
a ref that no type block ever defined.

The rest is the write-gate's own classifiers run over the standing tree:
relative imports, silent swallows, Protocol contracts that should be ABCs,
emitters hand-rendering target source, catch-all arms over closed enums, and
comments narrating the project rather than the code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-27 14:03:19 -04:00
parent 398c90fc8b
commit 3aafec6dd4
345 changed files with 11054 additions and 17359 deletions

View File

@@ -1,19 +0,0 @@
import path from 'path'
import { fileURLToPath } from 'url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const root = path.resolve(__dirname, '../../..')
export default {
projectId: 'mizan-fastapi-e2e',
source: {
fastapi: {
module: 'main',
cwd: path.join(root, 'examples/fastapi-react-site/backend'),
command: ['uv', 'run', 'python'],
},
},
output: 'src/api',
}

View File

@@ -0,0 +1,9 @@
project_id = "mizan-fastapi-e2e"
output = "src/api"
targets = ["react"]
[source.fastapi]
module = "main"
# Resolved from this file's directory; the backend project is the subprocess cwd.
cwd = "../backend"
command = ["uv", "run", "python"]

View File

@@ -3,7 +3,10 @@
"private": true,
"type": "module",
"scripts": {
"generate": "mizan-generate --config mizan.toml",
"prebuild": "npm run generate",
"build": "vite build",
"predev": "npm run generate",
"dev": "vite --port 5175"
},
"dependencies": {

View File

@@ -0,0 +1 @@
* linguist-generated=true

View File

@@ -1,5 +1,3 @@
// AUTO-GENERATED by mizan — do not edit
import { mizanFetch } from '@mizan/base'
import type { currentUserOutput } from '../types'

View File

@@ -1,5 +1,3 @@
// AUTO-GENERATED by mizan — do not edit
import { mizanFetch } from '@mizan/base'
import type { morphGroupsOutput, morphLayersOutput } from '../types'

View File

@@ -1,5 +1,3 @@
// AUTO-GENERATED by mizan — do not edit
import { mizanCall } from '@mizan/base'
import type { addInput, addOutput } from '../types'

View File

@@ -1,9 +0,0 @@
// AUTO-GENERATED by mizan — do not edit
import { mizanCall } from '@mizan/base'
import type { buggyFnOutput } from '../types'
export function callBuggyFn(): Promise<buggyFnOutput> {
return mizanCall('buggy_fn', {})
}

View File

@@ -0,0 +1,7 @@
import { mizanCall } from '@mizan/base'
import type { divideInput, divideOutput } from '../types'
export function callDivide(args: divideInput): Promise<divideOutput> {
return mizanCall('divide', args)
}

View File

@@ -1,5 +1,3 @@
// AUTO-GENERATED by mizan — do not edit
import { mizanCall } from '@mizan/base'
import type { echoInput, echoOutput } from '../types'

View File

@@ -0,0 +1,7 @@
import { mizanCall } from '@mizan/base'
import type { echoTransformInput, echoTransformOutput } from '../types'
export function callEchoTransform(args: echoTransformInput): Promise<echoTransformOutput> {
return mizanCall('echo_transform', args)
}

View File

@@ -1,5 +1,3 @@
// AUTO-GENERATED by mizan — do not edit
import { mizanCall } from '@mizan/base'
import type { multiplyInput, multiplyOutput } from '../types'

View File

@@ -1,9 +0,0 @@
// AUTO-GENERATED by mizan — do not edit
import { mizanCall } from '@mizan/base'
import type { notImplementedFnOutput } from '../types'
export function callNotImplementedFn(): Promise<notImplementedFnOutput> {
return mizanCall('not_implemented_fn', {})
}

View File

@@ -1,5 +1,3 @@
// AUTO-GENERATED by mizan — do not edit
import { mizanCall } from '@mizan/base'
import type { permissionCheckFnInput, permissionCheckFnOutput } from '../types'

View File

@@ -1,5 +1,3 @@
// AUTO-GENERATED by mizan — do not edit
import { mizanCall } from '@mizan/base'
import type { setMorphValueInput, setMorphValueOutput } from '../types'

View File

@@ -1,5 +1,3 @@
// AUTO-GENERATED by mizan — do not edit
import { mizanCall } from '@mizan/base'
import type { staffOnlyOutput } from '../types'

View File

@@ -1,5 +1,3 @@
// AUTO-GENERATED by mizan — do not edit
import { mizanCall } from '@mizan/base'
import type { superuserOnlyOutput } from '../types'

View File

@@ -1,5 +1,3 @@
// AUTO-GENERATED by mizan — do not edit
import { mizanCall } from '@mizan/base'
import type { verifiedOnlyOutput } from '../types'

View File

@@ -1,5 +1,3 @@
// AUTO-GENERATED by mizan — do not edit
import { mizanCall } from '@mizan/base'
import type { whoamiOutput } from '../types'

View File

@@ -1,21 +1,18 @@
// AUTO-GENERATED by mizan — do not edit
export * from './types'
export { fetchGlobalContext, type GlobalContextData, type GlobalContextParams } from './contexts/global'
export { fetchMorphsContext, type MorphsContextData, type MorphsContextParams } from './contexts/morphs'
export { callEcho } from './functions/echo'
export { callAdd } from './functions/add'
export { callDivide } from './functions/divide'
export { callEcho } from './functions/echo'
export { callEchoTransform } from './functions/echoTransform'
export { callMultiply } from './functions/multiply'
export { callWhoami } from './functions/whoami'
export { callPermissionCheckFn } from './functions/permissionCheckFn'
export { callSetMorphValue } from './functions/setMorphValue'
export { callStaffOnly } from './functions/staffOnly'
export { callSuperuserOnly } from './functions/superuserOnly'
export { callVerifiedOnly } from './functions/verifiedOnly'
export { callNotImplementedFn } from './functions/notImplementedFn'
export { callBuggyFn } from './functions/buggyFn'
export { callPermissionCheckFn } from './functions/permissionCheckFn'
export { callSetMorphValue } from './functions/setMorphValue'
export { callWhoami } from './functions/whoami'
// Stage 2 framework adapter
export * from './react'

View File

@@ -1,7 +1,5 @@
'use client'
// AUTO-GENERATED by mizan — do not edit
import {
createContext,
useCallback,
@@ -14,17 +12,15 @@ import {
} from 'react'
import {
configure,
initSession,
mizanCall,
mizanFetch,
MizanError,
registerContext,
type ContextState,
} from '@mizan/base'
import { fetchGlobalContext, type GlobalContextData, type GlobalContextParams, fetchMorphsContext, type MorphsContextData, type MorphsContextParams, callEcho, callAdd, callMultiply, callWhoami, callStaffOnly, callSuperuserOnly, callVerifiedOnly, callNotImplementedFn, callBuggyFn, callPermissionCheckFn, callSetMorphValue, type currentUserOutput, type morphGroupsOutput, type morphLayersOutput } from './index'
import { fetchGlobalContext, type GlobalContextData, type GlobalContextParams, fetchMorphsContext, type MorphsContextData, type MorphsContextParams, callAdd, callDivide, callEcho, callEchoTransform, callMultiply, callPermissionCheckFn, callSetMorphValue, callStaffOnly, callSuperuserOnly, callVerifiedOnly, callWhoami, type currentUserOutput, type morphGroupsOutput, type morphLayersOutput, } from './index'
// Internal — runs inside a Provider, registers with the kernel exactly once.
// Runs inside a Provider, registers with the kernel exactly once.
function useContextSubscription<T>(
name: string,
params: Record<string, any>,
@@ -45,7 +41,8 @@ function useContextSubscription<T>(
return useSyncExternalStore(handle.subscribe, handle.getState, handle.getState)
}
// Internal — wraps an imperative call() with isPending / error state.
// Wraps an imperative call() with isPending / error state.
interface MutationHook<TArgs, TResult> {
mutate: (args: TArgs) => Promise<TResult>
isPending: boolean
@@ -74,8 +71,6 @@ function useMutation<TArgs, TResult>(
return { mutate, isPending, error }
}
// ── Global Context ──
const GlobalCtx = createContext<ContextState<GlobalContextData> | null>(null)
export function GlobalContextProvider({ children }: { children: ReactNode }) {
@@ -94,8 +89,6 @@ export function useCurrentUser(): currentUserOutput | null {
return useGlobalContext().data?.current_user ?? null
}
// ── Morphs Context ──
const MorphsCtx = createContext<ContextState<MorphsContextData> | null>(null)
export function MorphsContext({ children }: { children: ReactNode }) {
@@ -117,20 +110,32 @@ export function useMorphLayers(): morphLayersOutput | null {
return useMorphsContext().data?.morph_layers ?? null
}
export function useAdd() {
return useMutation<Parameters<typeof callAdd>[0], Awaited<ReturnType<typeof callAdd>>>(callAdd)
}
export function useDivide() {
return useMutation<Parameters<typeof callDivide>[0], Awaited<ReturnType<typeof callDivide>>>(callDivide)
}
export function useEcho() {
return useMutation<Parameters<typeof callEcho>[0], Awaited<ReturnType<typeof callEcho>>>(callEcho)
}
export function useAdd() {
return useMutation<Parameters<typeof callAdd>[0], Awaited<ReturnType<typeof callAdd>>>(callAdd)
export function useEchoTransform() {
return useMutation<Parameters<typeof callEchoTransform>[0], Awaited<ReturnType<typeof callEchoTransform>>>(callEchoTransform)
}
export function useMultiply() {
return useMutation<Parameters<typeof callMultiply>[0], Awaited<ReturnType<typeof callMultiply>>>(callMultiply)
}
export function useWhoami() {
return useMutation<void, Awaited<ReturnType<typeof callWhoami>>>(() => callWhoami() as any)
export function usePermissionCheckFn() {
return useMutation<Parameters<typeof callPermissionCheckFn>[0], Awaited<ReturnType<typeof callPermissionCheckFn>>>(callPermissionCheckFn)
}
export function useSetMorphValue() {
return useMutation<Parameters<typeof callSetMorphValue>[0], Awaited<ReturnType<typeof callSetMorphValue>>>(callSetMorphValue)
}
export function useStaffOnly() {
@@ -145,24 +150,10 @@ export function useVerifiedOnly() {
return useMutation<void, Awaited<ReturnType<typeof callVerifiedOnly>>>(() => callVerifiedOnly() as any)
}
export function useNotImplementedFn() {
return useMutation<void, Awaited<ReturnType<typeof callNotImplementedFn>>>(() => callNotImplementedFn() as any)
export function useWhoami() {
return useMutation<void, Awaited<ReturnType<typeof callWhoami>>>(() => callWhoami() as any)
}
export function useBuggyFn() {
return useMutation<void, Awaited<ReturnType<typeof callBuggyFn>>>(() => callBuggyFn() as any)
}
export function usePermissionCheckFn() {
return useMutation<Parameters<typeof callPermissionCheckFn>[0], Awaited<ReturnType<typeof callPermissionCheckFn>>>(callPermissionCheckFn)
}
export function useSetMorphValue() {
return useMutation<Parameters<typeof callSetMorphValue>[0], Awaited<ReturnType<typeof callSetMorphValue>>>(callSetMorphValue)
}
// ── MizanContext root provider ──
export interface MizanContextProps {
/** Base URL for protocol endpoints. Defaults to "/api/mizan". */
baseUrl?: string
@@ -172,8 +163,8 @@ export interface MizanContextProps {
}
/**
* Root provider — calls configure() once and mounts the global context (if defined).
* Must wrap any component using Mizan-generated hooks.
* Calls configure() once and mounts the global context when one is defined.
* Every component reading a generated hook resolves through this provider.
*/
export function MizanContext({ baseUrl, session, children }: MizanContextProps) {
const configured = useRef(false)
@@ -187,12 +178,7 @@ export function MizanContext({ baseUrl, session, children }: MizanContextProps)
return <GlobalContextProvider>{children}</GlobalContextProvider>
}
// ── Imperative escape hatch ──
/**
* Returns the imperative kernel API. For test harnesses or rare cases where
* a typed generated hook does not fit. Most app code should use the typed hooks.
*/
/** The untyped kernel entry points, bound to the configured client. */
export function useMizan() {
return { call: mizanCall, fetch: mizanFetch }
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,784 +1,105 @@
// AUTO-GENERATED by mizan — do not edit
export interface paths {
"/mizan/echo": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Echoes the input text. */
post: operations["echo"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/mizan/add": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Returns a + b. */
post: operations["add"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/mizan/multiply": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Returns x * y. */
post: operations["multiply"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/mizan/whoami": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Returns the authenticated user's identity. Anonymous → UNAUTHORIZED. */
post: operations["whoami"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/mizan/staff_only": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Staff-only endpoint. */
post: operations["staffOnly"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/mizan/superuser_only": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Superuser-only endpoint. */
post: operations["superuserOnly"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/mizan/verified_only": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Verified-users-only endpoint. Anonymous → FORBIDDEN. */
post: operations["verifiedOnly"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/mizan/not_implemented_fn": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Always raises NotImplementedError → NOT_IMPLEMENTED. */
post: operations["notImplementedFn"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/mizan/buggy_fn": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Always raises a generic exception → INTERNAL_ERROR. */
post: operations["buggyFn"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/mizan/permission_check_fn": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Wrong secret → FORBIDDEN; correct secret → success. */
post: operations["permissionCheckFn"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/mizan/current_user": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** The global context — auto-mounted at the React root. */
post: operations["currentUser"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/mizan/morph_groups": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Summary-shape slot — server must route MorphLayer mutations away from here. */
post: operations["morphGroups"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/mizan/morph_layers": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Detailed-shape slot — server routes MorphLayer mutations here. */
post: operations["morphLayers"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/mizan/set_morph_value": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Mutation that returns the changed row; kernel splices into morph_layers. */
post: operations["setMorphValue"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
}
export type webhooks = Record<string, never>;
export interface components {
schemas: {
/** HTTPValidationError */
HTTPValidationError: {
/** Detail */
detail?: components["schemas"]["ValidationError"][];
};
/**
* MorphGroupMeta
* @description Group summary — narrower shape than MorphLayer. Listed alongside
* morph_layers so the server's slot resolver has to discriminate by
* return-type rather than by bundle order.
*/
MorphGroupMeta: {
/** Id */
id: number;
/** Label */
label: string;
/** Count */
count: number;
};
/** MorphLayer */
MorphLayer: {
/** Id */
id: number;
/** Group Id */
group_id: number;
/** Label */
label: string;
/** Value */
value: number;
};
/** ValidationError */
ValidationError: {
/** Location */
loc: (string | number)[];
/** Message */
msg: string;
/** Error Type */
type: string;
/** Input */
input?: unknown;
/** Context */
ctx?: Record<string, never>;
};
/** addInput */
addInput: {
/** A */
a: number;
/** B */
b: number;
};
/** addOutput */
addOutput: {
/** Result */
result: number;
};
/** buggyFnOutput */
buggyFnOutput: {
/** Message */
message: string;
};
/** currentUserOutput */
currentUserOutput: {
/** Email */
email: string;
/** Authenticated */
authenticated: boolean;
/**
* Is Staff
* @default false
*/
is_staff: boolean;
};
/** echoInput */
echoInput: {
/** Text */
text: string;
};
/** echoOutput */
echoOutput: {
/** Message */
message: string;
};
/** morphGroupsOutput */
morphGroupsOutput: components["schemas"]["MorphGroupMeta"][];
/** morphLayersOutput */
morphLayersOutput: components["schemas"]["MorphLayer"][];
/** multiplyInput */
multiplyInput: {
/** X */
x: number;
/** Y */
y: number;
};
/** multiplyOutput */
multiplyOutput: {
/** Product */
product: number;
};
/** notImplementedFnOutput */
notImplementedFnOutput: {
/** Message */
message: string;
};
/** permissionCheckFnInput */
permissionCheckFnInput: {
/** Secret */
secret: string;
};
/** permissionCheckFnOutput */
permissionCheckFnOutput: {
/** Message */
message: string;
};
/** setMorphValueInput */
setMorphValueInput: {
/** Id */
id: number;
/** Value */
value: number;
};
/** setMorphValueOutput */
setMorphValueOutput: {
/** Id */
id: number;
/** Group Id */
group_id: number;
/** Label */
label: string;
/** Value */
value: number;
};
/** staffOnlyOutput */
staffOnlyOutput: {
/** Message */
message: string;
};
/** superuserOnlyOutput */
superuserOnlyOutput: {
/** Message */
message: string;
};
/** verifiedOnlyOutput */
verifiedOnlyOutput: {
/** Message */
message: string;
};
/** whoamiOutput */
whoamiOutput: {
/** Email */
email: string;
/** Authenticated */
authenticated: boolean;
/**
* Is Staff
* @default false
*/
is_staff: boolean;
};
};
responses: never;
parameters: never;
requestBodies: never;
headers: never;
pathItems: never;
}
export type $defs = Record<string, never>;
export interface operations {
echo: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["echoInput"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["echoOutput"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
add: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["addInput"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["addOutput"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
multiply: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["multiplyInput"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["multiplyOutput"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
whoami: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["whoamiOutput"];
};
};
};
};
staffOnly: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["staffOnlyOutput"];
};
};
};
};
superuserOnly: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["superuserOnlyOutput"];
};
};
};
};
verifiedOnly: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["verifiedOnlyOutput"];
};
};
};
};
notImplementedFn: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["notImplementedFnOutput"];
};
};
};
};
buggyFn: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["buggyFnOutput"];
};
};
};
};
permissionCheckFn: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["permissionCheckFnInput"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["permissionCheckFnOutput"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
currentUser: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["currentUserOutput"];
};
};
};
};
morphGroups: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["morphGroupsOutput"];
};
};
};
};
morphLayers: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["morphLayersOutput"];
};
};
};
};
setMorphValue: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["setMorphValueInput"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["setMorphValueOutput"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
export interface MorphGroupMeta {
id: number
label: string
count: number
}
export interface MorphLayer {
id: number
group_id: number
label: string
value: number
}
export interface addInput {
a: number
b: number
}
export interface addOutput {
result: number
}
export interface currentUserOutput {
email: string
authenticated: boolean
is_staff: boolean
}
export interface divideInput {
numerator: number
denominator: number
}
export interface divideOutput {
quotient: number
}
export interface echoInput {
text: string
}
export interface echoOutput {
message: string
}
export interface echoTransformInput {
text: string
mode: string
}
export interface echoTransformOutput {
message: string
}
export type morphGroupsOutput = MorphGroupMeta[]
export type morphLayersOutput = MorphLayer[]
export interface multiplyInput {
x: number
y: number
}
export interface multiplyOutput {
product: number
}
export interface permissionCheckFnInput {
secret: string
}
export interface permissionCheckFnOutput {
message: string
}
export interface setMorphValueInput {
id: number
value: number
}
export interface setMorphValueOutput {
id: number
group_id: number
label: string
value: number
}
export interface staffOnlyOutput {
message: string
}
export interface superuserOnlyOutput {
message: string
}
export interface verifiedOnlyOutput {
message: string
}
export interface whoamiOutput {
email: string
authenticated: boolean
is_staff: boolean
}
// Convenience type exports
export type HTTPValidationError = components["schemas"]["HTTPValidationError"]
export type MorphGroupMeta = components["schemas"]["MorphGroupMeta"]
export type MorphLayer = components["schemas"]["MorphLayer"]
export type ValidationError = components["schemas"]["ValidationError"]
export type addInput = components["schemas"]["addInput"]
export type addOutput = components["schemas"]["addOutput"]
export type buggyFnOutput = components["schemas"]["buggyFnOutput"]
export type currentUserOutput = components["schemas"]["currentUserOutput"]
export type echoInput = components["schemas"]["echoInput"]
export type echoOutput = components["schemas"]["echoOutput"]
export type morphGroupsOutput = components["schemas"]["morphGroupsOutput"]
export type morphLayersOutput = components["schemas"]["morphLayersOutput"]
export type multiplyInput = components["schemas"]["multiplyInput"]
export type multiplyOutput = components["schemas"]["multiplyOutput"]
export type notImplementedFnOutput = components["schemas"]["notImplementedFnOutput"]
export type permissionCheckFnInput = components["schemas"]["permissionCheckFnInput"]
export type permissionCheckFnOutput = components["schemas"]["permissionCheckFnOutput"]
export type setMorphValueInput = components["schemas"]["setMorphValueInput"]
export type setMorphValueOutput = components["schemas"]["setMorphValueOutput"]
export type staffOnlyOutput = components["schemas"]["staffOnlyOutput"]
export type superuserOnlyOutput = components["schemas"]["superuserOnlyOutput"]
export type verifiedOnlyOutput = components["schemas"]["verifiedOnlyOutput"]
export type whoamiOutput = components["schemas"]["whoamiOutput"]

View File

@@ -1,16 +1,11 @@
/**
* E2E Test Fixtures — FastAPI flavor.
*
* Mirrors examples/django-react-site/harness/src/fixtures.tsx minus the
* Django-only surfaces (channels, forms). Each fixture uses GENERATED
* mizan hooks (not raw call()). Playwright reads the DOM to verify
* behavior. URL hash selects the fixture: #echo, #add, etc.
* The URL hash selects which component renders; each one drives one generated
* hook and writes its outcome into data-testid nodes the Playwright spec reads.
*/
import { useState, useEffect } from 'react'
import {
MizanContext,
MorphsContext,
useEcho,
useAdd,
@@ -19,8 +14,8 @@ import {
useStaffOnly,
useSuperuserOnly,
useVerifiedOnly,
useNotImplementedFn,
useBuggyFn,
useEchoTransform,
useDivide,
usePermissionCheckFn,
useCurrentUser,
useMorphGroups,
@@ -50,8 +45,8 @@ export function Fixtures() {
case 'staff-only': return <StaffOnly />
case 'superuser-only': return <SuperuserOnly />
case 'verified-only': return <VerifiedOnly />
case 'not-implemented': return <NotImplemented />
case 'internal-error': return <InternalError />
case 'not-implemented': return <UnsupportedEchoMode />
case 'internal-error': return <DivideByZero />
case 'permission-error': return <PermissionError_ />
case 'permission-success': return <PermissionSuccess />
case 'context-current-user': return <ContextCurrentUser />
@@ -105,7 +100,7 @@ function Multiply() { const r = useRun(useMultiply, { x: 6, y: 7 }); return <Res
function NotFound() {
const { call } = useMizan()
const [error, setError] = useState<unknown>()
useEffect(() => { call('does_not_exist').catch(setError) }, [call])
useEffect(() => { call('does_not_exist', {}).catch(setError) }, [call])
return <Result error={error} />
}
@@ -120,20 +115,16 @@ function AuthRequired() { const r = useRun(useWhoami); return <Result {...r} />
function StaffOnly() { const r = useRun(useStaffOnly); return <Result {...r} /> }
function SuperuserOnly() { const r = useRun(useSuperuserOnly); return <Result {...r} /> }
function VerifiedOnly() { const r = useRun(useVerifiedOnly); return <Result {...r} /> }
function NotImplemented() { const r = useRun(useNotImplementedFn); return <Result {...r} /> }
function InternalError() { const r = useRun(useBuggyFn); return <Result {...r} /> }
function UnsupportedEchoMode() { const r = useRun(useEchoTransform, { text: 'e2e-test', mode: 'rot13' }); return <Result {...r} /> }
function DivideByZero() { const r = useRun(useDivide, { numerator: 1, denominator: 0 }); return <Result {...r} /> }
function PermissionError_() { const r = useRun(usePermissionCheckFn, { secret: 'wrong' }); return <Result {...r} /> }
function PermissionSuccess() { const r = useRun(usePermissionCheckFn, { secret: 'open-sesame' }); return <Result {...r} /> }
function ContextCurrentUser() {
try {
const user = useCurrentUser()
if (user === null) return <div>loading context...</div>
return <pre data-testid="result">{JSON.stringify(user)}</pre>
} catch {
return <div>loading context...</div>
}
const user = useCurrentUser()
if (user === null) return <div>loading context...</div>
return <pre data-testid="result">{JSON.stringify(user)}</pre>
}
@@ -150,14 +141,16 @@ function MergeMorphInner() {
const layers = useMorphLayers()
const { mutate } = useSetMorphValue()
const [fired, setFired] = useState(false)
const [error, setError] = useState<unknown>()
useEffect(() => {
if (layers && groups && !fired) {
setFired(true)
mutate({ id: 1, value: 0.75 })
mutate({ id: 1, value: 0.75 }).catch(setError)
}
}, [layers, groups, fired, mutate])
if (error !== undefined) return <Result error={error} />
if (layers === null || groups === null) return <div>loading...</div>
return <pre data-testid="result">{JSON.stringify({ groups, layers })}</pre>
}