Docs carry the shape of the system, not its status

MIZAN.md's phase-numbered implementation order, PRODUCT_ARCHITECTURE's
"Deferred until Render revenue funds it" and "Shipped in", PSR_VS_EDGE's
current-state section, and the READMEs' passing-test counts were all reporting
where the work stood rather than what the system is. OWED_SURFACE keeps its
subject — surface that is specified but unbuilt — stated as the shape each unit
owes.

The channel sections follow the renamed slots: Params / ClientMessage /
ServerMessage, and Channel as the base class on both backends.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-27 14:03:29 -04:00
parent 3aafec6dd4
commit e00b3a177e
10 changed files with 243 additions and 213 deletions

View File

@@ -1,19 +1,12 @@
# MIZAN — Named Contexts & Mutation Architecture
> **Historical design spec.** The original named-contexts / mutation design
> document from the January 2025 design conversation. Kept as a record of design
> intent, not as a description of the current build — names and surfaces here
> predate the implementation (the codegen is the Rust binary
> `protocol/mizan-codegen`, never shipped under the working name "Maison"). For
> current architecture, read `CLAUDE.md` (wire protocol, package layout, codegen
> state) and `docs/` (`AFI_ARCHITECTURE.md`, `SSR_ARCHITECTURE.md`,
> `CACHE_KEYING.md`, `MWT_SPEC.md`).
## For Claude Code
This plan was written by Ryth's Claude.ai session after an extended design conversation
reviewing the full codebase, the original @compose discussion from January 2025, and
several rounds of architectural refinement.
The design spec for the named-contexts and mutation-invalidation surface: the
developer-facing API tiers, param elevation, context bundling, the `affects`
invalidation graph, and the `ReactContext` read/write class form. Wire protocol
and package layout live in `CLAUDE.md`; subsystem architecture lives in `docs/`
(`AFI_ARCHITECTURE.md`, `SSR_ARCHITECTURE.md`, `CACHE_KEYING.md`,
`MWT_SPEC.md`). The codegen that consumes this surface is the Rust binary
`protocol/mizan-codegen`.
---
@@ -39,7 +32,7 @@ the class form. `@client` + `affects` covers 95% of cases.
---
## 1. Named Contexts (replacing context='local' and @compose)
## 1. Named Contexts
### How it works
Any string passed to `context=` becomes a named context. Functions and classes sharing
@@ -176,7 +169,7 @@ GET /api/mizan/ctx/global/
No params. Fetched once. SSR-hydrated.
### Mutation calls
Non-context `@client` functions (including those with `affects`) use the existing
Non-context `@client` functions (including those with `affects`) use the
POST endpoint:
```
POST /api/mizan/call/
@@ -383,7 +376,7 @@ Mutation is business logic, not automation.
## 6. Discovery and Registration
### @client functions
Discovered via `clients.py` convention (DjangoAppVisitor), same as current.
Discovered via the `clients.py` convention (DjangoAppVisitor).
### ReactContext classes
Same discovery. Classes inheriting from `ReactContext` found in `clients.py` are
@@ -394,54 +387,24 @@ are detected at registration time.
- Duplicate names within same context → error
- Mixed WebSocket transport within context → error
- `receive` defined without `send` → error
- `affects` referencing a non-existent context name or function → error (or warning)
- `affects` referencing a non-existent context name or function → error
---
## 7. What to Remove / Deprecate
## 7. Surfaces the named-context design subsumes
- `context='local'` → replaced by any non-'global' context string
- `@compose` decorator → replaced by shared context names
- `ComposedContext` class → remove from public API
- `on_server` flag → default behavior (contexts always bundled)
- `share` prop pattern → replaced by param elevation + `specify`
Each of these is expressed by a primitive above rather than by a mechanism of
its own, which is why none of them is part of the public API:
- A per-component local scope → any non-`'global'` context string
- Composition of several read functions into one fetch → a shared context name
- A composed-context class → the shared context name plus `ReactContext`
- A per-function "bundle on the server" flag → contexts are always bundled
- A prop that shares params down a subtree → param elevation + `specify`
---
## 8. Implementation Order
### Phase 1: Named contexts (core feature)
1. Accept any string for `context=` (not just 'global'/'local')
2. Group functions by context name in the registry
3. Add context bundling endpoint: `GET /api/mizan/ctx/<name>/`
4. Update codegen to produce named providers with param elevation
5. Update codegen to produce `specify` prop handling
6. Make `context='global'` use the same mechanism, just auto-mounted
### Phase 2: affects invalidation
1. Add `affects` parameter to `@client` decorator
2. Accept string (context name), function reference, or list
3. Store affects metadata in the function's `_meta` dict
4. Export affects relationships in the schema
5. Update codegen: mutation hooks auto-invalidate after success
6. Frontend: invalidation checks if affected context is mounted before refetching
### Phase 3: ReactContext classes
1. Implement `ReactContext` base class with metaclass magic for the string arg
2. `send` method registered as a context function (same as @client with context)
3. `receive` method registered as a commit handler
4. Commit endpoint: `POST /api/mizan/ctx/<name>/commit/`
5. Update codegen: produce commit hooks for classes with `receive`
6. Auto-refetch after commit, with optional fresh-data-from-receive optimization
### Phase 4: Cleanup
1. Remove `@compose` from public API and docs
2. Remove `context='local'` (accept for backwards compat with deprecation warning)
3. Update README and all examples
---
## 9. The Developer's Mental Model
## 8. The Developer's Mental Model
Write functions. Name your contexts. Declare what affects what.
The framework generates the client, handles the caching, and runs the invalidation.