@specverse/runtime
v4.1.14
Published
SpecVerse runtime — view engine, React adapter, Tailwind renderer
Readme
@specverse/runtime
Runtime view engine for SpecVerse — renders spec-driven and dev views at runtime in the browser.
Architecture
@specverse/runtime
src/runtime/views/
core/ Framework-agnostic
├── entity-display.ts Entity name resolution (name > title > label > FK chain > ID)
├── field-classification.ts Field categorization (business, metadata, auto, relationship)
├── pattern-engine.ts View type → pattern detection, CURVED protocol mapping
├── composite-pattern-types.ts 9 interfaces for tech-independent view patterns
├── composite-patterns.ts 4 pattern definitions (form, list, detail, dashboard)
├── atomic-components-registry.ts 49 component type definitions
└── types.ts RuntimeViewProviderValue contract
react/ React adapter
├── context.ts RuntimeViewProvider + useRuntimeContext()
├── react-pattern-adapter.tsx 1743-line pattern → HTML renderer
├── hooks/
│ ├── useTheme.ts Dark/light toggle with localStorage
│ ├── useResizableSidebar.ts Draggable sidebar width
│ ├── useEntityHelpers.ts Field separation, relationship resolution
│ └── useEventStream.ts WebSocket event streaming
└── components/
├── DevShell.tsx Main dev GUI (tabs: Views, Models, Events)
├── ViewRouter.tsx Sidebar + routing (display, forms, operations)
├── RuntimeView.tsx Pattern-based view renderer
├── FormView.tsx View-spec adapter → ModelManager
├── ModelSelector.tsx Model browser sidebar
├── ModelManager.tsx Full CRUD + lifecycle transitions
├── ViewSidebar.tsx View selection sidebar
├── RelationshipField.tsx Smart FK dropdowns (belongsTo/hasMany/hasOne)
├── FieldInput.tsx Type-aware input (bool, number, datetime, etc.)
├── EntitySelect.tsx Entity dropdown with cascading filters
├── OperationView.tsx Service operation header + executor
├── OperationExecutor.tsx Parameter inputs, execute, result display
├── OperationResultView.tsx Formatted results with entity name resolution
├── EventStream.tsx Real-time event log with type filtering
└── ui/
└── ResizeHandle.tsx Draggable resize handle
tailwind/ Styling
└── universal-adapter.ts 48 component types → Tailwind HTML markupSubpath Exports
// Core utilities (framework-agnostic)
import { getEntityDisplayName, classifyFields, detectPattern, COMPOSITE_VIEW_PATTERNS } from '@specverse/runtime/views';
// React adapter (hooks, components)
import { DevShell, RuntimeView, ViewRouter, FormView, ModelManager, useTheme, useEventStream } from '@specverse/runtime/views/react';
// Tailwind markup generator
import { createUniversalTailwindAdapter } from '@specverse/runtime/views/tailwind';How It Works
The RuntimeViewProvider Pattern
The runtime package's components are decoupled from any specific state management or API client. Instead, they consume data and hooks through a React context:
// Generated App.tsx (or app-demo's RuntimeBridge)
import { RuntimeViewProvider } from '@specverse/runtime/views/react';
<RuntimeViewProvider value={{
// Instance-specific hooks — how to fetch data
useEntitiesQuery: (controller, model) => useMyApiHook(controller, model),
useModelSchemaQuery: (model) => useMySchemaHook(model),
useExecuteOperationMutation: () => useMyMutation(),
useTransitionStateMutation: () => useMyTransitionMutation(),
// Current state
entities: { Order: [...], Customer: [...] },
modelSchemas: { Order: { attributes: {...}, relationships: {...} } },
views: [...],
spec: parsedSpec,
apiBaseUrl: '/api',
}}>
<DevShell />
</RuntimeViewProvider>Components call useRuntimeContext() to access this — no direct imports of appStore, API clients, or framework-specific state.
Two Realize Modes
# Runtime mode (default) — slim frontend, views rendered at runtime
specverse realize all specs/main.specly
# Static mode — full frontend, all views pre-generated as code
specverse realize all specs/main.specly --staticRuntime mode generates ~12 frontend files:
App.tsx— imports DevShell, sets up RuntimeViewProviderapiClient.ts,useApi.ts,api.ts— instance-specific API wiringdev.specly— auto-generated dev GUI views- Config files (vite, tsconfig, package.json, tailwind, postcss)
Static mode generates ~185 frontend files:
- Per-model view components (list, detail, form, dashboard)
- Per-model hooks, types, forms
- Full pattern adapter and tailwind adapter bundled
dev.specly — The Dev GUI Specification
Inference automatically generates dev.specly alongside the inferred spec. It describes the development GUI:
metadata:
name: MyApp-dev
type: development
generated: true
navigation:
sidebar:
General:
- Customer
- Product
Customer:
- Order
Services:
- ShippingService
views:
# Standard views for every model
CustomerList:
type: list
model: Customer
CustomerDetail:
type: detail
model: Customer
CustomerForm:
type: form
model: Customer
# Specialist views (auto-detected from model characteristics)
OrderBoard: # lifecycle → board view
type: board
model: Order
OrderTimeline: # date fields → timeline view
type: timeline
model: Order
ProductDashboard: # numeric fields → dashboard
type: dashboard
model: Product
# Service operations
ShippingService_calculateShipping:
type: operation
service: ShippingService
operation: calculateShipping
parameters:
orderId: UUID
weight: Decimal
returns: Decimal
source: serviceView Promotion Path
Users can progressively promote dev views to spec views:
1. Dev view (automatic) — generated in dev.specly, rendered by DevShell
↓
2. Named spec view — copy to main.specly, same rendering
↓
3. Configured spec view — add columns, sort, filters
↓
4. Custom spec view — add full uiComponents, user-owned codeEach step is additive. The runtime engine handles all levels.
How to Use
In a Generated Project (realize)
specverse init my-app --demo
cd my-app
specverse realize all specs/main.specly
cd generated/code
npm run setup
npm run dev:backend # Start Fastify + Prisma
npm run dev:frontend # Start Vite dev server → DevShellThe frontend opens with three tabs:
- Views — browse all spec and dev views
- Models — CRUD any model, manage lifecycle states
- Events — real-time event stream from the backend
In app-demo (RuntimeBridge)
// main.tsx
import { RuntimeBridge } from './providers/RuntimeBridge';
<QueryProvider>
<ApiInitializer>
<RuntimeBridge>
<App />
</RuntimeBridge>
</ApiInitializer>
</QueryProvider>Then import components from the runtime package:
import { ViewRouter, FormView, OperationView, usePatternAdapter } from '@specverse/runtime/views/react';
import { ModelSelector } from '@specverse/runtime/views/react';Standalone Usage
import { RuntimeViewProvider, DevShell } from '@specverse/runtime/views/react';
function App() {
return (
<RuntimeViewProvider value={myRuntimeConfig}>
<DevShell
devSpec={parsedDevSpec}
appSpec={parsedAppSpec}
title="My App"
/>
</RuntimeViewProvider>
);
}How to Extend
Add a New View Type
- Add pattern detection in
core/pattern-engine.ts:
const VIEW_TYPE_TO_PATTERN = {
// ... existing
'kanban': { patternId: 'kanban-view', category: 'data-display' },
};- Add rendering in
react/react-pattern-adapter.tsx(or the built-in renderer inRuntimeView.tsx):
if (pattern.patternId === 'kanban-view') {
return this.renderKanbanView(context);
}- Add to dev.specly generator in
engines/src/inference/dev-specly-generator.ts:
if (someModelCharacteristic) {
views[`${modelName}Kanban`] = { type: 'kanban', model: modelName };
}Add a New DevShell Tab
Edit runtime/src/runtime/views/react/components/DevShell.tsx:
type TabId = 'views' | 'models' | 'events' | 'mytab';
const TABS: Tab[] = [
// ... existing
{ id: 'mytab', label: 'My Tab' },
];
// In render:
{activeTab === 'mytab' && <MyComponent />}Or pass a custom tab via props (not yet implemented — would need a tabs prop on DevShell).
Add a New Tailwind Component
Edit runtime/src/runtime/views/tailwind/universal-adapter.ts:
case 'kanban-column':
return `
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg p-4 min-w-[280px]">
<h3 class="font-semibold text-sm mb-3">${properties.title}</h3>
<div class="space-y-2">${children}</div>
</div>
`;Create a Framework Adapter (Vue, Svelte, etc.)
The core layer (@specverse/runtime/views) is framework-agnostic. To create a Vue adapter:
- Create
runtime/src/runtime/views/vue/directory - Implement the same component set (DevShell, ViewRouter, etc.) using Vue
- Use the same core utilities (entity-display, field-classification, pattern-engine)
- Add a subpath export:
@specverse/runtime/views/vue
The core does the thinking (pattern detection, field classification, entity resolution). The adapter does the rendering.
Bridge to a Custom State Manager
The RuntimeViewProviderValue interface is the only contract. Implement it with any state manager:
// Redux
const value: RuntimeViewProviderValue = {
useEntitiesQuery: (ctrl, model) => useSelector(state => state.entities[model]),
entities: store.getState().entities,
// ...
};
// MobX
const value: RuntimeViewProviderValue = {
useEntitiesQuery: (ctrl, model) => ({ data: entityStore.get(model), isLoading: false, error: null }),
entities: entityStore.all,
// ...
};Package Dependencies
@specverse/runtime
├── @specverse/types (workspace dependency)
├── react (peer dependency, ^18.0.0 || ^19.0.0)
└── react-dom (peer dependency, ^18.0.0 || ^19.0.0)No other runtime dependencies. The tailwind adapter generates HTML strings — Tailwind CSS is a dev dependency of the consuming project, not of the runtime package.
Known Limitations (v4.1.x)
- Board, timeline, calendar views — not yet rendered (filtered from sidebar)
- Service operation routes — generated Fastify backend has no service endpoints
- Custom controller operations — only standard CRUD routes generated
- WebSocket events — generated backend has no
/wsendpoint (Events tab won't connect) - Entity format — handles both flat (Prisma) and wrapped (app-demo) but FK naming varies (camelCase vs snake_case)
- No tests — runtime components have no unit test coverage
See TODO.md for the full roadmap including the planned backend runtime engine extraction.
Relationship to Other Packages
@specverse/types Contracts (both build-time and runtime)
@specverse/entities Language definition (build-time only)
@specverse/engines Toolchain — parser, inference, realize (build-time only)
@specverse/runtime View engine (runtime, browser) ← this packageThe engines package knows about runtime (the slim factory generates code that imports it). The runtime package does not know about engines — it only depends on types.
