Update ISSUES.md — 16 fixed, 22 remaining

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 12:30:52 -04:00
parent 9c837cf285
commit 07f1c7842c

151
ISSUES.md
View File

@@ -2,154 +2,99 @@
Identified by domain expert review (Cloudflare, Serverless, Vercel, React Query, Django, Laravel, Vue/Svelte).
## Critical
## Fixed
### C1. Scoped cache purge doesn't pass user_id/rev
**File:** `mizan-django/src/mizan/client/executor.py` — cache_purge call in function_call_view
The scoped purge recomputes the HMAC with `user_id=None, rev=0`, but the entry was stored with the actual user_id and rev. The key never matches. Stale data persists until TTL expires.
- ~~C1~~ Scoped cache purge now passes user_id
- ~~C2~~ initSession retries 3x, resets on failure
- ~~C3~~ SSR backend injects `__MIZAN_SSR_DATA__` script tag
- ~~C4~~ SSR bridge uses _write_lock for stdin
- ~~C5~~ SSR bridge registers atexit handler
- ~~C7~~ View-path mutations now purge origin cache
- ~~H1~~ pendingScoped is Array, not Map (no overwrite)
- ~~H2~~ stableKey() sorts JSON keys (order-independent)
- ~~H3~~ mizanFetch retries 2x on 5xx/network errors
- ~~H4~~ Named contexts skip refetch if SSR data exists
- ~~H6~~ refreshContext uses GET /ctx/ not POST /call/
- ~~H10~~ _meta always fresh dict
- ~~H11~~ Python normalizes True→"true" for cross-language HMAC
- ~~H13~~ isValid checks all required fields are touched
- ~~M11~~ execute_function return type includes HttpResponseBase
- ~~M18~~ registerContext cleanup uses ?. (no crash)
### C2. initSession swallows errors permanently
**File:** `mizan-runtime/src/index.ts` — initSession()
If the session init fetch fails, the promise resolves (catch swallows). Every subsequent call returns the resolved promise. CSRF is broken for the page lifetime with no recovery.
### C3. SSR hydration data never written
**File:** `mizan-django/src/mizan/ssr/backend.py` — MizanTemplate.render()
Generated code checks `window.__MIZAN_SSR_DATA__` but nothing injects it. The `<script>` tag with serialized data is missing from the SSR output. Client re-fetches everything, defeating SSR.
### C4. SSR bridge stdin.write not thread-safe
**File:** `mizan-django/src/mizan/ssr/bridge.py` — render()
The lock protects `_counter` but not the `stdin.write()`. Concurrent Django threads interleave writes, producing garbled JSON. Bun worker returns parse errors.
### C5. SSR bridge orphans Bun processes
**File:** `mizan-django/src/mizan/ssr/bridge.py` — no cleanup
No atexit handler, no signal handler. Django auto-reloader, gunicorn recycling, and SIGTERM all leak Bun processes.
## Remaining Critical
### C6. No loading/error/stale states in runtime
**File:** `mizan-runtime/src/index.ts` — context registry
The kernel stores only `{params, refetch}`. No `data`, `status`, `error`. Every adapter independently reinvents loading tracking. No stale-while-revalidate possible.
**File:** `mizan-runtime/src/index.ts`
The kernel stores only `{params, refetch}`. No `data`, `status`, `error`. Every adapter reinvents loading tracking. Blocks stale-while-revalidate.
### C7. View-path mutations don't purge origin cache
**File:** `mizan-django/src/mizan/client/executor.py` — view-path return branch
Sets `X-Mizan-Invalidate` header but never calls `cache_purge`. Without Edge deployed, stale data persists.
## High
### H1. pendingScoped Map overwrites
**File:** `mizan-runtime/src/index.ts` — invalidate()
Two scoped invalidations for the same context in one microtask — second `set()` overwrites first. Only one param set gets refetched.
### H2. JSON.stringify params key is order-dependent
**File:** `mizan-runtime/src/index.ts` — registerContext + invalidation
`{a:1, b:2}` !== `{b:2, a:1}` as JSON strings. Scoped invalidation misses targets depending on property order.
### H3. No retry logic
**File:** `mizan-runtime/src/index.ts` — mizanFetch, mizanCall
Single network failure is permanent. No exponential backoff, no retry count.
### H4. Named contexts refetch unconditionally, ignore SSR data
**File:** `mizan-django/generate/generator/lib/adapters/react.mjs` — useEffect
Global context checks `if (!data) refetch()`. Named contexts always refetch, discarding hydrated state.
## Remaining High
### H5. Mutation hooks expose no loading/error state
**File:** `mizan-django/generate/generator/lib/adapters/react.mjs` — mutation template
Returns bare `useCallback`. No `isPending`, `error`, `isSuccess`. Every handler needs manual try/catch/finally.
### H6. refreshContext uses call() (mutation endpoint)
**File:** `mizan-react/src/context.tsx` — refreshContext callback
Calls `POST /call/` for context refresh. Should use `GET /ctx/<name>/`.
**File:** `mizan-django/generate/generator/lib/adapters/react.mjs`
Returns bare `useCallback`. No `isPending`, `error`, `isSuccess`.
### H7. Redis SCAN blocks request path at scale
**File:** `mizan-django/src/mizan/cache/backend.py` — delete_by_prefix
Synchronous SCAN loop with 1K batch. At 1M keys: multi-second blocking on the mutation response.
**File:** `mizan-django/src/mizan/cache/backend.py`
Synchronous SCAN at 1M keys: multi-second blocking.
### H8. Svelte codegen uses Svelte 4 stores
**File:** `mizan-django/generate/generator/lib/adapters/svelte.mjs`
Uses `writable`/`derived` from `svelte/store`. Svelte 5 (stable since 2024) uses `$state`/`$derived` runes.
Should use Svelte 5 `$state`/`$derived` runes.
### H9. Svelte destroy() not auto-called
**File:** `mizan-django/generate/generator/lib/adapters/svelte.mjs`
`registerContext` is called at store creation. `destroy()` is returned but never called automatically. Memory leak if user forgets `onDestroy`.
### H10. _meta ClassVar shared dict fragility
**File:** `mizan-django/src/mizan/client/function.py` — _create_server_function
When `meta` is empty, `FunctionWrapper._meta` is the same object as `ServerFunction._meta`. One in-place mutation pollutes all functions.
### H11. Cross-language str() vs String() divergence
**File:** `mizan-django/src/mizan/cache/keys.py` — derive_cache_key
Python `str(True)` = `"True"`, JS `String(true)` = `"true"`. Cache keys diverge for boolean/None params.
Memory leak if user forgets `onDestroy`.
### H12. Forms triggerValidation captures stale data
**File:** `mizan-react/src/forms.ts` — triggerValidation useCallback
Debounced touch() fires validation against the `data` value from when `useCallback` was created, not current data.
**File:** `mizan-react/src/forms.ts`
Debounced validation uses stale closure data.
### H13. Forms isValid true with untouched required fields
**File:** `mizan-react/src/forms.ts` — isValid useMemo
Only checks touched fields. 5 required fields, 2 touched, 0 errors → `isValid = true`.
## Medium
## Remaining Medium
### M1. SSR bridge not fork-safe
gunicorn prefork shares stdin/stdout file descriptors and Redis connections.
gunicorn prefork shares file descriptors and Redis connections.
### M2. cache_purge_user() spec'd but not implemented
No way to purge all cache entries for one user on permission change.
### M2. cache_purge_user() not implemented
No way to purge all cache entries for one user.
### M3. No garbage collection for context entries
Runtime `contexts` Map grows monotonically. No TTL, no eviction.
Runtime `contexts` Map grows monotonically.
### M4. No cross-tab invalidation
No BroadcastChannel, no storage events. Logout in tab 1 doesn't affect tab 2.
No BroadcastChannel. Logout in tab 1 doesn't affect tab 2.
### M5. React 18 Strict Mode double-fetch
useEffect runs twice in dev mode. Every context provider fires two requests on mount.
useEffect runs twice in dev mode.
### M6. No request deduplication
Two components mounting the same context fire parallel identical fetches.
Two components mounting same context fire parallel fetches.
### M7. SSR worker module cache never invalidates
Dynamic imports cached in Map forever. No reload mechanism.
Dynamic imports cached forever.
### M8. Vue injection key not exported
Consumers can't inject directly without using generated composables.
Can't inject directly without generated composables.
### M9. Vue onMounted won't pre-fetch in Vue SSR
Needs `onServerPrefetch` for Nuxt/Vue SSR. Only works with Mizan's bridge.
Needs `onServerPrefetch` for Nuxt.
### M10. Svelte should use setContext/getContext
Module-level stores don't scope to component tree. Two instances share state.
### M11. execute_function return type annotation wrong
Says `FunctionResult | FunctionError`, actually returns `HttpResponseBase` for view-path.
Module-level stores don't scope to component tree.
### M12. render_strategy heuristic uses hardcoded param names
`user_id`, `owner_id` etc. — misses `member_id`, `customer_id`, non-English names.
Misses `member_id`, `customer_id`, non-English names.
### M13. initSession called for token-auth requests
Wastes a GET /session/ round-trip for JWT/MWT apps that don't need CSRF.
Wastes GET /session/ round-trip for JWT/MWT apps.
### M14. Vue watch imported but unused
Params not watched — if reactive params change, context doesn't refetch.
Params not watched — reactive param changes don't trigger refetch.
### M15. Vue mutation composables are just re-exports with misleading `use` prefix
`export const useXxx = callXxx` — not a real composable, breaks Vue naming convention.
### M15. Vue mutation composables misleading `use` prefix
`export const useXxx = callXxx` — not a real composable.
### M16. Svelte mutation imports bypass Stage 1 index
Imports from `'../mutations/xxx'` directly instead of `'../index'`.
Should import from `'../index'` consistently.
### M17. Side effects in React state updater
Context listeners called inside `setContextStore()` updater — fires multiple times in concurrent mode.
### M18. registerContext cleanup crashes if Map deleted
`contexts.get(name)!.delete(key)` — non-null assertion throws if Map was cleared.
## Testing Gaps
- No test for scoped purge with user_id/rev mismatch (C1)
- No test for view-path cache purge (C7)
- No test for concurrent SSR renders (C4)
- No test for fork-safety (M1)
- No cross-language boolean/None pin tests (H11)
- No test for initSession failure recovery (C2)
- No test for SSR hydration data injection (C3)
- No test for pendingScoped overwrite (H1)
- No test for JSON key ordering mismatch (H2)
Context listeners called inside `setContextStore()` updater.