View and Block System v2
Unified view renderer, standalone view route, per-block Suspense loading, real-time AI iteration, and view management (copy, fork, pin).
Superseded by view-block-system-v3.
Problem
Three separate renderers exist today for views: ViewRenderer (list views), DetailViewRenderer (entity detail), and WorkspaceViewRenderer (workspace pages with regions). This creates duplicated logic, inconsistent behavior, and confusion about which renderer to use. Views can only be rendered in the context of entity list/detail pages -- there is no standalone view route for AI-generated dashboards or shareable reports. The view editor at 1,021 lines violates the 200-line component rule. Blocks load synchronously, meaning a slow chart blocks the entire page render.
Solution
Replace three view renderers with one UnifiedViewRenderer. Add a standalone /view/[id] route for opening views as full pages. Wrap each block in a Suspense boundary with type-specific skeleton loaders for independent loading. Add Supabase Realtime subscription so AI agents can iterate on views in real-time. Split the monolithic view editor into focused sub-components. Add view management operations: copy, fork, and pin-to-sidebar.
Design
Architecture
UnifiedViewRenderer
A single server component that handles all view types:
interface UnifiedViewRendererProps {
view: ViewRecord;
entity?: EntityRecord; // For detail views
entityType?: EntityTypeRecord; // For scoped resolution
className?: string;
editable?: boolean; // Enable block-level settings
}Rendering logic:
- If
view.regionsis defined and has content, render withViewShell(region-based layout) - Otherwise, render with
BlockGrid(flat layout usingview.blocks+view.layout)
Both paths use the same block resolution: resolveBlocks() fetches data for each block server-side. Each block is wrapped in a <Suspense> boundary with a BlockSkeleton fallback. Heavy blocks (chart, table, kanban) load independently -- the page shell renders instantly.
ViewShell (replaces WorkspaceShell)
Region-based layout using CSS Grid with four layout presets:
| Preset | Grid | Description |
|---|---|---|
stack | Single column | Main only, full width |
sidebar | 1fr 320px | Main + right sidebar |
three-panel | 240px 1fr 320px | Left + main + right |
document | 200px 1fr | Narrow nav + wide content |
All presets support optional header (above grid) and footer (below grid) regions.
Per-Block Suspense
Each block gets an independent loading boundary:
{blocks.map((block) => (
<div key={block.id} className={sizeClass(block.size, layout)}>
<Suspense fallback={<BlockSkeleton type={block.type} size={block.size} />}>
<AsyncBlockRenderer block={block} context={ctx} />
</Suspense>
</div>
))}BlockSkeleton renders type-appropriate placeholders:
stat-cards: row of shimmer rectangleschart: shimmer rectangle with axis linestable: shimmer header + rowsfield-card: label + value shimmer- Default: simple shimmer card
Standalone View Route -- /view/[id]
New route at app/(app)/view/[id]/page.tsx. Server component fetches the view and renders it with UnifiedViewRenderer.
ViewHeader shows: view title and description, edit button (opens view editor sheet), share/copy link button, "Save to sidebar" button (adds to nav), and fork button (creates a copy).
For transient views generated by AI in chat, the "Open full page" action saves the transient view to the views table (scope: "user") and navigates to /view/\{newId\}. The view is then editable, forkable, and shareable.
Real-Time AI Iteration
When a view is open at /view/[id], subscribe to Supabase Realtime on the views table:
function useViewRealtime(viewId: string) {
// Subscribe to postgres_changes on views table
// On UPDATE, invalidate React Query cache for this view
// View re-renders with new blocks instantly
}AI iteration flow:
- User opens
/view/\{id\}in browser - In chat, user says "Add a chart block showing revenue by stage"
- AI calls
manageViewtool to update the view's blocks - Realtime subscription fires, view re-renders with the new block
- User sees the change instantly without page refresh
View Management
Copy: Creates an independent duplicate with a new title via copyView(sourceViewId, newTitle?).
Fork: Creates a linked copy that tracks its parent. New parent_view_id column on the views table tracks lineage.
Pin to sidebar: Views can be added to the sidebar navigation via the existing nav_configs system. When pinned, the nav item links to /view/\{id\} for standalone views or to the entity type page with ?view=\{id\} for entity-scoped views.
View Templates
Pre-built view configurations that AI or users can instantiate:
const VIEW_TEMPLATES = {
"kpi-dashboard": {
title: "KPI Dashboard",
description: "Key metrics overview with charts",
layout: "bento",
blocks: [
{ type: "stat-cards", size: "full", config: { limit: 8 } },
{ type: "chart", size: "half", config: { chartType: "bar" } },
{ type: "ranking", size: "half", config: { limit: 5 } },
{ type: "activity", size: "full", config: { limit: 10 } },
]
},
"entity-overview": { ... },
"comparison-view": { ... },
"pipeline-tracker": { ... },
"document-workspace": { ... },
}Templates are available in the "New View" dialog and via AI tools.
View Editor Refactor
Split the 1,021-line view-editor.tsx into focused components:
features/views/components/
view-editor/
index.tsx (~80 lines) -- main editor shell
block-list.tsx (~100 lines) -- sortable block list with DnD
block-config-panel.tsx (~80 lines) -- config panel dispatcher
block-type-picker.tsx (~60 lines) -- block type selection grid
configs/
chart-config.tsx (~80 lines)
table-config.tsx (~60 lines)
kanban-config.tsx (~60 lines)
field-card-config.tsx (~50 lines)
stat-cards-config.tsx (~50 lines)
connection-config.tsx (~50 lines)
generic-config.tsx (~40 lines) -- fallback for simple blocksEach config component is self-contained with its own Zod schema for validation.
Per-Block-Type Config Interfaces
Add typed config interfaces for each block type:
type TypedBlockConfig =
| { type: "chart"; config?: ChartBlockConfig }
| { type: "kanban"; config?: KanbanBlockConfig }
| { type: "field-card"; config?: FieldCardBlockConfig }
// ... etcThis enables type-safe config editing and validation throughout the system.
API
POST /api/views/copy -- copy a view. Body: { sourceViewId: string, title?: string }. Returns the new view record.
POST /api/views/[id]/fork -- fork a view with lineage tracking. Returns the new view record with parent_view_id set.
Existing manageView AI tool continues to work for view CRUD. The Realtime subscription ensures changes propagate instantly.
Migration
ALTER TABLE views ADD COLUMN parent_view_id uuid REFERENCES views(id) ON DELETE SET NULL;
CREATE INDEX idx_views_parent ON views(parent_view_id) WHERE parent_view_id IS NOT NULL;No other schema changes required. The existing schema supports standalone views (entity_type_id = NULL, entity_id = NULL), entity-specific overrides (entity_id IS NOT NULL), sidebar pinning (via nav_configs), and real-time via Supabase Realtime on the existing views table.
Cleanup
Delete after migration:
features/views/components/view-renderer.tsx-- replaced by UnifiedViewRendererfeatures/views/components/view-renderer-client.tsx-- replaced by UnifiedViewRendererfeatures/views/components/detail-view-renderer.tsx-- replaced by UnifiedViewRendererfeatures/views/components/workspace-view-renderer.tsx-- replaced by UnifiedViewRendererfeatures/views/components/workspace-shell.tsx-- renamed to view-shell.tsxfeatures/views/components/detail-view-tabs.tsx-- merged into ViewTabBarfeatures/views/components/view-editor.tsx-- split into view-editor/ directory
Trade-offs
Unified renderer vs. specialized renderers: A single renderer with two code paths (regions vs. flat blocks) is simpler to maintain than three separate renderers, but the rendering logic must handle more cases in one place. The internal branching is straightforward (regions defined? use ViewShell : use BlockGrid), so the complexity is manageable.
Supabase Realtime vs. polling: Realtime provides instant updates for AI iteration, which is critical for the "AI edits, user sees" workflow. Polling would add latency and unnecessary load. The tradeoff is a persistent WebSocket connection per open view, but Supabase handles this efficiently.
Per-block Suspense vs. page-level loading: Per-block Suspense means fast initial paint (the layout renders immediately, blocks stream in). The tradeoff is more Suspense boundaries and slightly more complex error handling (each block needs its own error boundary). The user experience improvement is significant for views with heavy blocks like charts and tables.
View templates as code vs. DB-stored: Code-stored templates are versioned, type-safe, and easy to update. DB-stored templates would allow user-created templates but add complexity. Starting with code templates is simpler; user templates can be added later by promoting saved views.
parent_view_id for fork tracking: A simple nullable FK is the lightest way to track fork lineage. It does not support deep fork trees (fork of a fork), but one-level tracking covers the primary use case. Deep lineage could be added later with a separate provenance table if needed.
Acceptance Criteria
- All views render through
UnifiedViewRenderer-- no separate detail/workspace/list renderers - Views with regions use ViewShell; views without regions use BlockGrid
- Each block loads independently via Suspense with a type-specific skeleton
- Page shell renders instantly; slow blocks (chart, table) stream in
- Standalone view at
/view/[id]renders any persisted view - ViewHeader shows title, edit, share, pin-to-sidebar, and fork actions
- AI-generated transient views can be saved and opened at
/view/[id] - Realtime subscription updates view when AI modifies it via
manageViewtool - Copy creates an independent duplicate with a new title
- Fork creates a linked copy with
parent_view_idset - Pin-to-sidebar adds a nav item via
nav_configs - View templates can be instantiated from the New View dialog
- View editor is split into sub-components, all under 200 lines
- Per-block config forms use typed Zod schemas
- Old renderers deleted, no dead code remaining
- All existing view functionality preserved (no regressions)
- Visual verification of standalone view, skeleton loading, and editor
-
pnpm buildandpnpm testpass
Files
New
app/(app)/view/[id]/page.tsx-- standalone view route (server component)app/(app)/view/[id]/view-page-client.tsx-- client wrapper with realtime subscriptionfeatures/views/components/unified-view-renderer.tsx-- single unified rendererfeatures/views/components/view-shell.tsx-- region-based layout (replaces workspace-shell)features/views/components/view-header.tsx-- view title, actions, managementfeatures/views/components/view-tab-bar.tsx-- unified tab bar (replaces two separate tab components)features/views/components/block-skeleton.tsx-- type-specific loading skeletonsfeatures/views/hooks/use-view-realtime.ts-- Supabase Realtime subscription hookfeatures/views/lib/templates.ts-- view template definitionsfeatures/views/components/view-editor/index.tsx-- editor shell (~80 lines)features/views/components/view-editor/block-list.tsx-- sortable block list (~100 lines)features/views/components/view-editor/block-config-panel.tsx-- config dispatcher (~80 lines)features/views/components/view-editor/block-type-picker.tsx-- type selection grid (~60 lines)features/views/components/view-editor/configs/chart-config.tsx-- chart block config formfeatures/views/components/view-editor/configs/table-config.tsx-- table block config formfeatures/views/components/view-editor/configs/kanban-config.tsx-- kanban block config formfeatures/views/components/view-editor/configs/field-card-config.tsx-- field card config formfeatures/views/components/view-editor/configs/stat-cards-config.tsx-- stat cards config formfeatures/views/components/view-editor/configs/connection-config.tsx-- connection block config formfeatures/views/components/view-editor/configs/generic-config.tsx-- fallback config formdocuments/AI-VIEW-AUTHORING.md-- AI agent reference guide for view authoringsupabase/migrations/YYYYMMDD_NNN_add_parent_view_id.sql-- parent_view_id column
Modified
features/views/types.ts-- addparent_view_idto ViewRecordfeatures/views/server/actions.ts-- addcopyView(), enhanceforkViewForEntity()features/blocks/types.ts-- add per-block-type config interfaces (TypedBlockConfig union)features/blocks/components/block-renderer.tsx-- add card wrapper, animation, skeleton supportapp/(app)/[typeSlug]/page.tsx-- use UnifiedViewRendererapp/(app)/[typeSlug]/[id]/entity-detail-client.tsx-- use UnifiedViewRendererapp/api/views/route.ts-- add copy endpointapp/api/views/[id]/route.ts-- add fork endpoint
Deleted
features/views/components/view-renderer.tsx-- replaced by unified rendererfeatures/views/components/view-renderer-client.tsx-- replaced by unified rendererfeatures/views/components/detail-view-renderer.tsx-- replaced by unified rendererfeatures/views/components/detail-view-tabs.tsx-- replaced by ViewTabBarfeatures/views/components/workspace-view-renderer.tsx-- replaced by unified rendererfeatures/views/components/workspace-shell.tsx-- replaced by view-shell.tsxfeatures/views/components/view-editor.tsx-- split into view-editor/ directory
Unified Agent Intelligence
Consolidate feedback intake, self-improvement, admin review, and eval systems into a thin harness around two primitives — feedback inputs and shared_context outputs — that turn every agent interaction into durable workspace intelligence.
View/Block System Cleanup (v3)
Unified workspace editor, response-integrated field blocks, container removal, block config schemas, and consistent editing experience across admin and entity detail pages.