Cortex Panel — runtime frontend¶
Cómo el shell React consume manifests y dashboards CUS por HTTP. Para declarar UI desde Python, usa la guía Plugins.
Ver ADR 003, ADR 018 y contexto del proyecto.
Arquitectura npm¶
| Paquete | Rol |
|---|---|
@cortex/panel-core | Headless: tipos, PanelProvider, nav, routing, PanelApiClient |
@cortex/panel-shadcn | Skin shadcn/ui + Tailwind v4: shell, widgets, DashboardRenderer |
@cortex/web | createPanelShellApp — rutas, provider, API client |
framework/web-shadcn | Demo Vite → :5175 |
flowchart TB
subgraph demo [Demo Vite]
SH[framework/web-shadcn]
end
subgraph web ["@cortex/web"]
W["createPanelShellApp"]
end
subgraph skin ["@cortex/panel-shadcn"]
SK[widgets + PanelShell]
end
subgraph core ["@cortex/panel-core"]
C["PanelProvider navUtils PanelApiClient"]
end
SH --> W --> SK --> C Arranque local¶
npm install # raíz — workspaces
npm run dev:web-shadcn # http://localhost:5175
npm run build:web-shadcn
Vite hace proxy de /api → http://localhost:8000. La API debe estar en marcha (uv run cortex-api).
Flujo runtime¶
- En bootstrap, el plugin registra UI (
register_resourcesoregister_dashboards). - El shell carga rutas con
GET /api/v1/panels/routes. ModuleScreenresuelve pantalla →GET /api/v1/ui/dashboards/{dashboardId}.WidgetRendererinstancia el componente segúnwidget.type.
sequenceDiagram
participant API as FastAPI
participant Shell as PanelProvider
participant M as ModuleScreen
participant W as Widgets
Shell->>API: GET /api/v1/panels/{panelId}
M->>API: GET manifest + dashboard
M->>W: layout + configs
W->>API: fetchPath datos y forms Componentes clave¶
| Componente | Responsabilidad |
|---|---|
PanelProvider | Contexto API, registry, refresh entre widgets |
PanelShell | Sidebar, header, slots de render hooks |
ModuleScreen | Pantalla activa → dashboard |
DashboardRenderer | Grid de widgets |
WidgetRenderer | Lookup registry.get(type) |
Jerarquía de títulos¶
El shell shadcn aplica un contrato de un título visible por pantalla (ver ADR 018):
| Capa | Responsabilidad |
|---|---|
PanelShell | h1 = screens[].title de la pantalla activa |
ModuleScreen | h2 solo si theme.shellOwnsPageTitle es false |
DashboardRenderer | h3 solo si layout.hideTitle es false |
Widgets (data-table, form, infolist) | CardTitle solo si config.title está definido |
Flags en PanelConfiguration.theme:
| Flag | Default (shadcn) | Efecto |
|---|---|---|
shellOwnsPageTitle | true | El header del shell muestra el título; ModuleScreen omite h2 |
showPanelSubtitle | true | Muestra panel.title como subtítulo gris sobre el h1 |
En Python, emit_dashboard(..., hide_title=True) suprime el título del dashboard; el título vive en screens[].title. ResourceBuilder lo aplica por defecto en list/create/edit/view.
flowchart TB
subgraph titles [Jerarquía de títulos]
Shell["PanelShell h1 screen.title"]
Module["ModuleScreen h2 opcional"]
Dash["DashboardRenderer h3 si no hideTitle"]
Widget["Widget CardTitle si config.title"]
end
Shell --> Module --> Dash --> Widget Resolución de pantallas¶
ModuleScreen y PanelShell usan matchModuleScreen de @cortex/panel-core:
- Calculan la ruta relativa con
relativeModulePath(pathname, moduleUrl). - Evalúan todas las entradas de
manifest.screenscondashboardId. - Eligen la pantalla con mayor especificidad (
scoreScreenPath): segmentos literales antes que parámetros (:id).
Ejemplo: la ruta relativa create coincide con path: "create" y con path: ":id", pero gana la literal.
import { matchModuleScreen, relativeModulePath } from '@cortex/panel-core';
const relative = relativeModulePath('/panel/clients/create', '/panel/clients');
const { screen, params } = matchModuleScreen(manifest, relative);
// screen.path === 'create'
Si una acción CREATE navega a create, el manifest debe declarar una pantalla con path: "create" (o usar ResourceBuilder, que la genera automáticamente).
PanelApiClient¶
Rutas habituales (packages/cortex-panel-core/src/panelApiClient.ts):
| Método | Endpoint |
|---|---|
listPanelRoutes() | GET /api/v1/panels/routes |
getPanel(panelId) | GET /api/v1/panels/{panelId} |
getModuleManifest(...) | GET /api/v1/panels/{panelId}/modules/{moduleId}/manifest |
getDashboard(id) | GET /api/v1/ui/dashboards/{id} |
getForm(formId) | GET /api/v1/forms/{formId} |
fetchPath(path) | Cualquier ruta de datos del plugin |
Widget registry¶
import { createDefaultRegistry } from '@cortex/panel-shadcn';
const registry = createDefaultRegistry();
registry.register('mi-widget', MiWidget);
Catálogo de tipos: Widgets (plugins).
Shell mínimo¶
import { createPanelShellApp } from '@cortex/web';
import { PanelShell, createDefaultRegistry } from '@cortex/panel-shadcn';
export const { AppPanelProvider, AppPanelRoutes } = createPanelShellApp({
PanelShell,
createDefaultRegistry,
});
Multi-módulo en un panel¶
GET /api/v1/panels/panel devuelve modules[], navGroups[] y navigation[] — sidebar agrupado con orden por navSort. Varios plugins contribuyen módulos sin compartir código React.
flowchart TB
subgraph panelOperativo [Panel panel]
Shell[PanelShell sidebar único]
B[cortex_plugin_booking]
C[cortex_plugin_clients]
P[cortex_plugin_pricing]
end
Shell --> B
Shell --> C
Shell --> P
B --> API_B["/api/v1/booking/"]
C --> API_C["/api/v1/clients/"]
P --> API_P["/api/v1/pricing/"] Panel Control¶
Módulo embebido cortex_framework.control — rutas /control/, API /api/v1/control/plugins. No es plugin pip.
Validación CUS¶
cortex_framework.panel.validate — schemas en framework/cortex_framework/panel/schemas/.