Rename djarea to mizan and fix React casing conventions

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>
This commit is contained in:
2026-03-31 20:01:03 -04:00
parent bf837e598b
commit c866142770
118 changed files with 1778 additions and 1433 deletions

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env python
"""
Djarea Desktop — PyWebView + Django local RPC.
mizan Desktop — PyWebView + Django local RPC.
Starts a local Django ASGI server and opens a native desktop window.
All communication between the UI and backend uses Djarea server functions.
All communication between the UI and backend uses mizan server functions.
"""
import os
@@ -63,7 +63,7 @@ def main():
base_url = f"http://{host}:{port}"
if not wait_for_server(f"{base_url}/api/djarea/session/"):
if not wait_for_server(f"{base_url}/api/mizan/session/"):
print("ERROR: Django server failed to start", file=sys.stderr)
sys.exit(1)
@@ -83,7 +83,7 @@ def main():
import webview
window = webview.create_window(
title="Djarea Desktop",
title="mizan Desktop",
url=base_url,
width=1024,
height=768,

View File

@@ -6,8 +6,8 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings")
django.setup()
from django.core.asgi import get_asgi_application
from djarea import wrap_asgi
from mizan import wrap_asgi
import backend.djarea_clients # noqa: F401
import backend.mizan_clients # noqa: F401
application = wrap_asgi(get_asgi_application())

View File

@@ -1,7 +1,7 @@
"""
Desktop RPC server functions.
Tests Djarea's appropriateness for desktop apps:
Tests mizan's appropriateness for desktop apps:
- Local file system access
- SQLite CRUD
- System introspection
@@ -20,10 +20,10 @@ from pathlib import Path
from django.http import HttpRequest
from pydantic import BaseModel
from djarea.client import client
from djarea.channels import ReactChannel
from djarea.setup.registry import register
from djarea.channels import register as register_channel
from mizan.client import client
from mizan.channels import ReactChannel
from mizan.setup.registry import register
from mizan.channels import register as register_channel
# =============================================================================
@@ -40,12 +40,12 @@ class SystemInfoOutput(BaseModel):
home_dir: str
cwd: str
cpu_count: int
djarea_version: str
mizan_version: str
@client(websocket=True)
def system_info(request: HttpRequest) -> SystemInfoOutput:
import djarea
import mizan
return SystemInfoOutput(
os_name=platform.system(),
@@ -56,7 +56,7 @@ def system_info(request: HttpRequest) -> SystemInfoOutput:
home_dir=str(Path.home()),
cwd=os.getcwd(),
cpu_count=os.cpu_count() or 1,
djarea_version=getattr(djarea, "__version__", "dev"),
mizan_version=getattr(mizan, "__version__", "dev"),
)
@@ -114,16 +114,20 @@ def list_files(request: HttpRequest, directory: str = "~") -> ListFilesOutput:
entries = []
try:
for entry in sorted(dir_path.iterdir(), key=lambda e: (not e.is_dir(), e.name.lower())):
for entry in sorted(
dir_path.iterdir(), key=lambda e: (not e.is_dir(), e.name.lower())
):
try:
stat = entry.stat()
entries.append(FileEntry(
name=entry.name,
path=str(entry),
is_dir=entry.is_dir(),
size=stat.st_size if not entry.is_dir() else 0,
modified=datetime.fromtimestamp(stat.st_mtime).isoformat(),
))
entries.append(
FileEntry(
name=entry.name,
path=str(entry),
is_dir=entry.is_dir(),
size=stat.st_size if not entry.is_dir() else 0,
modified=datetime.fromtimestamp(stat.st_mtime).isoformat(),
)
)
except (PermissionError, OSError):
continue
except PermissionError:
@@ -268,7 +272,9 @@ register(list_notes, "list_notes")
@client(websocket=True)
def create_note(request: HttpRequest, title: str, content: str = "", pinned: bool = False) -> NoteOutput:
def create_note(
request: HttpRequest, title: str, content: str = "", pinned: bool = False
) -> NoteOutput:
from backend.models import Note
note = Note.objects.create(title=title, content=content, pinned=pinned)
@@ -403,7 +409,7 @@ def app_info(request: HttpRequest) -> AppInfoOutput:
from django.conf import settings
return AppInfoOutput(
app_name="Djarea Desktop",
app_name="mizan Desktop",
uptime_seconds=round(time.time() - _start_time, 2),
db_path=str(settings.DATABASES["default"]["NAME"]),
pid=os.getpid(),

View File

@@ -1,5 +1,5 @@
"""
Django settings for the Djarea desktop integration test app.
Django settings for the mizan desktop integration test app.
Runs entirely local: SQLite database, in-memory channel layer,
no external services required.

View File

@@ -27,7 +27,7 @@ def serve_dist(request, path="index.html"):
urlpatterns = [
path("api/djarea/", include("djarea.urls")),
path("api/mizan/", include("mizan.urls")),
re_path(r"^(?P<path>assets/.+)$", serve_dist),
path("favicon.ico", serve_dist, {"path": "favicon.ico"}),
path("", serve_dist),

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Djarea Desktop</title>
<title>mizan Desktop</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: system-ui, -apple-system, sans-serif; background: #0f0f0f; color: #e0e0e0; }

View File

@@ -1,5 +1,5 @@
{
"name": "djarea-desktop-frontend",
"name": "mizan-desktop-frontend",
"private": true,
"type": "module",
"scripts": {
@@ -7,7 +7,7 @@
"build": "vite build"
},
"dependencies": {
"@rythazhur/djarea": "file:../../react",
"@rythazhur/mizan": "file:../../react",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},

View File

@@ -1,10 +1,10 @@
import { useState, useEffect, useCallback } from 'react'
import { DjareaProvider, useDjarea, useDjareaStatus } from '@rythazhur/djarea'
import { MizanProvider, useMizan, useMizanStatus } from '@rythazhur/mizan'
// ─── System Info ────────────────────────────────────────────────────────────
function SystemInfo() {
const { call } = useDjarea()
const { call } = useMizan()
const [info, setInfo] = useState<Record<string, unknown> | null>(null)
useEffect(() => {
@@ -33,7 +33,7 @@ function SystemInfo() {
// ─── Connection Status ──────────────────────────────────────────────────────
function StatusBar() {
const status = useDjareaStatus()
const status = useMizanStatus()
return (
<div style={{ ...styles.statusBar, color: status === 'connected' ? '#4ade80' : '#f87171' }}>
{status}
@@ -46,7 +46,7 @@ function StatusBar() {
type Note = { id: number; title: string; content: string; pinned: boolean; updated_at: string }
function Notes() {
const { call } = useDjarea()
const { call } = useMizan()
const [notes, setNotes] = useState<Note[]>([])
const [selected, setSelected] = useState<Note | null>(null)
const [title, setTitle] = useState('')
@@ -140,7 +140,7 @@ function Notes() {
type FileEntry = { name: string; path: string; is_dir: boolean; size: number }
function FileBrowser() {
const { call } = useDjarea()
const { call } = useMizan()
const [dir, setDir] = useState('~')
const [entries, setEntries] = useState<FileEntry[]>([])
const [parent, setParent] = useState<string | null>(null)
@@ -184,17 +184,17 @@ function FileBrowser() {
export function App() {
return (
<DjareaProvider baseUrl="/api/djarea" autoConnect={false}>
<MizanProvider baseUrl="/api/mizan" autoConnect={false}>
<div style={{ maxWidth: 960, margin: '0 auto', padding: 24 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
<h1 style={{ fontSize: 24, color: '#fff' }}>Djarea Desktop</h1>
<h1 style={{ fontSize: 24, color: '#fff' }}>mizan Desktop</h1>
<StatusBar />
</div>
<SystemInfo />
<Notes />
<FileBrowser />
</div>
</DjareaProvider>
</MizanProvider>
)
}

View File

@@ -1,16 +1,16 @@
[project]
name = "djarea-desktop"
name = "mizan-desktop"
version = "0.1.0"
description = "Desktop integration test app for Djarea"
description = "Desktop integration test app for mizan"
requires-python = ">=3.10"
dependencies = [
"djarea[channels]",
"mizan[channels]",
"uvicorn[standard]>=0.30",
"pywebview[qt]>=5.0",
]
[tool.uv.sources]
djarea = { path = "../django", editable = true }
mizan = { path = "../django", editable = true }
[project.optional-dependencies]
dev = [

View File

@@ -1,7 +1,8 @@
import django
from django.conf import settings
# Ensure migrations run before tests
def pytest_configure():
# Import djarea_clients to trigger function registration
import backend.djarea_clients # noqa: F401
# Import mizan_clients to trigger function registration
import backend.mizan_clients # noqa: F401

View File

@@ -1,5 +1,5 @@
"""
REAL integration tests for the Djarea RPC framework layer.
REAL integration tests for the mizan RPC framework layer.
Tests the actual HTTP stack: CSRF, middleware, error codes, validation.
Every test makes a real HTTP request — no mocks, no RequestFactory.
@@ -14,7 +14,7 @@ from django.test import LiveServerTestCase
class RealHTTPMixin:
def _session_init(self):
url = f"{self.live_server_url}/api/djarea/session/"
url = f"{self.live_server_url}/api/mizan/session/"
resp = urlopen(Request(url))
cookies = resp.headers.get_all("Set-Cookie") or []
for cookie in cookies:
@@ -26,7 +26,7 @@ class RealHTTPMixin:
self._cookies = ""
def _call(self, fn: str, args: dict | None = None):
url = f"{self.live_server_url}/api/djarea/call/"
url = f"{self.live_server_url}/api/mizan/call/"
body = json.dumps({"fn": fn, "args": args or {}}).encode()
req = Request(url, data=body, method="POST")
req.add_header("Content-Type", "application/json")
@@ -37,7 +37,13 @@ class RealHTTPMixin:
resp = urlopen(req)
return json.loads(resp.read())
def _raw_post(self, path: str, body: bytes | str, content_type: str = "application/json", include_csrf: bool = False):
def _raw_post(
self,
path: str,
body: bytes | str,
content_type: str = "application/json",
include_csrf: bool = False,
):
"""Raw POST without the call() envelope — for testing malformed requests."""
url = f"{self.live_server_url}{path}"
if isinstance(body, str):
@@ -55,7 +61,7 @@ class CSRFTests(RealHTTPMixin, LiveServerTestCase):
def test_session_endpoint_sets_csrf_cookie(self):
"""GET /session/ must return a Set-Cookie with csrftoken."""
url = f"{self.live_server_url}/api/djarea/session/"
url = f"{self.live_server_url}/api/mizan/session/"
resp = urlopen(Request(url))
cookies = resp.headers.get_all("Set-Cookie") or []
@@ -64,7 +70,7 @@ class CSRFTests(RealHTTPMixin, LiveServerTestCase):
def test_call_without_csrf_is_rejected(self):
"""POST /call/ without CSRF token must fail."""
url = f"{self.live_server_url}/api/djarea/call/"
url = f"{self.live_server_url}/api/mizan/call/"
body = json.dumps({"fn": "system_info", "args": {}}).encode()
req = Request(url, data=body, method="POST")
req.add_header("Content-Type", "application/json")
@@ -134,7 +140,7 @@ class ErrorCodeTests(RealHTTPMixin, LiveServerTestCase):
def test_get_method_rejected(self):
"""GET to /call/ should be rejected."""
url = f"{self.live_server_url}/api/djarea/call/"
url = f"{self.live_server_url}/api/mizan/call/"
try:
resp = urlopen(Request(url))
data = json.loads(resp.read())
@@ -147,7 +153,7 @@ class ErrorCodeTests(RealHTTPMixin, LiveServerTestCase):
self._session_init()
try:
resp = self._raw_post(
"/api/djarea/call/",
"/api/mizan/call/",
body="not valid json{{{",
include_csrf=True,
)
@@ -162,7 +168,7 @@ class ErrorCodeTests(RealHTTPMixin, LiveServerTestCase):
self._session_init()
try:
resp = self._raw_post(
"/api/djarea/call/",
"/api/mizan/call/",
body=json.dumps({"not_fn": "hello"}),
include_csrf=True,
)

View File

@@ -12,7 +12,7 @@ from urllib.request import urlopen, Request
class RealHTTPMixin:
def _session_init(self):
url = f"{self.live_server_url}/api/djarea/session/"
url = f"{self.live_server_url}/api/mizan/session/"
resp = urlopen(Request(url))
cookies = resp.headers.get_all("Set-Cookie") or []
for cookie in cookies:
@@ -24,7 +24,7 @@ class RealHTTPMixin:
self._cookies = ""
def _call(self, fn: str, args: dict | None = None):
url = f"{self.live_server_url}/api/djarea/call/"
url = f"{self.live_server_url}/api/mizan/call/"
body = json.dumps({"fn": fn, "args": args or {}}).encode()
req = Request(url, data=body, method="POST")
req.add_header("Content-Type", "application/json")
@@ -105,6 +105,7 @@ class NotesCRUDTests(RealHTTPMixin, LiveServerTestCase):
# Verify it's gone
from urllib.error import HTTPError
try:
get_data = self._call("get_note", {"id": note_id})
self.assertTrue(get_data["error"])

View File

@@ -18,8 +18,8 @@ class RealHTTPMixin:
"""Makes real HTTP requests to the live server."""
def _session_init(self):
"""Hit /session/ to get CSRF cookie, like DjareaProvider does."""
url = f"{self.live_server_url}/api/djarea/session/"
"""Hit /session/ to get CSRF cookie, like mizanProvider does."""
url = f"{self.live_server_url}/api/mizan/session/"
req = Request(url)
resp = urlopen(req)
# Extract csrftoken from Set-Cookie header
@@ -33,8 +33,8 @@ class RealHTTPMixin:
self._cookies = ""
def _call(self, fn: str, args: dict | None = None):
"""Make a real POST to /api/djarea/call/ with CSRF token."""
url = f"{self.live_server_url}/api/djarea/call/"
"""Make a real POST to /api/mizan/call/ with CSRF token."""
url = f"{self.live_server_url}/api/mizan/call/"
body = json.dumps({"fn": fn, "args": args or {}}).encode()
req = Request(url, data=body, method="POST")
req.add_header("Content-Type", "application/json")
@@ -80,7 +80,7 @@ class SystemInfoTests(RealHTTPMixin, LiveServerTestCase):
data = self._call("app_info")
self.assertFalse(data["error"])
self.assertEqual(data["data"]["app_name"], "Djarea Desktop")
self.assertEqual(data["data"]["app_name"], "mizan Desktop")
self.assertGreater(data["data"]["uptime_seconds"], 0)
@@ -89,11 +89,12 @@ class FileSystemTests(RealHTTPMixin, LiveServerTestCase):
def setUp(self):
self._session_init()
self.test_dir = Path.home() / ".djarea-test"
self.test_dir = Path.home() / ".mizan-test"
self.test_dir.mkdir(exist_ok=True)
def tearDown(self):
import shutil
if self.test_dir.exists():
shutil.rmtree(self.test_dir)
@@ -116,7 +117,9 @@ class FileSystemTests(RealHTTPMixin, LiveServerTestCase):
test_content = "Hello from a REAL HTTP integration test!"
# Write
write_data = self._call("write_file", {"path": test_path, "content": test_content})
write_data = self._call(
"write_file", {"path": test_path, "content": test_content}
)
self.assertFalse(write_data["error"])
self.assertEqual(write_data["data"]["path"], test_path)
@@ -130,7 +133,9 @@ class FileSystemTests(RealHTTPMixin, LiveServerTestCase):
from urllib.error import HTTPError
try:
data = self._call("write_file", {"path": "/tmp/escape.txt", "content": "nope"})
data = self._call(
"write_file", {"path": "/tmp/escape.txt", "content": "nope"}
)
# If we get here, check the response has an error
self.assertTrue(data["error"])
self.assertEqual(data["code"], "FORBIDDEN")