diff --git a/AGENTS.md b/AGENTS.md
index 31f4bc302..9f7a8cb1c 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -329,6 +329,50 @@ npm run test:e2e
See `TESTING.md` for comprehensive E2E testing guide.
+#### Component Documentation (Storybook)
+
+**🚨 CRITICAL GUARDRAIL: Stories Are Data, Not Code**
+
+Like tests, Storybook stories must be JSON, not TypeScript!
+
+**❌ WRONG: Writing new .stories.tsx files**
+```typescript
+// DON'T DO THIS - No new .stories.tsx files!
+export const HomePage: Story = {
+ render: () =>
+}
+```
+
+**✅ CORRECT: JSON story definitions in packages**
+```json
+// packages/ui_home/storybook/stories.json
+{
+ "$schema": "https://metabuilder.dev/schemas/package-storybook.schema.json",
+ "title": "Home Page Components",
+ "stories": [{
+ "name": "HomePage",
+ "render": "home_page",
+ "description": "Complete home page",
+ "args": {
+ "title": "Welcome to MetaBuilder"
+ }
+ }]
+}
+```
+
+**Story Location Rules:**
+- ✅ `packages/{package_name}/storybook/stories.json` - Package stories (PREFERRED)
+- ✅ `storybook/src/stories/*.stories.tsx` - Existing stories (legacy, don't add more)
+- ❌ NEVER create new `.stories.tsx` files - use JSON instead!
+
+**JSON Story Loader:**
+- Stories defined in `packages/*/storybook/stories.json` are auto-discovered
+- `storybook/json-loader/` loads and renders JSON stories directly
+- No code generation - JSON is rendered at runtime
+- Uses `DynamicStory` component to render from JSON definitions
+
+See `storybook/json-loader/README.md` for complete guide.
+
#### Project Structure
```
@@ -477,7 +521,56 @@ npm run test:e2e
### Writing Tests
-**Unit test example:**
+**🚨 CRITICAL GUARDRAIL: Tests Are Data, Not Code**
+
+MetaBuilder follows the **95% JSON rule** for tests too. Tests must be defined as JSON, not TypeScript!
+
+**❌ WRONG: Writing new .spec.ts files**
+```typescript
+// DON'T DO THIS - No new .spec.ts files!
+test('should login', async ({ page }) => {
+ await page.goto('/login')
+ await page.fill('[name="username"]', 'user')
+})
+```
+
+**✅ CORRECT: JSON test definitions in packages**
+```json
+// packages/auth/playwright/tests.json
+{
+ "$schema": "https://metabuilder.dev/schemas/package-playwright.schema.json",
+ "package": "auth",
+ "tests": [{
+ "name": "should login",
+ "tags": ["@auth", "@smoke"],
+ "steps": [
+ {"action": "navigate", "url": "/login"},
+ {"action": "fill", "label": "Username", "value": "user"},
+ {"action": "click", "role": "button", "text": "Login"}
+ ]
+ }]
+}
+```
+
+**Test Location Rules:**
+- ✅ `packages/{package_name}/playwright/tests.json` - Package-scoped tests (NEW)
+- ✅ `e2e/*.spec.ts` - Existing manual tests (legacy, don't add more)
+- ✅ `e2e/json-packages.spec.ts` - Auto-loads all package JSON tests
+- ❌ NEVER create new `.spec.ts` files - use JSON instead!
+
+**JSON Test Runner:**
+- Tests defined in `packages/*/playwright/tests.json` are auto-discovered
+- `e2e/json-runner/playwright-json-runner.ts` interprets and executes JSON directly
+- No code generation - JSON is executed at runtime
+- Changes to JSON tests take effect immediately
+
+**Running JSON Tests:**
+```bash
+npm run test:e2e:json # All package JSON tests
+npm run test:e2e -- e2e/json-packages.spec.ts # Same thing, explicit
+```
+
+**Unit test example (still TypeScript):**
```typescript
// tests/lib/users/createUser.test.ts
import { createUser } from '@/lib/users/createUser'
@@ -490,20 +583,7 @@ describe('createUser', () => {
})
```
-**E2E test example:**
-```typescript
-// e2e/users.spec.ts
-import { test, expect } from '@playwright/test'
-
-test('create user flow', async ({ page }) => {
- await page.goto('http://localhost:3000/users')
- await page.fill('input[name="username"]', 'john')
- await page.click('button:has-text("Create")')
- await expect(page).toHaveURL(/.*\/users\/.*/)
-})
-```
-
-See `TESTING.md` for comprehensive testing guide.
+See `TESTING.md` and `e2e/json-runner/README.md` for comprehensive testing guide.
---
diff --git a/CLAUDE.md b/CLAUDE.md
index 8a70e805a..b4489ca25 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -78,6 +78,87 @@ import HomePage from './HomePage'
// ✅ MetaBuilder (data-driven)
const route = await db.query('PageConfig', { path: '/' })
+const component = await loadComponent(route.componentId)
+return renderComponent(component)
+```
+
+### 🚨 CRITICAL GUARDRAILS
+
+#### 1. Tests Are Data, Not Code
+
+**❌ NEVER write new .spec.ts files** - Tests must be JSON!
+
+```json
+// ✅ CORRECT: packages/auth/playwright/tests.json
+{
+ "$schema": "https://metabuilder.dev/schemas/package-playwright.schema.json",
+ "package": "auth",
+ "tests": [{
+ "name": "should login successfully",
+ "tags": ["@auth", "@smoke"],
+ "steps": [
+ {"action": "navigate", "url": "/login"},
+ {"action": "fill", "label": "Username", "value": "testuser"},
+ {"action": "click", "role": "button", "text": "Login"},
+ {"action": "expect", "selector": ".dashboard", "assertion": {"matcher": "toBeVisible"}}
+ ]
+ }]
+}
+```
+
+- ✅ `packages/{name}/playwright/tests.json` - JSON test definitions (PREFERRED)
+- ✅ `e2e/*.spec.ts` - Existing manual tests (legacy)
+- ❌ NEVER create new .spec.ts files!
+
+Tests are auto-discovered and executed directly from JSON by `e2e/json-runner/`.
+
+#### 2. Stories Are Data, Not Code
+
+**❌ NEVER write new .stories.tsx files** - Stories must be JSON!
+
+```json
+// ✅ CORRECT: packages/ui_home/storybook/stories.json
+{
+ "$schema": "https://metabuilder.dev/schemas/package-storybook.schema.json",
+ "title": "Home Page Components",
+ "stories": [{
+ "name": "HomePage",
+ "render": "home_page",
+ "args": {"title": "Welcome"}
+ }]
+}
+```
+
+- ✅ `packages/{name}/storybook/stories.json` - JSON story definitions (PREFERRED)
+- ✅ `storybook/src/stories/*.stories.tsx` - Existing stories (legacy)
+- ❌ NEVER create new .stories.tsx files!
+
+Stories are auto-discovered and rendered directly from JSON by `storybook/json-loader/`.
+
+#### 3. 95% Configuration Rule
+
+Everything should be data/configuration:
+- UI → JSON component definitions
+- Routes → Database PageConfig entries
+- Tests → JSON test definitions
+- Stories → JSON story definitions
+- Business logic → JSON workflows/scripts
+
+Only infrastructure code should be TypeScript (5%).
+
+---
+
+## 📚 Documentation Priority
+
+**READ THESE IN ORDER:**
+
+1. **.github/prompts/workflow/0-kickstart.md** - Your complete development workflow
+2. **AGENTS.md** - Core principles, testing (JSON!), DBAL, packages
+3. **.github/copilot-instructions.md** - AI development patterns
+4. **README.md** - System overview
+5. **ARCHITECTURE.md** - MetaBuilder foundation
+
+---
const component = await loadPackage(route.packageId)
return renderJSONComponent(component)
```
diff --git a/TEST_STORY_CONVERSION.md b/TEST_STORY_CONVERSION.md
new file mode 100644
index 000000000..a263bef83
--- /dev/null
+++ b/TEST_STORY_CONVERSION.md
@@ -0,0 +1,213 @@
+# Test & Story Conversion Guide
+
+## Overview
+
+Old Playwright tests (`.spec.ts`) and Storybook stories (`.stories.tsx`) can be converted to JSON format for direct interpretation at runtime.
+
+## ✅ Benefits of JSON Format
+
+1. **True Meta Architecture**: Tests/stories are data, not code
+2. **No Code Generation**: JSON is directly executed/rendered
+3. **Immediate Changes**: Edit JSON → Effect is immediate
+4. **Single Source of Truth**: JSON only (no generated code)
+5. **Package Ownership**: Each package owns its test/story data
+6. **Schema Validated**: Definitions conform to JSON schemas
+
+## 🚨 Critical Rules
+
+### Tests
+- ✅ NEW TESTS: Write in `packages/{name}/playwright/tests.json`
+- ✅ EXISTING: Keep old `.spec.ts` files (don't break existing tests)
+- ❌ NEVER: Create new `.spec.ts` files
+
+### Stories
+- ✅ NEW STORIES: Write in `packages/{name}/storybook/stories.json`
+- ✅ EXISTING: Keep old `.stories.tsx` files (don't break existing stories)
+- ❌ NEVER: Create new `.stories.tsx` files
+
+## Conversion Examples
+
+### Playwright Test Conversion
+
+**Before (TypeScript):**
+```typescript
+// e2e/login.spec.ts
+import { test, expect } from '@playwright/test'
+
+test('should login successfully', async ({ page }) => {
+ await page.goto('/login')
+ await page.fill('[name="username"]', 'testuser')
+ await page.fill('[name="password"]', 'testpass')
+ await page.click('button:has-text("Login")')
+ await expect(page).toHaveURL('/dashboard')
+})
+```
+
+**After (JSON):**
+```json
+// packages/auth/playwright/tests.json
+{
+ "$schema": "https://metabuilder.dev/schemas/package-playwright.schema.json",
+ "package": "auth",
+ "version": "1.0.0",
+ "tests": [{
+ "name": "should login successfully",
+ "tags": ["@auth", "@smoke"],
+ "steps": [
+ {
+ "description": "Navigate to login page",
+ "action": "navigate",
+ "url": "/login"
+ },
+ {
+ "description": "Fill username",
+ "action": "fill",
+ "selector": "[name='username']",
+ "value": "testuser"
+ },
+ {
+ "description": "Fill password",
+ "action": "fill",
+ "selector": "[name='password']",
+ "value": "testpass"
+ },
+ {
+ "description": "Click login button",
+ "action": "click",
+ "role": "button",
+ "text": "Login"
+ },
+ {
+ "description": "Verify redirected to dashboard",
+ "action": "expect",
+ "selector": "body",
+ "assertion": {
+ "matcher": "toHaveURL",
+ "expected": "/dashboard"
+ }
+ }
+ ]
+ }]
+}
+```
+
+### Storybook Story Conversion
+
+**Before (TypeScript):**
+```typescript
+// storybook/src/stories/HomePage.stories.tsx
+import type { Meta, StoryObj } from '@storybook/react'
+import { HomePage } from '@/components/HomePage'
+
+const meta: Meta = {
+ title: 'Pages/HomePage',
+ component: HomePage,
+}
+
+export default meta
+type Story = StoryObj
+
+export const Default: Story = {
+ args: {
+ title: 'Welcome to MetaBuilder',
+ subtitle: 'Build apps visually'
+ }
+}
+```
+
+**After (JSON):**
+```json
+// packages/ui_home/storybook/stories.json
+{
+ "$schema": "https://metabuilder.dev/schemas/package-storybook.schema.json",
+ "title": "Pages/HomePage",
+ "description": "Home page components",
+ "stories": [{
+ "name": "Default",
+ "render": "home_page",
+ "description": "Default home page view",
+ "args": {
+ "title": "Welcome to MetaBuilder",
+ "subtitle": "Build apps visually"
+ }
+ }]
+}
+```
+
+## Existing Test Files Status
+
+### Can Be Converted (2,500+ lines)
+- `e2e/smoke.spec.ts` → `packages/smoke_tests/playwright/tests.json` ✅ DONE
+- `e2e/login.spec.ts` → `packages/auth/playwright/tests.json` ✅ DONE
+- `e2e/crud.spec.ts` → Can be converted
+- `e2e/navigation.spec.ts` → Can be converted
+- `e2e/pagination.spec.ts` → Can be converted
+- `e2e/package-loading.spec.ts` → Can be converted
+- `e2e/package-rendering.spec.ts` → Can be converted
+- `e2e/auth/*.spec.ts` → Can be converted
+- `e2e/crud/*.spec.ts` → Can be converted
+- `e2e/api/*.spec.ts` → Can be converted
+
+### Should Keep As-Is
+- `e2e/dbal-daemon/*.spec.ts` - Complex daemon testing
+- Tests with custom TypeScript logic that can't be represented in JSON
+
+### Storybook Files
+- `storybook/src/stories/*.stories.tsx` (8 files) - Can be converted
+
+## Conversion Priority
+
+1. **High Priority** - Smoke/critical tests
+ - ✅ `smoke.spec.ts` (converted)
+ - ✅ `login.spec.ts` (converted)
+
+2. **Medium Priority** - Common workflows
+ - `crud.spec.ts`
+ - `navigation.spec.ts`
+ - `auth/*.spec.ts`
+
+3. **Low Priority** - Complex/specialized tests
+ - `dbal-daemon/*.spec.ts` (keep as TypeScript)
+ - Tests with complex assertions
+
+## Running JSON Tests
+
+```bash
+# Run all JSON-defined package tests
+npm run test:e2e:json
+
+# Or explicitly
+npm run test:e2e -- e2e/json-packages.spec.ts
+
+# Run with UI mode
+npm run test:e2e:ui -- e2e/json-packages.spec.ts
+
+# Run legacy tests (still works)
+npm run test:e2e
+```
+
+## No Leftover Junk
+
+✅ **Clean Status:**
+- No generated files or directories
+- No `.bak`, `.old`, `.tmp` files
+- No leftover code generators (removed in bccc336)
+- JSON runners/loaders are clean, documented code
+
+## Documentation
+
+- `e2e/json-runner/README.md` - JSON test runner documentation
+- `storybook/json-loader/README.md` - JSON story loader documentation
+- `schemas/package-schemas/playwright.schema.json` - Test schema
+- `schemas/package-schemas/storybook_schema.json` - Story schema
+- `schemas/package-schemas/PLAYWRIGHT_SCHEMA_README.md` - Schema guide
+
+## Summary
+
+- ✅ 2 test suites converted (smoke, auth)
+- ✅ 12+ tests now in JSON format
+- ✅ JSON runner infrastructure complete
+- ✅ No code generation - direct interpretation
+- ✅ Guardrails added to AGENTS.md and CLAUDE.md
+- ✅ Clean codebase - no junk files
+- 🎯 Ready for more conversions as needed
diff --git a/packages/auth/package.json b/packages/auth/package.json
new file mode 100644
index 000000000..ca6a2cd90
--- /dev/null
+++ b/packages/auth/package.json
@@ -0,0 +1,10 @@
+{
+ "$schema": "https://metabuilder.dev/schemas/package-metadata.schema.json",
+ "packageId": "auth",
+ "name": "Authentication",
+ "version": "1.0.0",
+ "description": "Authentication and login functionality for MetaBuilder",
+ "category": "authentication",
+ "minLevel": 0,
+ "keywords": ["auth", "login", "authentication", "security"]
+}
diff --git a/packages/auth/playwright/tests.json b/packages/auth/playwright/tests.json
new file mode 100644
index 000000000..3c8ffd661
--- /dev/null
+++ b/packages/auth/playwright/tests.json
@@ -0,0 +1,160 @@
+{
+ "$schema": "https://metabuilder.dev/schemas/package-playwright.schema.json",
+ "package": "auth",
+ "version": "1.0.0",
+ "description": "Authentication and login tests converted from e2e/login.spec.ts",
+ "tests": [
+ {
+ "name": "should display login form after navigating from landing page",
+ "tags": ["@auth", "@login"],
+ "timeout": 10000,
+ "steps": [
+ {
+ "description": "Navigate to home page",
+ "action": "navigate",
+ "url": "/"
+ },
+ {
+ "description": "Click Sign In button",
+ "action": "click",
+ "role": "button",
+ "text": "Sign In"
+ },
+ {
+ "description": "Wait for navigation",
+ "action": "waitForLoadState",
+ "state": "networkidle"
+ },
+ {
+ "description": "Verify username field is visible",
+ "action": "expect",
+ "label": "Username",
+ "assertion": {
+ "matcher": "toBeVisible",
+ "timeout": 5000
+ }
+ },
+ {
+ "description": "Verify password field is visible",
+ "action": "expect",
+ "label": "Password",
+ "assertion": {
+ "matcher": "toBeVisible"
+ }
+ },
+ {
+ "description": "Verify login button is visible",
+ "action": "expect",
+ "role": "button",
+ "text": "Login",
+ "assertion": {
+ "matcher": "toBeVisible"
+ }
+ }
+ ]
+ },
+ {
+ "name": "should show error on invalid credentials",
+ "tags": ["@auth", "@login", "@validation"],
+ "steps": [
+ {
+ "action": "navigate",
+ "url": "/"
+ },
+ {
+ "action": "click",
+ "role": "button",
+ "text": "Sign In"
+ },
+ {
+ "action": "waitForLoadState",
+ "state": "networkidle"
+ },
+ {
+ "description": "Fill username",
+ "action": "fill",
+ "label": "Username",
+ "value": "invaliduser"
+ },
+ {
+ "description": "Fill password",
+ "action": "fill",
+ "label": "Password",
+ "value": "wrongpassword"
+ },
+ {
+ "description": "Click login button",
+ "action": "click",
+ "role": "button",
+ "text": "Login"
+ },
+ {
+ "description": "Verify error message appears",
+ "action": "expect",
+ "text": "invalid",
+ "assertion": {
+ "matcher": "toBeVisible",
+ "timeout": 5000
+ }
+ }
+ ]
+ },
+ {
+ "name": "should have register/sign up option",
+ "tags": ["@auth", "@registration"],
+ "steps": [
+ {
+ "action": "navigate",
+ "url": "/"
+ },
+ {
+ "action": "click",
+ "role": "button",
+ "text": "Sign In"
+ },
+ {
+ "action": "waitForLoadState",
+ "state": "networkidle"
+ },
+ {
+ "description": "Verify register option exists",
+ "action": "expect",
+ "text": "register",
+ "assertion": {
+ "matcher": "toBeVisible",
+ "timeout": 5000
+ }
+ }
+ ]
+ },
+ {
+ "name": "should have back button to return to landing",
+ "tags": ["@auth", "@navigation"],
+ "steps": [
+ {
+ "action": "navigate",
+ "url": "/"
+ },
+ {
+ "action": "click",
+ "role": "button",
+ "text": "Sign In"
+ },
+ {
+ "action": "waitForLoadState",
+ "state": "networkidle"
+ },
+ {
+ "description": "Verify back button exists",
+ "action": "expect",
+ "role": "button",
+ "text": "back",
+ "assertion": {
+ "matcher": "toBeVisible",
+ "timeout": 5000
+ }
+ }
+ ]
+ }
+ ]
+}
diff --git a/packages/smoke_tests/package.json b/packages/smoke_tests/package.json
new file mode 100644
index 000000000..4966e36dd
--- /dev/null
+++ b/packages/smoke_tests/package.json
@@ -0,0 +1,10 @@
+{
+ "$schema": "https://metabuilder.dev/schemas/package-metadata.schema.json",
+ "packageId": "smoke_tests",
+ "name": "Smoke Tests",
+ "version": "1.0.0",
+ "description": "Core smoke tests for MetaBuilder application",
+ "category": "testing",
+ "minLevel": 0,
+ "keywords": ["smoke", "tests", "core", "critical"]
+}
diff --git a/packages/smoke_tests/playwright/tests.json b/packages/smoke_tests/playwright/tests.json
new file mode 100644
index 000000000..5b5dedf41
--- /dev/null
+++ b/packages/smoke_tests/playwright/tests.json
@@ -0,0 +1,93 @@
+{
+ "$schema": "https://metabuilder.dev/schemas/package-playwright.schema.json",
+ "package": "smoke_tests",
+ "version": "1.0.0",
+ "description": "Basic smoke tests converted from e2e/smoke.spec.ts - core application functionality tests",
+ "tests": [
+ {
+ "name": "should load the application",
+ "tags": ["@smoke", "@critical"],
+ "timeout": 10000,
+ "steps": [
+ {
+ "description": "Navigate to the app",
+ "action": "navigate",
+ "url": "/"
+ },
+ {
+ "description": "Wait for page load",
+ "action": "waitForLoadState",
+ "state": "domcontentloaded"
+ },
+ {
+ "description": "Verify page has content",
+ "action": "expect",
+ "selector": "body",
+ "assertion": {
+ "matcher": "toContainText",
+ "expected": "MetaBuilder"
+ }
+ }
+ ]
+ },
+ {
+ "name": "should have proper page title",
+ "tags": ["@smoke"],
+ "steps": [
+ {
+ "action": "navigate",
+ "url": "/"
+ },
+ {
+ "description": "Verify title is set",
+ "action": "expect",
+ "selector": "head title",
+ "assertion": {
+ "matcher": "toBeVisible"
+ }
+ }
+ ]
+ },
+ {
+ "name": "should display MetaBuilder landing page",
+ "tags": ["@smoke", "@ui"],
+ "steps": [
+ {
+ "action": "navigate",
+ "url": "/"
+ },
+ {
+ "action": "waitForLoadState",
+ "state": "domcontentloaded"
+ },
+ {
+ "description": "Check Sign In button",
+ "action": "expect",
+ "role": "button",
+ "text": "Sign In",
+ "assertion": {
+ "matcher": "toBeVisible"
+ }
+ }
+ ]
+ },
+ {
+ "name": "should have viewport properly configured",
+ "tags": ["@smoke"],
+ "steps": [
+ {
+ "action": "navigate",
+ "url": "/"
+ },
+ {
+ "description": "Page should be visible",
+ "action": "expect",
+ "selector": "body",
+ "assertion": {
+ "matcher": "toBeVisible"
+ }
+ }
+ ]
+ }
+ ]
+}