Generated by Spark: Ensure / route is wired up with some JSON config.

This commit is contained in:
2026-01-17 12:54:05 +00:00
committed by GitHub
parent 8d5f87d8be
commit b8168ded76
5 changed files with 248 additions and 31 deletions

View File

@@ -29,9 +29,11 @@
## JSON-Driven UI
- [JSON_PAGES_GUIDE.md](./JSON_PAGES_GUIDE.md) - Building pages from JSON configuration
- [json-routing.md](./json-routing.md) - **JSON-based routing and root route configuration**
- [JSON_UI_GUIDE.md](../JSON_UI_GUIDE.md) - Original JSON UI documentation
### Page Schemas
- `/src/config/pages.json` - **Master routing configuration (includes root `/` route)**
- `/src/config/pages/dashboard.json` - Dashboard page configuration
- More schemas can be added for other pages

184
docs/json-routing.md Normal file
View File

@@ -0,0 +1,184 @@
# JSON-Driven Routing Configuration
This application uses a JSON-based configuration system to define routes and pages declaratively.
## Overview
All routes are defined in `/src/config/pages.json` and automatically loaded by the router system.
## Root Route Configuration
The `/` (root) route is determined by the page marked with `"isRoot": true` in the pages.json configuration:
```json
{
"id": "home",
"title": "Home",
"icon": "House",
"component": "ProjectDashboard",
"enabled": true,
"isRoot": true,
"order": 0,
"props": {
"state": ["files", "models", "components", "theme", ...],
}
}
```
## Page Configuration Schema
Each page in `pages.json` supports the following properties:
### Required Properties
- `id` (string): Unique identifier used in the URL path (`/dashboard`, `/code`, etc.)
- `title` (string): Display name shown in navigation
- `icon` (string): Phosphor icon name for navigation UI
- `component` (string): Name of the React component to render (must be registered in ComponentRegistry)
- `enabled` (boolean): Whether the page is active
- `order` (number): Sort order for navigation menu
### Optional Properties
- `isRoot` (boolean): If true, this page will be rendered at the `/` route
- `toggleKey` (string): Feature toggle key that controls page visibility
- `shortcut` (string): Keyboard shortcut (e.g., "ctrl+1", "ctrl+shift+s")
- `requiresResizable` (boolean): Whether the page uses a resizable split layout
- `props` (object): Component props configuration (see Props Configuration)
- `resizableConfig` (object): Split panel layout configuration (see Resizable Layout)
## Props Configuration
Props can be automatically resolved from app state and actions:
```json
"props": {
"state": ["files", "models", "activeFileId"],
"actions": ["onFileChange:handleFileChange", "onFileSelect:setActiveFileId"]
}
```
### State Props
State props are mapped from the global state context:
- Simple mapping: `"files"``{ files: stateContext.files }`
- Renamed mapping: `"trees:componentTrees"``{ trees: stateContext.componentTrees }`
### Action Props
Action props are mapped from the action context using `propName:actionName` format:
- `"onFileChange:handleFileChange"``{ onFileChange: actionContext.handleFileChange }`
## Resizable Layout Configuration
Pages with split panels use the `requiresResizable` flag and `resizableConfig`:
```json
{
"id": "code",
"requiresResizable": true,
"resizableConfig": {
"leftComponent": "FileExplorer",
"leftProps": {
"state": ["files", "activeFileId"],
"actions": ["onFileSelect:setActiveFileId"]
},
"leftPanel": {
"defaultSize": 20,
"minSize": 15,
"maxSize": 30
},
"rightPanel": {
"defaultSize": 80
}
}
}
```
## Feature Toggles
Pages can be conditionally shown based on feature toggles:
```json
{
"id": "models-json",
"toggleKey": "modelsJSON",
"enabled": true
}
```
The page will only appear if `featureToggles.modelsJSON !== false`.
## Keyboard Shortcuts
Define shortcuts in `ctrl+key` or `ctrl+shift+key` format:
```json
{
"id": "dashboard",
"shortcut": "ctrl+1"
}
```
Shortcuts automatically navigate to the page when pressed.
## Route Resolution Flow
1. **Load Configuration**: `pages.json` is loaded by `getPageConfig()`
2. **Filter Enabled Pages**: Only pages with `enabled: true` and matching feature toggles
3. **Sort by Order**: Pages are sorted by the `order` field
4. **Create Routes**: Each page becomes a route object with:
- Path: `/${page.id}` or `/` if `isRoot: true`
- Element: Lazy-loaded component with resolved props
- Layout: Standard or resizable based on configuration
5. **Add Fallback**: Wildcard route redirects to root
## Adding a New Page
1. Add entry to `src/config/pages.json`:
```json
{
"id": "my-page",
"title": "My Page",
"icon": "Star",
"component": "MyPageComponent",
"enabled": true,
"order": 100,
"props": {
"state": ["someData"],
"actions": ["onSave:handleSave"]
}
}
```
2. Register component in `src/lib/component-registry.ts`
3. The route is automatically created at `/my-page`
## Console Logging
The routing system includes extensive console logging prefixed with `[ROUTES]` and `[CONFIG]`:
- `[ROUTES] 🏗️` - Route creation started
- `[ROUTES] 📄` - Pages loaded
- `[ROUTES] 🏠` - Root page identified
- `[ROUTES] 📝` - Individual route configured
- `[CONFIG] 🔍` - Config queries
- `[CONFIG] ✅` - Successful resolution
## Example: Changing the Root Page
To make a different page appear at `/`:
1. Remove `"isRoot": true` from the current root page (if any)
2. Add `"isRoot": true` to your desired page in `pages.json`
3. The change takes effect immediately on reload
## Related Files
- `/src/config/pages.json` - Page definitions
- `/src/config/page-loader.ts` - Configuration loading and prop resolution
- `/src/router/routes.tsx` - Route creation from config
- `/src/router/RouterProvider.tsx` - Router setup
- `/src/lib/component-registry.ts` - Component registration

