Commit Graph

8 Commits

Author SHA1 Message Date
7fb0c4a400 Mutation→context merge primitive across the stack
The @client(merge=[context, ...]) decorator lets a mutation patch its
return value directly into the cached context bundle by matching the
mutation's Output type against each context-function's Output type
to identify the slot, then splicing server-side. Kernel runs
splice_slot on the response to apply locally — no refetch, no
invalidate-cascade.

Lands H14, H15, H16, M19, M20 from ISSUES.md.

Backends (Django + FastAPI):
  _resolve_merges() in both executors walks @client(merge=...) targets,
  resolves the per-context slot via types_match_for_merge, and emits
  {context, slot, value, params?} entries on the response. Param
  auto-scoping mirrors _resolve_invalidation's tier-1 logic.

Frontend kernel (mizan-base):
  Response handler reads the merge[] array and applies splice_slot
  for each entry — locates the cached context bundle by name+params,
  overwrites the named slot with the new value, notifies subscribers.

Core (mizan-python):
  @client decorator extended with merge= parameter. Schema export
  threads merge metadata onto the OpenAPI x-mizan-functions entries.

Examples / fixtures:
  fastapi-react-site harness exercises merge + Playwright spec covers
  the end-to-end happy path (mutation → instant UI update without
  network refetch). AFI fixture's rename_user function is the
  canonical merge target.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 18:29:06 -04:00
aaaf80cdbf End-to-end: harness Playwright suite green (14 pass, 1 skip)
After the React-codegen rework, ran the full e2e harness against the
docker-stack backend. Surfaced and fixed real friction:

