Restructure tree by role; rename mizan-runtime → mizan-base

packages/ flattens into:
  backends/   server protocol adapters (mizan-django, mizan-ts)
  frontends/  client kernel + framework adapters (mizan-base, mizan-react, mizan-vue, mizan-svelte)
  workers/    runtime workers (mizan-ssr)
  cores/      shared language-level primitives (empty for now; mizan-python forthcoming)

The frontend kernel (was packages/mizan-runtime, now frontends/mizan-base) is
renamed to reflect its role — it's the shared base that frontend adapters
depend on directly. Reflects the substrate position that per-framework adapters
wrap a single shared kernel; codegen targets the adapter, not the raw kernel.

Path updates landed in: Makefile, two Gitea workflows, Dockerfile.test, four
example/harness config files, .claude/settings.local.json, four docs
(CLAUDE/ISSUES/ROADMAP/AFI_ARCHITECTURE), four codegen templates (stage1 +
react/vue/svelte adapters), and three package.jsons (the mizan-base rename
plus mizan-vue/svelte peerDeps).

Generated files under examples/django-react-site/harness/src/api/ still
reference @mizan/runtime — left as-is; they're regenerated artifacts and
the harness is non-functional pending the React wrapper-layer codegen.

Also folded in a pre-existing fix: the Gitea workflows had
working-directory: react / django pointing at a layout that predates
packages/, never updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-05 20:55:37 -04:00
parent 6eca514777
commit fe39fcb229
126 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "@mizan/ssr",
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
},
"devDependencies": {
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"bun-types": "latest",
},
},
},
"packages": {
"@types/node": ["@types/node@25.5.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg=="],
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
"bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="],
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
"react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
}
}

View File

@@ -0,0 +1,20 @@
{
"name": "@mizan/ssr",
"version": "0.1.0",
"description": "Mizan SSR worker — renders React components to HTML via stdin/stdout JSON-RPC.",
"type": "module",
"main": "src/worker.tsx",
"scripts": {
"test": "bun test"
},
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"bun-types": "latest",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0"
},
"license": "MIT"
}

View File

@@ -0,0 +1,2 @@
// The SSR package is a Bun worker subprocess (worker.tsx).
// It is not imported as a library. Django's SSRBridge spawns it.

View File

@@ -0,0 +1,29 @@
/**
* Test SSR worker — registers simple components for the test suite.
*/
import { registerComponent } from './worker'
// Simple component that renders props
function Hello({ name }: { name: string }) {
return <div>Hello, {name}!</div>
}
// Component that renders a list
function UserProfile({ user_id, name }: { user_id: number; name: string }) {
return (
<div className="profile">
<h1>{name}</h1>
<span>ID: {user_id}</span>
</div>
)
}
// Component that throws during render
function Broken() {
throw new Error('Intentional render error')
}
registerComponent('Hello', Hello)
registerComponent('UserProfile', UserProfile)
registerComponent('Broken', Broken)

View File

@@ -0,0 +1,72 @@
import { renderToString } from 'react-dom/server'
import { createElement } from 'react'
const cache = new Map<string, any>()
function respond(msg: Record<string, any>): void {
Bun.write(Bun.stdout, JSON.stringify(msg) + '\n')
}
async function handleMessage(msg: any): Promise<void> {
if (msg.method === 'ping') {
respond({ id: msg.id, pong: true })
return
}
if (msg.method === 'render') {
const { file, props } = msg.params ?? {}
if (!file) {
respond({ id: msg.id, error: 'Missing file path' })
return
}
try {
let mod = cache.get(file)
if (!mod) {
mod = await import(file)
cache.set(file, mod)
}
const Component = mod.default || Object.values(mod)[0]
if (!Component) {
respond({ id: msg.id, error: `No component exported from ${file}` })
return
}
const html = renderToString(createElement(Component, props ?? {}))
respond({ id: msg.id, html })
} catch (e: any) {
respond({ id: msg.id, error: e.message })
}
return
}
respond({ id: msg.id, error: `Unknown method: ${msg.method}` })
}
async function processInput(): Promise<void> {
const reader = Bun.stdin.stream().getReader()
const decoder = new TextDecoder()
let buffer = ''
while (true) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
let i: number
while ((i = buffer.indexOf('\n')) !== -1) {
const line = buffer.slice(0, i).trim()
buffer = buffer.slice(i + 1)
if (line) {
try { await handleMessage(JSON.parse(line)) }
catch (e: any) { respond({ id: -1, error: e.message }) }
}
}
}
}
respond({ id: 0, ready: true })
processInput()