View File

@@ -25,6 +25,7 @@ export interface PageConfig {
icon: string
component: string
enabled: boolean
isRoot?: boolean
toggleKey?: string
shortcut?: string
order: number

View File

@@ -1,5 +1,17 @@
{
"pages": [
{
"id": "home",
"title": "Home",
"icon": "House",
"component": "ProjectDashboard",
"enabled": true,
"isRoot": true,
"order": 0,
"props": {
"state": ["files", "models", "components", "theme", "playwrightTests", "storybookStories", "unitTests", "flaskConfig", "nextjsConfig"]
}
},
{
"id": "dashboard",
"title": "Dashboard",

View File

@@ -76,46 +76,64 @@ export function createRoutes(
const enabledPages = getEnabledPages(featureToggles)
console.log('[ROUTES] 📄 Enabled pages:', enabledPages.map(p => p.id).join(', '))
const routes: RouteObject[] = enabledPages.map(page => {
console.log('[ROUTES] 📝 Configuring route for page:', page.id)
const props = page.props
? resolveProps(page.props, stateContext, actionContext)
: {}
const rootPage = enabledPages.find(p => p.isRoot)
console.log('[ROUTES] 🏠 Root page:', rootPage?.id || 'none (will use dashboard)')
if (page.requiresResizable && page.resizableConfig) {
console.log('[ROUTES] 🔀 Page requires resizable layout:', page.id)
const config = page.resizableConfig
const leftProps = resolveProps(config.leftProps, stateContext, actionContext)
const routes: RouteObject[] = enabledPages
.filter(p => !p.isRoot)
.map(page => {
console.log('[ROUTES] 📝 Configuring route for page:', page.id)
const props = page.props
? resolveProps(page.props, stateContext, actionContext)
: {}
if (page.requiresResizable && page.resizableConfig) {
console.log('[ROUTES] 🔀 Page requires resizable layout:', page.id)
const config = page.resizableConfig
const leftProps = resolveProps(config.leftProps, stateContext, actionContext)
return {
path: `/${page.id}`,
element: (
<ResizableLayout
leftComponent={config.leftComponent}
rightComponent={page.component}
leftProps={leftProps}
rightProps={props}
config={config}
/>
)
}
}
return {
path: `/${page.id}`,
element: (
<ResizableLayout
leftComponent={config.leftComponent}
rightComponent={page.component}
leftProps={leftProps}
rightProps={props}
config={config}
/>
)
element: <LazyComponent componentName={page.component} props={props} />
}
}
})
return {
path: `/${page.id}`,
element: <LazyComponent componentName={page.component} props={props} />
}
})
routes.push({
path: '/',
element: <Navigate to="/dashboard" replace />
})
if (rootPage) {
console.log('[ROUTES] ✅ Adding root route from JSON config:', rootPage.component)
const props = rootPage.props
? resolveProps(rootPage.props, stateContext, actionContext)
: {}
routes.push({
path: '/',
element: <LazyComponent componentName={rootPage.component} props={props} />
})
} else {
console.log('[ROUTES] ⚠️ No root page in config, redirecting to /dashboard')
routes.push({
path: '/',
element: <Navigate to="/dashboard" replace />
})
}
routes.push({
path: '*',
element: <Navigate to="/dashboard" replace />
element: <Navigate to="/" replace />
})
console.log('[ROUTES] ✅ Routes created:', routes.length, 'routes')