mizan-base/src/index.ts (kernel):
- MizanError now parses both error envelopes — the FastAPI shape
  ({"error": {"code", "message", "details"}}) and the Django shape
  ({"error": true, "code", "message", "details"}). Exposes .code and
  .details on the thrown error so consumer code can branch on them.
  This was needed for the harness's `instanceof MizanError && error.code
  === 'NOT_FOUND'` pattern to work; the previous MizanError only carried
  status + raw body, leaving callers to parse the body themselves.

examples/django-react-site/Dockerfile.test:
- Backend image now copies and installs cores/mizan-python before
  installing mizan-django (which imports from mizan_core after the
  Layer 1 extraction).

harness/src/fixtures.tsx:
- useRun helper updated for the new mutation-hook shape: pulls
  { mutate } off the hook result instead of treating the hook return
  as a callable. Same for ValidationError fixture.

mizan.spec.ts:
- DjangoError → MizanError (kernel error class is backend-agnostic).
- Form tests removed (forms codegen deferred per Blazr scope).
- Channel test marked test.skip (channels deferred per Blazr scope).

.gitignore: ignore Playwright test-results/.

Final verification across all surfaces:
- mizan-core unit:       15/15
- mizan-django unit:     348 pass, 21 skip
- mizan-fastapi unit:    11/11
- mizan-ts edge-compat:  34/34 (cross-language HMAC pin)
- harness e2e (Playwright): 14/15 (1 skip = channels deferred)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:38:52 -04:00
2982741aad React wrapper-layer codegen — restore the idioms over the kernel
The harness was written against the MIZAN.md oracle (<MizanContext>,
provider-per-context, useMizan, etc.) but the codegen had been narrowed
to just hooks-direct-on-kernel after the kernel split. Restoring the
React-idiomatic layer on top of the kernel.

backends/mizan-django/generate/generator/lib/adapters/react.mjs:
- Emits <MizanContext baseUrl="…"> root provider that calls configure()
  once and (if a global context is registered) wraps children in
  <GlobalContextProvider>.
- Emits <GlobalContextProvider> + <{Name}Context> per named context —
  kernel registration happens once per provider mount, not per hook
  call. Consumers read from React Context.
- Base hooks: useGlobalContext() / use{Name}Context() return full
  ContextState<T> (data + status + error).
- Convenience hooks per context-function (use{Fn}() returns data | null)
  and per regular function/mutation (use{Fn}() returns
  { mutate, isPending, error }).
- useMizan() returns { call, fetch } as an imperative escape hatch
  for test harnesses or rare cases where typed hooks don't fit.
- Re-exports MizanError, configure, initSession, ContextState from
  @mizan/base.

backends/mizan-django/generate/generator/cli.mjs:
- After Stage 2, appends `export * from './<adapter>'` to index.ts so
  `import { useEcho, MizanContext } from './api'` works as a barrel.

Bug fixes surfaced during integration:
- react.mjs was generating `from '../index'` (wrong path); flat layout
  needs `./index`.
- harness django.config.mjs had `output: 'src/api/generated.ts'` which
  the codegen treated as a directory; corrected to `output: 'src/api'`.
- example testapp/clients.py imported from the deleted
  mizan.setup.registry path; routed through mizan.setup aggregator.

harness/package.json: adds @mizan/base dep so the generated react.tsx
can resolve its kernel imports.

harness/src/fixtures.tsx:
- DjangoError → MizanError (kernel error class, backend-agnostic).
- useChatChannel sourced from ./api/channels.hooks directly (not
  re-exported from the unified index for now).
- Form fixtures removed — forms codegen deferred per Blazr scope.

Verified: harness `vite build` succeeds, 53 modules transformed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 17:21:49 -04:00
bb88fd984b C6: Runtime kernel owns data, status, error — adapters subscribe
The kernel is no longer a blind refetch pipe. Each context entry has:
  { data, status: idle|loading|success|error, error }

registerContext() returns { getState, subscribe, refetch, unregister }.
Adapters subscribe to state changes via callbacks. The kernel does
the fetch and notifies subscribers with the new state.

React adapter uses useSyncExternalStore for tear-free reads.
Vue adapter uses ref + subscribe callback.
Svelte adapter uses readable store backed by kernel subscription.

All three adapters also get:
- Mutation hooks with { mutate, isPending, error } (fixes H5)
- Vue: onServerPrefetch for Nuxt SSR (fixes M9)
- Svelte: readable store auto-cleans up on unsubscribe (fixes H9)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:38:53 -04:00
c20de182e1 Two-stage codegen: React + Vue + Svelte from one schema
Stage 1 (framework-agnostic):
  types.ts           — OpenAPI interfaces
  contexts/<name>.ts — fetchXxxContext(params) using mizanFetch
  mutations/<name>.ts — callXxx(args) using mizanCall
  functions/<name>.ts — callXxx(args) using mizanCall
  index.ts           — re-exports

Stage 2 (per framework):
  react.tsx  — hooks + context providers + SSR hydration
  vue.ts     — composables with provide/inject + ref/computed
  svelte.ts  — writable/derived store factories

New packages:
  mizan-runtime — the kernel (~200 lines, zero framework deps)
    configure(), initSession(), registerContext(), invalidate(),
    mizanFetch(), mizanCall(), MizanError
  mizan-vue     — Vue adapter (package.json, codegen template)
  mizan-svelte  — Svelte adapter (package.json, codegen template)

CLI: mizan-generate --target react,vue,svelte
Config: target: 'react' (default) in django.config.mjs

Verified: codegen produces 33 functions across 2 contexts,
14 plain functions, 0 mutations, generating all three Stage 2
outputs from one schema fetch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:09:35 -04:00
6108845d99 Rename all djarea references to mizan
- djarea_clients.py → clients.py (both example apps)
- export_djarea_schema → export_mizan_schema (management command)
- djarea.spec.ts → mizan.spec.ts (playwright test)
- fetch.mjs: command name updated
- apps.py/asgi.py: import paths updated
- Removed stale generated.djarea.* artifacts
- Fixed desktop app: asgi.py import, vite config aliases, package.json dep path

373 Django tests pass. Both example apps verified running.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:26:19 -04:00
658cbebce1 Regenerate example code + fix broken paths from repo restructure
- Fix testapp/apps.py: import djarea_clients (file was never renamed)
- Fix fetch.mjs: command is export_djarea_schema not export_mizan_schema
- Fix harness package.json: dependency path to mizan-react after restructure
- Add package.json for generator (openapi-typescript dependency)
- Regenerate all example code with new protocol format:
  - generated.provider.tsx uses raw context responses + SSR hydration
  - generated.server.ts uses GET /ctx/global/ with response.ok check
  - generated.forms.ts, channels.ts, channels.hooks.tsx refreshed
- Remove stale generated.django.tsx and generated.django.server.ts
- Update imports: fixtures.tsx and main.tsx import from ./api (index)
- Use MizanContext instead of deprecated DjangoContext in examples

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 03:13:35 -04:00
eee352d908 Move desktop and e2e into examples/ directory
- desktop/ → examples/django-react-desktop-app/
- e2e/ → examples/django-react-site/
- example/ → examples/django-react-site/backend/
- Update Dockerfile.test, Makefile, playwright config, and
  django.config.mjs path references

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:41:31 -04:00