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

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

View File

@@ -1,40 +1,22 @@
'use client'
// AUTO-GENERATED by mizan - do not edit manually
// Regenerate with: npm run schemas
import { useChannel, type ChannelSubscription } from 'mizan/channels'
import type { ChatParams, ChatReactMessage, ChatDjangoMessage, NotificationsDjangoMessage, PresenceDjangoMessage, PrivateDjangoMessage } from './generated.channels'
import type { ChatParams, ChatClientMessage, ChatServerMessage, NotificationsServerMessage, PresenceServerMessage, PrivateServerMessage } from './channels'
// ============================================================================
// Channel Hooks
// ============================================================================
/**
* Hook for the chat channel.
*/
export function useChatChannel(params: ChatParams): ChannelSubscription<ChatParams, ChatDjangoMessage, ChatReactMessage> {
return useChannel('chat', params)
export function useChatChannel(params: ChatParams): ChannelSubscription<ChatParams, ChatServerMessage, ChatClientMessage> {
return useChannel('chat', params)
}
/**
* Hook for the notifications channel.
*/
export function useNotificationsChannel(): ChannelSubscription<Record<string, never>, NotificationsDjangoMessage, never> {
return useChannel('notifications', {})
export function useNotificationsChannel(): ChannelSubscription<Record<string, never>, NotificationsServerMessage, never> {
return useChannel('notifications', {})
}
/**
* Hook for the presence channel.
*/
export function usePresenceChannel(): ChannelSubscription<Record<string, never>, PresenceDjangoMessage, never> {
return useChannel('presence', {})
export function usePresenceChannel(): ChannelSubscription<Record<string, never>, PresenceServerMessage, never> {
return useChannel('presence', {})
}
/**
* Hook for the private channel.
*/
export function usePrivateChannel(): ChannelSubscription<Record<string, never>, PrivateDjangoMessage, never> {
return useChannel('private', {})
export function usePrivateChannel(): ChannelSubscription<Record<string, never>, PrivateServerMessage, never> {
return useChannel('private', {})
}

View File

@@ -1,337 +1,48 @@
// AUTO-GENERATED by mizan - do not edit manually
// Regenerate with: npm run schemas
// ============================================================================
// OpenAPI Types (generated by openapi-typescript)
// ============================================================================
export interface paths {
"/channels/chat/params": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Chat channel params */
post: operations["chatParams"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/channels/chat/react": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Chat React→Django message */
post: operations["chatReactMessage"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/channels/chat/django": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Chat Django→React message */
post: operations["chatDjangoMessage"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/channels/notifications/django": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Notifications Django→React message */
post: operations["notificationsDjangoMessage"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/channels/presence/django": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Presence Django→React message */
post: operations["presenceDjangoMessage"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/channels/private/django": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Private Django→React message */
post: operations["privateDjangoMessage"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
}
export type webhooks = Record<string, never>;
export interface components {
schemas: {
/** BaseModel */
BaseModel: Record<string, never>;
/** ChatParams */
ChatParams: {
/** Room */
room: string;
};
/** ChatReactMessage */
ChatReactMessage: {
/** Text */
text: string;
};
/** ChatDjangoMessage */
ChatDjangoMessage: {
/** Text */
text: string;
};
/** NotificationsDjangoMessage */
NotificationsDjangoMessage: {
/** Text */
text: string;
};
/** PresenceDjangoMessage */
PresenceDjangoMessage: {
/** Value */
value: number;
};
/** PrivateDjangoMessage */
PrivateDjangoMessage: {
/** Text */
text: string;
};
};
responses: never;
parameters: never;
requestBodies: never;
headers: never;
pathItems: never;
}
export type $defs = Record<string, never>;
export interface operations {
chatParams: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["ChatParams"];
};
};
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["BaseModel"];
};
};
};
};
chatReactMessage: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["ChatReactMessage"];
};
};
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["BaseModel"];
};
};
};
};
chatDjangoMessage: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ChatDjangoMessage"];
};
};
};
};
notificationsDjangoMessage: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["NotificationsDjangoMessage"];
};
};
};
};
presenceDjangoMessage: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["PresenceDjangoMessage"];
};
};
};
};
privateDjangoMessage: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description OK */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["PrivateDjangoMessage"];
};
};
};
};
export interface ChatClientMessage {
text: string
}
export interface ChatParams {
room: string
}
// ============================================================================
// Convenience Type Exports
// ============================================================================
export interface ChatServerMessage {
text: string
}
export type ChatParams = components["schemas"]["ChatParams"]
export type ChatReactMessage = components["schemas"]["ChatReactMessage"]
export type ChatDjangoMessage = components["schemas"]["ChatDjangoMessage"]
export type NotificationsDjangoMessage = components["schemas"]["NotificationsDjangoMessage"]
export type PresenceDjangoMessage = components["schemas"]["PresenceDjangoMessage"]
export type PrivateDjangoMessage = components["schemas"]["PrivateDjangoMessage"]
export interface NotificationsServerMessage {
text: string
}
// ============================================================================
// Channel Registry
// ============================================================================
export interface PresenceServerMessage {
value: number
}
export interface PrivateServerMessage {
text: string
}
export const CHANNELS = {
chat: {
name: 'chat',
pascalName: 'Chat',
hasParams: true,
hasReactMessage: true,
hasDjangoMessage: true,
paramsType: 'ChatParams',
reactMessageType: 'ChatReactMessage',
djangoMessageType: 'ChatDjangoMessage',
},
notifications: {
name: 'notifications',
pascalName: 'Notifications',
hasParams: false,
hasReactMessage: false,
hasDjangoMessage: true,
djangoMessageType: 'NotificationsDjangoMessage',
},
presence: {
name: 'presence',
pascalName: 'Presence',
hasParams: false,
hasReactMessage: false,
hasDjangoMessage: true,
djangoMessageType: 'PresenceDjangoMessage',
},
private: {
name: 'private',
pascalName: 'Private',
hasParams: false,
hasReactMessage: false,
hasDjangoMessage: true,
djangoMessageType: 'PrivateDjangoMessage',
},
'chat': {
name: 'chat',
pascalName: 'Chat',
paramsType: 'ChatParams',
clientMessageType: 'ChatClientMessage',
serverMessageType: 'ChatServerMessage',
},
'notifications': {
name: 'notifications',
pascalName: 'Notifications',
serverMessageType: 'NotificationsServerMessage',
},
'presence': {
name: 'presence',
pascalName: 'Presence',
serverMessageType: 'PresenceServerMessage',
},
'private': {
name: 'private',
pascalName: 'Private',
serverMessageType: 'PrivateServerMessage',
},
} as const

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 { greetOutput } 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 { httpOnlyEchoInput, httpOnlyEchoOutput } from '../types'

