Phase 07: Studio (Code Generation UI)
Objective: Build a visual DocType builder (framework-m-studio) that generates Python code, running as a Litestar app.
1. Packaging Strategy (Runtime vs DevTools)
1.1 framework-m (Runtime)
- Goal: Production-grade kernel.
- Content: API Server, ORM, DocType Engine, Basic CLI (
start,migrate,worker). - Dependencies:
litestar,sqlalchemy,pydantic,cyclopts. - Excluded: Code generators, Studio UI, Heavy formatting/linting libs.
1.2 framework-m-studio (DevTools)
- Goal: Developer Productivity & SaaS Component.
- Content: Studio UI, Code Generators, Visual Editors.
- Dependencies:
framework-m,libcst,jinja2. - Integration: Plugs into
mCLI via entry points (m codegen).
2. Studio Backend (Litestar App)
1. Project Structure (Monorepo)
- Create
apps/studio/in the workspace - Initialize
apps/studio/pyproject.toml:- Add dependency:
framework-m = { workspace = true } - Add dependency:
libcst(for code modification) - Add dependency:
jinja2(for code generation) - Add code generation endpoints
- Add dependency:
1.2 File System API
-
Create
GET /studio/api/doctypes:- Scan project for
*.pyfiles containing DocType classes - Return list of DocTypes with metadata
- Scan project for
-
Create
GET /studio/api/doctype/{name}:- Read DocType file
- Parse with LibCST
- Return structured JSON
-
Create
POST /studio/api/doctype/{name}:- Accept DocType schema JSON
- Generate Python code
- Write to file system
-
Create
DELETE /studio/api/doctype/{name}:- Delete DocType file
- Delete Controller file (if exists)
2. LibCST Code Transformer
2.1 Parser
- Create
src/framework_m_studio/codegen/parser.py - Implement
parse_doctype(file_path: str) -> dict:- Use LibCST to parse Python file
- Extract class definition
- Extract fields with types and defaults
- Extract Config metadata
- Return structured dict
2.2 Generators (The Strategy)
"Jinja for Creation, LibCST for Mutation"
-
Creation (Scaffolding):
- Use
jinja2templates (shared with CLIm new:doctype). - Goal: Generate clean, standard Python code from scratch.
- Implement
generate_doctype_source(schema: dict) -> str.
- Use
-
Mutation (Transformer):
- Use
LibCSTto parse and modify existing files. - Goal: Add/Edit fields while preserving comments and custom methods.
- Implement
update_doctype_source(source: str, schema: dict) -> str.
- Use
2.3 Test Generator
- Create
src/framework_m_studio/codegen/test_generator.py - Implement
generate_test(schema: dict) -> str:- Generate
test_{doctype}.py - Import
pytestand DocType - Generate basic CRUD test (Create, Read, Update, Delete)
- Generate validation failure test (if required fields exist)
- Generate
2.4 Transformer
-
Create
src/framework_m_studio/codegen/transformer.py -
Implement
update_doctype(file_path: str, schema: dict):- Parse existing file with LibCST
- Locate DocType class
- Update/add/remove fields
- Preserve comments and custom methods
- Write back to file
-
Handle edge cases:
- Field renamed (detect and update)
- Field type changed
- Field deleted (remove from class)
- Custom methods preserved
3. Studio Frontend
Stack: Per ADR-0005 (Use Refine for Frontend) — Refine + shadcn/ui + RJSF + Tailwind
3.1 React App Setup (Refine Stack)
-
Create
apps/studio/studio_ui/directory -
Initialize Refine app:
pnpm create refine-app@latest studio_ui
# Selected: refine-vite, custom-json-rest, no UI, no examples, pnpm -
Install dependencies per ADR-0005:
Layer Package Purpose Data/State @refinedev/coreCRUD, caching, auth Forms @rjsf/core+@rjsf/validator-ajv8Schema-driven forms Tables @tanstack/react-tableData tables UI Components shadcn/uiAccessible components Styling tailwindcssUtility-first CSS Code Editor @monaco-editor/reactPython code preview Drag & Drop @dnd-kit/core+@dnd-kit/sortableField reordering -
Create Framework M Data Provider (
src/providers/dataProvider.ts):// Maps Refine's DataProvider to Framework M API
const dataProvider = frameworkMDataProvider(config.apiBaseUrl); -
Configure API base URLs (per ADR-0005):
- Read from
window.__FRAMEWORK_CONFIG__or env vars - Support same-origin, subdomain, and CDN scenarios
- Default to
/studio/apifor Studio endpoints
- Read from
3.2 DocType List View (Refine Resource)
- Create
pages/doctypes/list.tsx:- Use
useListhook from@refinedev/coreto fetch DocTypes - Display with
@tanstack/react-table+ Tailwind styling - Add search/filter with
useTableglobalFilter - Add "New DocType" button → navigates to
/doctypes/create
- Use
3.3 DocType Editor (Refine Form)
- Create
pages/doctypes/edit.tsx:- Use
useOne/useUpdate/useCreatehooks for data - Left panel: Field list with
@dnd-kit/sortable - Right panel: Field properties form (name, type, required, default, description)
- Top bar: DocType name input + Save button
- Use
3.4 Field Editor (RJSF Schema-Driven)
- Create
components/FieldEditor.tsx:- Render with RJSF using JSON Schema for field properties
- Custom Tailwind widgets:
TextWidget,SelectWidget,CheckboxWidget,TextareaWidget - Dynamic "Validators" collapsible for min/max length, pattern, etc.
- Live preview of generated Python type annotation
3.5 Drag & Drop
- Implement field reordering:
- Use
@dnd-kit/core+@dnd-kit/sortable - Allow dragging fields to reorder
- Update schema on drop (via
arrayMove)
- Use
3.6 Code Preview (Monaco Editor)
- Create
components/CodePreview.tsx:- Use
@monaco-editor/reactwithlanguage="python" - Read-only mode (
readOnly: true) - Theme:
vs-dark - Auto-update on schema change (memoized generation)
- Copy button for generated code
- Use
3.7 Save Flow
- Implement save functionality:
- Validate schema (name required)
- Call backend API via
useCreate/useUpdate - Navigate back to list on success
- Loading state on save button
3.8 Visual Data Modeling (Mind Map)
- Implement ERD/Mind Map View:
- Central Node: Current DocType (highlighted)
- Connected Nodes: Related DocTypes via Link fields
- Visual Action: Drag to connect nodes → Creates Link Field
- Using
@xyflow/react(React Flow v12)
3.9 Layout Designer (Grid System)
- Implement Visual Layout Drag & Drop:
- Define Sections with title and Columns (1/2/3 cols selector)
- Drag fields from palette into grid cells
- Real-time form preview toggle
- Add/delete sections
3.10 Module Explorer
- Implement Module Scanner:
- Group DocTypes by directory/app module (tree structure)
- Tree view navigation in Sidebar
- Expand/collapse all, search filter
- Click to navigate to DocType editor
4. Studio Cloud Mode (Ephemeral & Git-Backed)
"Stateless Studio": No RWX storage required. Repo is cloned to ephemeral path.
[!NOTE] Workspace Terminology
- Studio Workspace = The Git monorepo where code lives (this section)
- Desk Workspace = End-user navigation groupings in sidebar (Phase 09, Section 6.2)
A Studio Workspace (monorepo) can contain multiple Desk Workspace definitions in code.
4.1 Architecture
- Storage:
/tmp/workspace_{session_id}(Ephemeral/Pod-local). ✅ - Persistence: Git Provider (GitHub/GitLab). ✅
- Concurrency: Optimistic Locking (Last push wins, or Rebase flow). ✅
- Deployment: StatefulSet or Deployment (Replicas=1).
- Reasoning: Workspaces are local to the Pod. Scaling requires Sticky Sessions or RWX.
- Recommendation: Keep Studio as a Singleton. Scale the Application, not the Editor.
4.2 GitOps Integration (FluxCD/ArgoCD)
- Configuration: Inject
GIT_REPO,GIT_TOKENvia Environment Variables. ✅ - Read State: Clone target repo on startup (or user connect). ✅
- Write State:
- Create Branch + Open PR (Review Process). ✅
- Update State:
- Implement "Pull Latest" Button (
POST /workspace/{id}/pull). - Show "Updates Available" indicator (
POST /workspace/{id}/check-updates).
- Implement "Pull Latest" Button (
4.3 Git Adapter (Data Agnostic)
- Create
src/framework_m_studio/git/protocol.py- GitAdapterProtocol (Port) - Create
src/framework_m_studio/git/adapter.py- GitAdapter (Adapter) - Implement
GitAdapter:- Wraps standard
gitbinary via asyncio subprocess. -
clone(repo_url, auth): Clone to temp dir. -
commit(message): Stage and commit changes. -
push(branch): Push to remote. -
pull(): Pull latest changes. -
create_branch(name): Create new branch. -
get_status(): Get workspace status.
- Wraps standard
- Unit tests in
tests/test_git_adapter.py(12 tests)
4.4 GitHub Provider
- Create
src/framework_m_studio/git/github_provider.py - Implement
GitHubProvider:- Use GitHub API for operations (REST API with urllib)
- Support personal access tokens (Bearer auth)
- Handle authentication (GitHubAuthError)
- Create pull requests
- List pull requests
- Validate tokens
- Unit tests in
tests/test_github_provider.py(9 tests)
4.5 Generic Git Provider
Note: Implemented as GitAdapter using git CLI - no need for separate provider.
- Support HTTPS and SSH (via git CLI)
- Work with any Git server (generic implementation)
4.6 Workspace Management
- Create
src/framework_m_studio/workspace.py - Implement workspace lifecycle:
- Clone repo to temp directory
- Track workspace sessions
- Clean up old workspaces (TTL-based)
- Handle concurrent edits (asyncio lock)
- Unit tests in
tests/test_workspace.py(12 tests)
4.7 Cloud Studio API
-
Create
POST /studio/api/workspace/connect:- Accept repo URL and token
- Clone repository
- Return workspace ID
-
Create
POST /studio/api/workspace/{id}/commit:- Commit changes
- Push to branch
- Return commit SHA
-
Create
GET /studio/api/workspace/{id}/status: Git status -
Create
POST /studio/api/workspace/{id}/pull: Pull latest -
Create
DELETE /studio/api/workspace/{id}: Cleanup workspace -
Create
GET /studio/api/workspace/sessions: List all sessions
5. Studio CLI Command
5.1 SPA Packaging & Serving (Path Prefix Architecture)
-
Build Process:
-
pnpm run buildinstudio_uioutputs tosrc/framework_m_studio/static/(vite.config.ts). -
pyproject.tomlincludesframework_m_studio/static/**/*.
-
-
Serving Strategy (Litestar) (in
app.py):- UI Routes: Serve
/studio/ui/*→ React SPA (HTML pages) - API Routes: Serve
/studio/api/*→ JSON endpoints - Assets: Serve
/studio/ui/assets/*as static files (Cached) - SPA Catch-all:
/studio/ui/{path:path}→ returnsindex.htmlfor client-side routing - Redirect:
/studio→/studio/ui/for convenience - Content-Type Fix: All responses use explicit
media_typeto prevent download issues - Implementation:
serve_spa()and_get_spa_response()functions inapp.py
- UI Routes: Serve
-
Implement
m studio(inframework-m-studiopackage):- Register CLI command via entry point:
[project.entry-points."framework_m.cli_commands"]
studio = "framework_m_studio.cli:studio_app" - Start uvicorn server on port 9000 (default)
- Serve Studio UI
- Open browser automatically (TODO)
- Options:
-
--port- Custom port -
--host- Custom host -
--reload- Development mode -
--cloud- Enable cloud mode
-
- Register CLI command via entry point:
6. DevTools CLI (Extensions)
"cli needs clear separation... m docs|codegen needs to be part of studio"
These commands are registered via entry_points in framework-m-studio. They are NOT available in the base framework-m package.
6.1 Documentation Generator (m docs:generate)
- Dependencies: Uses stdlib
urllib,json,subprocess(no external deps required). - Command:
m docs:generate:- API Reference: Generates markdown from DocTypes in
docs_generator.py. - OpenAPI Export: Exports
openapi.jsonfrom URL (--openapi-url). - Site Build: Runs
mkdocs buildif available (--build-site).
- API Reference: Generates markdown from DocTypes in
- Unit tests in
tests/test_docs_generator.py(10 tests)
6.2 Client SDK Generator (m codegen client)
- Dependencies: Uses stdlib only (no external deps required).
- Command:
m codegen client:- Flow:
- Fetches OpenAPI JSON from URL.
- Generates code using stdlib.
- Arguments:
--lang [ts|py]: Target language.--out [dir]: Output directory.--openapi-url: URL to fetch schema from.
- TypeScript: Generates interfaces + fetch client in
sdk_generator.py. - Python: Generates Pydantic models in
sdk_generator.py.
- Flow:
- Unit tests in
tests/test_codegen.py(6 tests) - CLI tests updated in
tests/test_cli.py
7. Field Type Library
7.1 Supported Field Types
- Implement field type definitions:
- Text (str)
- Number (int, float)
- Checkbox (bool)
- Date (date)
- DateTime (datetime)
- Select (enum)
- Link (foreign key)
- Table (child table)
- Level 1: Grid/Table view
- Level 2+: Drill-down pattern (Button -> Drawer/Dialog) for infinite nesting
- JSON (dict)
- File (file upload)
7.2 Custom Field Type Discovery
- Dynamic Loading: Studio reads available types from
FieldRegistryat runtime.- API Endpoint:
GET /studio/api/field-types - Returns: Built-in types + Types registered by installed apps.
- Each type includes:
name,pydantic_type,sqlalchemy_type,ui_component(optional).
- API Endpoint:
- UI Integration: Field Type Selector in DocType Editor dynamically populates from this API.
7.3 Custom UI Components (Client-Side)
- Goal: Allow apps to register custom React components for their field types.
- Registration (App-Side):
// my_app/frontend/index.ts
import { registerFieldComponent } from '@framework-m/studio';
import StarRating from './components/StarRating';
registerFieldComponent('Rating', StarRating); - Discovery (Studio-Side):
- Shadow Build (Phase 09) loads all app
frontend/plugins. - Studio UI checks
window.__STUDIO_FIELD_COMPONENTS__map. - Falls back to default
<input>if no custom component.
- Shadow Build (Phase 09) loads all app
7.5 Live Preview & Sandbox Mode
[!IMPORTANT] Sandbox = Local Development Environment The Sandbox runs on the same machine where Studio is running. It is NOT a separate cloud environment. This enables citizen developers to design, test, and iterate locally before committing to Git.
Citizen Developer Workflow:
┌─────────────────────────────────────────────────────────────────────┐
│ LOCAL MACHINE (Developer or Citizen) │
│ │
│ 1. Studio UI 2. Sandbox 3. Git │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Design │ ──► │ Test with │ ──► │ Commit & │ │
│ │ DocType │ │ mock data │ │ Push │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │
└──────────────────────────────────────────────────────│──────────────┘
▼
CI/CD → Production
[!WARNING] Collaborative Environments: Conflict Risk When multiple citizens work on the same repo/branch, local sandboxes can cause Git conflicts. The sandbox is best suited for solo work or feature branches.
Alternatives for Collaborative Teams:
| Approach | How It Works | Best For |
|---|---|---|
| Feature Branches | Each citizen works on their own branch → PR to merge | Small teams (2-5) |
| Gitpod/Codespaces | Each citizen gets isolated cloud dev environment | Larger teams |
| Assigned Modules | Citizens own specific modules/apps, no overlap | Enterprise |
| Trunk-Based + Fast CI | All push to main, CI deploys in <2 min | Mature teams |
| Studio Cloud Mode | Hosted Studio with workspace isolation (Section 4) | SaaS offering |
-
Recommendation: Default to feature branches for teams. Add branch selector to Studio UI.
-
Goal: Preview the DocType being edited with mock data AND simulate CRUD.
-
Implementation:
- Preview Tab: Toggle between "Schema Editor" and "Sandbox".
- Mock Data Generation:
- Use
@faker-js/fakeror simple generators. - Generate sample rows based on field types.
- Use
- Form Preview: Render
AutoFormwith generated mock doc. - List Preview: Render
AutoTablewith 5-10 mock rows.
-
Schema Source: Uses the in-memory schema being edited, NOT the saved file.
- Allows instant feedback without saving.
7.6 Sandbox: Interactive CRUD Simulation
[!NOTE] Why Mock Data? Sandbox uses mock data so citizens can test the full UX (forms, lists, validation) without needing a database setup. Once satisfied, they commit the code and deploy to an environment with real data.
- Goal: Let users test the full UX before committing the DocType.
- Features:
- New: Create a new mock document. Test mandatory field validation.
- Update: Edit an existing mock document. See form behavior.
- Delete: Delete mock document. See confirmation dialogs.
- List (Few): Show 5 rows. Test basic list rendering.
- List (Many): Paginate 100+ mock rows. Test performance.
- Filter: Apply filters and see results change.
- Validation: Submit form with missing required fields. See error messages.
- Data Store: In-memory array (resets on page reload).
- Benefit: No database required. Test UX before writing to disk.
7.7 Local Hot Reload (Optional Enhancement)
- Goal: For users who want to test with a real local database instead of mock data.
- Prerequisites: Local Postgres/SQLite +
m startrunning. - Flow:
- Studio saves DocType to file.
- File watcher detects change → triggers schema sync.
- Table created/updated in local DB.
- User can now test with real CRUD operations.
- Benefit: Bridge between mock sandbox and production—test with actual data locally.
7.2 Field Metadata
- Define field options:
- Label
- Description
- Required
- Default value
- Validation rules
- Display options (hidden, read-only)
8. Controller Scaffolding
-
Add controller generation:
- Generate controller file template
- Add common hook methods
- Add validation examples
-
Controller editor in UI:
- List hook methods
- Add custom methods
- Code editor for method bodies
9. Testing
9.1 Unit Tests
-
Test LibCST parser:
- Parse valid DocType
- Extract fields correctly
- Handle edge cases
-
Test code generator:
- Generate valid Python code
- Format correctly
- Handle all field types
9.2 Integration Tests
-
Test full flow:
- Create DocType via UI
- Save to file system
- Reload and verify
- Update DocType
- Verify changes preserved
-
Test Git integration:
- Clone repository
- Make changes
- Commit and push
- Verify on GitHub
10. Documentation
- Create Studio user guide:
- How to start Studio
- How to create DocType
- How to add fields
- How to configure permissions
- How to use Git mode
Validation Checklist
Before moving to Phase 08, verify:
- Studio UI can create and edit DocTypes
- Generated code is valid Python
- Changes are saved to file system
- LibCST preserves custom code
- Git mode can commit and push
- Studio works in both local and cloud modes
Anti-Patterns to Avoid
❌ Don't: Store DocType metadata in database like Frappe ✅ Do: Generate Python source code
❌ Don't: Use AST (destroys formatting) ✅ Do: Use LibCST (preserves formatting)
❌ Don't: Overwrite custom code ✅ Do: Preserve comments and custom methods
❌ Don't: Require complex setup for Studio ✅ Do: Make it work in browser with Git