ReactContext('user') creates a reusable context marker that provides
proper linting, find-references, and autocomplete:
UserContext = ReactContext('user')
@client(context=UserContext)
def user_profile(request, user_id: int) -> ProfileShape: ...
@client(affects=UserContext)
def edit_profile(request, name: str) -> dict: ...
@client(affects=[UserContext, OrderContext])
def change_plan(request) -> dict: ...
- ReactContext class with name validation
- GlobalContext built-in instance for context='global'
- affects= accepts ReactContext, lists, strings, or function refs
- Backwards compat: raw strings still work for context= and affects=
- Exported from mizan and mizan.client
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Generator (react/src/generator/lib/mizan.mjs):
- Rename djarea.mjs → mizan.mjs
- MizanContext replaces DjangoContext (legacy alias kept)
- Global contexts fetched via bundled GET /ctx/global/ through
inner GlobalContextLoader component
- Named context providers generated per context group with param
elevation (required/optional props from x-mizan-contexts)
- Mutation hooks auto-invalidate affected contexts on success
- SSR hydration uses single GET /ctx/global/ instead of N POSTs
- Output files: generated.provider.tsx, generated.server.ts
Runtime (react/src/context.tsx):
- Add setContextData() for bundle splitting without refetch
- Add request() for auth-transparent HTTP from generated code
Index generator updated for new export names and named contexts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 1 (Named Contexts):
- @client(context=) accepts any string, not just 'global'/'local'
- context='local' emits deprecation warning
- Registry groups functions by context name (get_context_groups)
- GET /api/mizan/ctx/<name>/ bundles all context functions in one response
- Schema export includes x-mizan-contexts with param elevation metadata
Phase 2 (Affects):
- @client(affects=) declares mutation invalidation targets
- Accepts context name strings, function refs, or lists
- Mutually exclusive with context=
- Exported in x-mizan-functions schema for codegen
React runtime:
- MizanContextValue gains invalidateContext, invalidateFunctions,
registerContextProvider, and baseUrl
- Named context providers register for invalidation on mount
259 Django tests pass, 33 React tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root directory now contains only the two core packages (django/, react/),
examples/, and top-level docs. All e2e/integration test infrastructure
lives in examples/django-react-site/.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rename the package from djarea to mizan across the entire codebase —
Python package, React library, generators, tests, and examples. Fix
JSX/hook casing (MizanProvider, useMizan, etc.) that broke when the
original PascalCase names were lowercased during the rename.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Named contexts, param elevation, affects-based invalidation,
ReactContext classes for read/write coupling. This is the evolution
roadmap from Djarea to Mizan.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Set field-only _spec before building the full spec with nested shapes.
Self-references resolve because cls._spec already exists when the
generator encounters shape._spec where shape is cls.
Also: pass cls into get_type_hints localns so forward ref strings
like list["CategoryShape"] resolve to the class being defined.
49 shapes tests, 0 skipped.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
_extract_shape_class now handles `Shape | None` (Union types) by checking
isinstance(hint, types.UnionType) and iterating args for Shape subclasses.
This fixes nullable FK detection — any `editor: AuthorShape | None` field
is now correctly recognized as a nested shape.
48 stress tests covering:
- 5-level deep nesting (Publisher → Author → Book → Chapter → Section)
- Two FKs to same model (author + editor)
- Slug PK (Tag), UUID PK (Section)
- M2M relationships (Book.tags)
- Nullable FKs returning None
- Empty strings, zero integers, false booleans (truthiness traps)
- 100-record smoke test
- Query efficiency (assertNumQueries)
- All diff operations with deep nesting
Known gap documented: self-referential forward refs (CategoryShape)
crash get_type_hints() at __init_subclass__ time. Needs deferred resolution.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
25 tests covering Shape, Diff, and NestedDiff:
- Shape metaclass: model resolution, field extraction, nested detection, spec/pair building
- Query: list, filter, nested relations, empty results, Pydantic serialization
- Diff (new): detects all fields as changed
- Diff (existing): no changes, single field, multiple fields, nonexistent ID
- Diff (nested): created, updated, deleted, combined, nonexistent relation
Fixes:
- django-readers moved from optional to core dependency
- Shape import lazy-loaded via __getattr__ (django_readers imports
contenttypes which can't happen during apps.populate())
- Added Author, Book, Tag test models
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Imported from separate development branch. Provides Shape, Diff, and
NestedDiff classes for defining typed Pydantic schemas backed by Django
model querysets via django-readers.
Optional dependency: install with djarea[shapes] to get django-readers.
Import is guarded so the rest of djarea works without it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Test infrastructure:
- Django standalone test runner (pytest-django, test settings, EmailUser model)
- React unit tests via Vitest with jsdom, jest compat layer, path aliases
- Playwright E2E tests using generated hooks in a real Chromium browser
- Docker Compose test backend (Django + Redis) for integration testing
- Desktop integration test app (PyWebView + Django + uvicorn)
- Makefile with test/test-django/test-react/test-integration targets
Library bugs found and fixed:
- hasJWT truthiness: undefined !== null was true, skipping session init
- process.env crash: CSR client referenced process.env in non-Node browsers
- baseUrl not forwarded: DjareaProvider didn't pass baseUrl to CSR client
- Relative URL handling: new URL() failed with relative base paths
- call() race condition: HTTP requests fired before CSRF cookie was set
- Session init await: added sessionRef promise so call() waits for session
- path_prefix on schema export: both export commands failed with URL reverse
- NullBooleanField removed: referenced field doesn't exist in Django 5.0+
- lru_cache on JWT settings: get_settings() now cached as intended
- Channel message routing: broadcasts now include channel name and params
- httpFunctionCall: fixed URL and request body format
Generator fixes:
- Removed 1,100 lines of REST/OpenAPI client generation (not part of Djarea)
- Generator now works for djarea-only projects without django-ninja REST APIs
- Generated DjangoContext now includes ChannelProvider when channels exist
- Fixed env var passthrough for schema export commands
- Deduplicated fetch logic into single runDjangoCommand helper
Test quality:
- Fixed 33 tautological Django tests with real assertions
- Found hidden bug: benchmark functions were never registered
- Found hidden bug: unicode lookalike test used plain ASCII
- Deleted worthless React unit tests (duplicates, shape checks, Zod-tests-Zod)
- Replaced jsdom integration tests with Playwright browser tests
Example apps:
- example/: Integration test backend with 33 server functions, 5 forms,
4 channels covering auth variations, contexts, class-based ServerFunction,
error codes, DjareaFormMixin, formsets, and JWT
- desktop/: PyWebView desktop app with file system access, SQLite CRUD,
system introspection, and 39 real HTTP integration tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>