View File

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

View File

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

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 { 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,5 +1,3 @@
// AUTO-GENERATED by mizan — do not edit
import { mizanCall } from '@mizan/base'
import type { wsWhoamiOutput } from '../types'

View File

@@ -1,24 +1,21 @@
// AUTO-GENERATED by mizan — do not edit
export * from './types'
export { fetchGlobalContext, type GlobalContextData, type GlobalContextParams } from './contexts/global'
export { fetchLocalContext, type LocalContextData, type LocalContextParams } from './contexts/local'
export { callEcho } from './functions/echo'
export { callAdd } from './functions/add'
export { callWhoami } from './functions/whoami'
export { callDivide } from './functions/divide'
export { callEcho } from './functions/echo'
export { callEchoTransform } from './functions/echoTransform'
export { callHttpOnlyEcho } from './functions/httpOnlyEcho'
export { callJwtObtain } from './functions/jwtObtain'
export { callJwtRefresh } from './functions/jwtRefresh'
export { callMultiply } from './functions/multiply'
export { callPermissionCheckFn } from './functions/permissionCheckFn'
export { callStaffOnly } from './functions/staffOnly'
export { callSuperuserOnly } from './functions/superuserOnly'
export { callVerifiedOnly } from './functions/verifiedOnly'
export { callMultiply } from './functions/multiply'
export { callNotImplementedFn } from './functions/notImplementedFn'
export { callBuggyFn } from './functions/buggyFn'
export { callPermissionCheckFn } from './functions/permissionCheckFn'
export { callWhoami } from './functions/whoami'
export { callWsWhoami } from './functions/wsWhoami'
export { callJwtObtain } from './functions/jwtObtain'
export { callJwtRefresh } from './functions/jwtRefresh'
// 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, fetchLocalContext, type LocalContextData, type LocalContextParams, callEcho, callAdd, callWhoami, callHttpOnlyEcho, callStaffOnly, callSuperuserOnly, callVerifiedOnly, callMultiply, callNotImplementedFn, callBuggyFn, callPermissionCheckFn, callWsWhoami, callJwtObtain, callJwtRefresh, type currentUserOutput, type greetOutput } from './index'
import { fetchGlobalContext, type GlobalContextData, type GlobalContextParams, fetchLocalContext, type LocalContextData, type LocalContextParams, callAdd, callDivide, callEcho, callEchoTransform, callHttpOnlyEcho, callJwtObtain, callJwtRefresh, callMultiply, callPermissionCheckFn, callStaffOnly, callSuperuserOnly, callVerifiedOnly, callWhoami, callWsWhoami, type currentUserOutput, type greetOutput, } 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
}
// ── Local Context ──
const LocalCtx = createContext<ContextState<LocalContextData> | null>(null)
export function LocalContext({ children, ...params }: LocalContextParams & { children: ReactNode }) {
@@ -113,22 +106,42 @@ export function useGreet(): greetOutput | null {
return useLocalContext().data?.greet ?? null
}
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 useWhoami() {
return useMutation<void, Awaited<ReturnType<typeof callWhoami>>>(() => callWhoami() as any)
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 useEchoTransform() {
return useMutation<Parameters<typeof callEchoTransform>[0], Awaited<ReturnType<typeof callEchoTransform>>>(callEchoTransform)
}
export function useHttpOnlyEcho() {
return useMutation<Parameters<typeof callHttpOnlyEcho>[0], Awaited<ReturnType<typeof callHttpOnlyEcho>>>(callHttpOnlyEcho)
}
export function useJwtObtain() {
return useMutation<void, Awaited<ReturnType<typeof callJwtObtain>>>(() => callJwtObtain() as any)
}
export function useJwtRefresh() {
return useMutation<Parameters<typeof callJwtRefresh>[0], Awaited<ReturnType<typeof callJwtRefresh>>>(callJwtRefresh)
}
export function useMultiply() {
return useMutation<Parameters<typeof callMultiply>[0], Awaited<ReturnType<typeof callMultiply>>>(callMultiply)
}
export function usePermissionCheckFn() {
return useMutation<Parameters<typeof callPermissionCheckFn>[0], Awaited<ReturnType<typeof callPermissionCheckFn>>>(callPermissionCheckFn)
}
export function useStaffOnly() {
return useMutation<void, Awaited<ReturnType<typeof callStaffOnly>>>(() => callStaffOnly() as any)
}
@@ -141,36 +154,14 @@ export function useVerifiedOnly() {
return useMutation<void, Awaited<ReturnType<typeof callVerifiedOnly>>>(() => callVerifiedOnly() as any)
}
export function useMultiply() {
return useMutation<Parameters<typeof callMultiply>[0], Awaited<ReturnType<typeof callMultiply>>>(callMultiply)
}
export function useNotImplementedFn() {
return useMutation<void, Awaited<ReturnType<typeof callNotImplementedFn>>>(() => callNotImplementedFn() 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 useWhoami() {
return useMutation<void, Awaited<ReturnType<typeof callWhoami>>>(() => callWhoami() as any)
}
export function useWsWhoami() {
return useMutation<void, Awaited<ReturnType<typeof callWsWhoami>>>(() => callWsWhoami() as any)
}
export function useJwtObtain() {
return useMutation<void, Awaited<ReturnType<typeof callJwtObtain>>>(() => callJwtObtain() as any)
}
export function useJwtRefresh() {
return useMutation<Parameters<typeof callJwtRefresh>[0], Awaited<ReturnType<typeof callJwtRefresh>>>(callJwtRefresh)
}
// ── MizanContext root provider ──
export interface MizanContextProps {
/** Base URL for protocol endpoints. Defaults to "/api/mizan". */
baseUrl?: string
@@ -180,8 +171,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)
@@ -195,12 +186,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

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,13 @@
/**
* E2E Test Fixtures
*
* Each fixture uses GENERATED mizan hooks (not raw call()).
* Playwright reads the DOM to verify behavior.
*
* URL hash selects the fixture: #echo, #add, #multiply, etc.
* The URL hash selects which fixture component mounts; each one drives a
* generated mizan hook and writes the outcome into `data-testid` nodes that
* the Playwright spec reads.
*/
import { useState, useEffect, useRef } from 'react'
import { ChannelProvider } from 'mizan/channels'
// Generated typed hooks — the actual mizan API
import {
MizanContext,
useEcho,
useAdd,
useMultiply,
@@ -19,8 +15,8 @@ import {
useStaffOnly,
useSuperuserOnly,
useVerifiedOnly,
useNotImplementedFn,
useBuggyFn,
useEchoTransform,
useDivide,
usePermissionCheckFn,
useCurrentUser,
MizanError,
@@ -49,12 +45,11 @@ 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 'permission-error': return <PermissionError_ />
case 'permission-success': return <PermissionSuccess />
case 'not-implemented': return <UnsupportedEchoMode />
case 'internal-error': return <DivideByZero />
case 'permission-error': return <PermissionDenied />
case 'permission-success': return <PermissionGranted />
case 'context-current-user': return <ContextCurrentUser />
// Form fixtures removed — forms codegen deferred per Blazr scope
case 'channel-chat': return <ChannelChatFixture />
default: return <div data-testid="ready">Harness ready. Set #hash.</div>
}
@@ -117,15 +112,15 @@ function Multiply() {
}
function NotFound() {
// Deliberately call a non-existent function via the raw primitive
// Names a function the backend never registered, so no generated hook
// exists for it and the raw kernel primitive is the only way to send it.
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} />
}
function ValidationError() {
// Send wrong types to add (strings instead of numbers)
const { mutate } = useAdd()
const [error, setError] = useState<unknown>()
useEffect(() => { (mutate as any)({ a: 'not_a_number', b: 'also_not' }).catch(setError) }, [mutate])
@@ -152,22 +147,22 @@ function VerifiedOnly() {
return <Result data={data} error={error} />
}
function NotImplemented() {
const { data, error } = useRun(useNotImplementedFn)
function UnsupportedEchoMode() {
const { data, error } = useRun(useEchoTransform, { text: 'e2e-test', mode: 'rot13' })
return <Result data={data} error={error} />
}
function InternalError() {
const { data, error } = useRun(useBuggyFn)
function DivideByZero() {
const { data, error } = useRun(useDivide, { numerator: 1, denominator: 0 })
return <Result data={data} error={error} />
}
function PermissionError_() {
function PermissionDenied() {
const { data, error } = useRun(usePermissionCheckFn, { secret: 'wrong' })
return <Result data={data} error={error} />
}
function PermissionSuccess() {
function PermissionGranted() {
const { data, error } = useRun(usePermissionCheckFn, { secret: 'open-sesame' })
return <Result data={data} error={error} />
}
@@ -175,20 +170,19 @@ function PermissionSuccess() {
// ─── Context fixtures ───────────────────────────────────────────────────────
function ContextCurrentUser() {
// useCurrentUser throws if context not loaded yet, so catch that
try {
const user = useCurrentUser()
return <pre data-testid="result">{JSON.stringify(user)}</pre>
} catch {
return <div>loading context...</div>
}
const user = useCurrentUser()
if (user === null) return <div data-testid="pending">loading context</div>
return <pre data-testid="result">{JSON.stringify(user)}</pre>
}
// ─── Channel fixtures ───────────────────────────────────────────────────────
function ChannelChatFixture() {
// MizanContext already includes ChannelProvider
return <ChannelChat />
return (
<ChannelProvider>
<ChannelChat />
</ChannelProvider>
)
}
function ChannelChat() {
@@ -197,9 +191,8 @@ function ChannelChat() {
const prevStatus = useRef(chat.status)
useEffect(() => {
// Send once when status transitions to 'connected' (meaning subscribed)
// The hook maps subscribed → 'connected', but we need to wait for it
// to go through 'connecting' first (before subscription is confirmed)
// The hook reports 'connected' only once the server confirms the
// subscription, and it passes through 'connecting' on the way there.
const wasConnecting = prevStatus.current === 'connecting'
prevStatus.current = chat.status