# TASK-007H: Schema Manager UI ## Overview Build a native Nodegx UI for viewing and editing database schemas in local backends, providing a visual interface over the existing SchemaManager implementation. **Parent Task:** TASK-007 (Integrated Local Backend) **Phase:** H (Schema Management) **Effort:** 16-20 hours (2-3 days) **Priority:** HIGH (Unblocks user productivity) **Dependencies:** TASK-007A (LocalSQL Adapter) --- ## Objectives 1. Create a schema browser panel showing all tables/collections 2. Build a visual schema editor for adding/modifying columns 3. Integrate with existing SchemaManager (no reimplementation) 4. Support all Nodegx field types (String, Number, Boolean, Date, Object, Array, Pointer, Relation) 5. Enable table creation from UI 6. Provide schema export (PostgreSQL, MySQL, Supabase formats) 7. Reuse existing Nodegx UI components (grid, forms, modals) --- ## Background ### Current State Users can create local backends through the Backend Services panel, but have no way to: - View existing tables/schema - Add new tables - Modify table structure (add/remove columns) - Understand what data structures exist The `SchemaManager` class in `TASK-007A` already provides all backend functionality: ```typescript class SchemaManager { createTable(schema: TableSchema): void; addColumn(tableName: string, column: ColumnDefinition): void; getTableSchema(tableName: string): TableSchema | null; getSchema(): SchemaDefinition; exportToPostgres(): string; exportToSupabase(): string; } ``` **We need to build the UI layer on top of this existing logic.** ### Design Principles 1. **No Backend Reimplementation** - Only UI, all logic delegates to SchemaManager 2. **Leverage Existing Components** - Reuse PropertyPanel, DataGrid, Modal patterns 3. **MVP First** - Ship basic functionality fast, enhance in Phase 3 4. **Consistent with Nodegx** - Match editor's visual language --- ## User Flows ### Flow 1: View Schema ``` User clicks "Manage Schema" on Backend Services panel ↓ Schema Manager panel opens ↓ Shows list of tables with: - Table name - Column count - Record count (async load) - Last modified ↓ User clicks table name ↓ Expands to show columns with types ``` ### Flow 2: Create Table ``` User clicks "New Table" button ↓ Modal opens: "Create Table" - Table name input - Optional: Add initial columns ↓ User enters "Products" ↓ User clicks "Add Column" ↓ Column editor appears: - Name: "name" - Type: String (dropdown) - Required: checkbox ↓ User clicks "Create Table" ↓ SchemaManager.createTable() called ↓ Table appears in schema list ``` ### Flow 3: Modify Schema ``` User clicks "Edit Schema" on table ↓ Schema editor opens: - List of existing columns (read-only editing) - Add column button - Remove column button (with warning) ↓ User clicks "Add Column" ↓ Column form appears ↓ User fills: name="price", type=Number, required=true ↓ SchemaManager.addColumn() called ↓ Column added to table ``` --- ## Implementation Steps ### Step 1: Schema Panel Component (4 hours) Create the main schema browser UI component. **File:** `packages/noodl-editor/src/editor/src/views/panels/schemamanager/SchemaPanel.tsx` ```typescript import React, { useState, useEffect } from 'react'; import { ipcRenderer } from 'electron'; import styles from './SchemaPanel.module.css'; interface TableInfo { name: string; columns: ColumnDefinition[]; recordCount?: number; lastModified?: string; } interface ColumnDefinition { name: string; type: string; required: boolean; default?: any; } export function SchemaPanel({ backendId }: { backendId: string }) { const [tables, setTables] = useState([]); const [selectedTable, setSelectedTable] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { loadSchema(); }, [backendId]); async function loadSchema() { setLoading(true); try { // Call IPC to get schema from backend const schema = await ipcRenderer.invoke('backend:getSchema', backendId); // Enrich with record counts (async, non-blocking) const tablesWithCounts = await Promise.all( schema.tables.map(async (table) => { const count = await ipcRenderer.invoke('backend:getRecordCount', backendId, table.name); return { ...table, recordCount: count }; }) ); setTables(tablesWithCounts); } catch (error) { console.error('Failed to load schema:', error); } finally { setLoading(false); } } function handleCreateTable() { // Open create table modal setShowCreateModal(true); } function handleEditTable(tableName: string) { setSelectedTable(tableName); } if (loading) { return
Loading schema...
; } return (

Database Schema

{tables.length === 0 ? (

No tables yet

) : ( tables.map((table) => ( handleEditTable(table.name)} onExpand={() => setSelectedTable( selectedTable === table.name ? null : table.name )} expanded={selectedTable === table.name} /> )) )}
{showCreateModal && ( setShowCreateModal(false)} onSuccess={loadSchema} /> )} {selectedTable && ( setSelectedTable(null)} onUpdate={loadSchema} /> )}
); } ``` **File:** `packages/noodl-editor/src/editor/src/views/panels/schemamanager/TableRow.tsx` ```typescript import React from 'react'; import styles from './TableRow.module.css'; interface TableRowProps { table: TableInfo; expanded: boolean; onExpand: () => void; onEdit: () => void; } export function TableRow({ table, expanded, onExpand, onEdit }: TableRowProps) { return (
{expanded ? '▼' : '▶'}
{table.name}
{table.columns.length} {table.columns.length === 1 ? 'field' : 'fields'} {table.recordCount !== undefined && ( {table.recordCount.toLocaleString()} {table.recordCount === 1 ? 'record' : 'records'} )}
{expanded && (
{table.columns.map((col) => ( ))}
Field Name Type Required Default
{col.name} {col.required ? '✓' : ''} {col.default || '—'}
)}
); } function TypeBadge({ type }: { type: string }) { const typeColors = { String: '#3b82f6', Number: '#10b981', Boolean: '#f59e0b', Date: '#8b5cf6', Object: '#ec4899', Array: '#6366f1', Pointer: '#ef4444', Relation: '#ef4444', }; return ( {type} ); } ``` --- ### Step 2: Create Table Modal (3 hours) **File:** `packages/noodl-editor/src/editor/src/views/panels/schemamanager/CreateTableModal.tsx` ```typescript import React, { useState } from 'react'; import { ipcRenderer } from 'electron'; import { Modal } from '@noodl-core-ui/components/modal'; import { TextInput } from '@noodl-core-ui/components/inputs/TextInput'; import { PrimaryButton, SecondaryButton } from '@noodl-core-ui/components/buttons'; import styles from './CreateTableModal.module.css'; interface CreateTableModalProps { backendId: string; onClose: () => void; onSuccess: () => void; } export function CreateTableModal({ backendId, onClose, onSuccess }: CreateTableModalProps) { const [tableName, setTableName] = useState(''); const [columns, setColumns] = useState([ { name: 'name', type: 'String', required: true } ]); const [creating, setCreating] = useState(false); const [error, setError] = useState(null); async function handleCreate() { // Validation if (!tableName.trim()) { setError('Table name is required'); return; } if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(tableName)) { setError('Table name must start with a letter and contain only letters, numbers, and underscores'); return; } if (columns.length === 0) { setError('At least one column is required'); return; } setCreating(true); setError(null); try { await ipcRenderer.invoke('backend:createTable', backendId, { name: tableName, columns: columns }); onSuccess(); onClose(); } catch (err: any) { setError(err.message || 'Failed to create table'); } finally { setCreating(false); } } function handleAddColumn() { setColumns([...columns, { name: '', type: 'String', required: false }]); } function handleRemoveColumn(index: number) { setColumns(columns.filter((_, i) => i !== index)); } function handleColumnChange(index: number, field: string, value: any) { const newColumns = [...columns]; newColumns[index] = { ...newColumns[index], [field]: value }; setColumns(newColumns); } return (
Use lowercase with underscores (e.g., "blog_posts")

Initial Columns

{columns.map((col, index) => ( handleColumnChange(index, field, value)} onRemove={() => handleRemoveColumn(index)} canRemove={columns.length > 1} /> ))}
Note: objectId, createdAt, and updatedAt are added automatically
{error && (
{error}
)}
); } ``` **File:** `packages/noodl-editor/src/editor/src/views/panels/schemamanager/ColumnEditor.tsx` ```typescript import React from 'react'; import { TextInput } from '@noodl-core-ui/components/inputs/TextInput'; import { Select } from '@noodl-core-ui/components/inputs/Select'; import { Checkbox } from '@noodl-core-ui/components/inputs/Checkbox'; import styles from './ColumnEditor.module.css'; const FIELD_TYPES = [ { value: 'String', label: 'String' }, { value: 'Number', label: 'Number' }, { value: 'Boolean', label: 'Boolean' }, { value: 'Date', label: 'Date' }, { value: 'Object', label: 'Object' }, { value: 'Array', label: 'Array' }, { value: 'Pointer', label: 'Pointer' }, { value: 'Relation', label: 'Relation' }, ]; interface ColumnEditorProps { column: ColumnDefinition; onChange: (field: string, value: any) => void; onRemove: () => void; canRemove: boolean; } export function ColumnEditor({ column, onChange, onRemove, canRemove }: ColumnEditorProps) { return (
onChange('name', value)} placeholder="field_name" />
) : ( <>
{result}
)}
); } ``` --- ## Files to Create ``` packages/noodl-editor/src/editor/src/views/panels/schemamanager/ ├── SchemaPanel.tsx # Main schema browser ├── SchemaPanel.module.css ├── TableRow.tsx # Individual table display ├── TableRow.module.css ├── CreateTableModal.tsx # New table creation ├── CreateTableModal.module.css ├── SchemaEditor.tsx # Edit existing table schema ├── SchemaEditor.module.css ├── ColumnEditor.tsx # Column configuration UI ├── ColumnEditor.module.css ├── ExportSchemaDialog.tsx # Export to SQL ├── ExportSchemaDialog.module.css └── index.ts # Public exports packages/noodl-editor/src/main/src/ipc/ └── backend-schema-handlers.ts # IPC handlers for schema operations ``` ## Files to Modify ``` packages/noodl-editor/src/editor/src/views/panels/BackendServicesPanel/BackendCard.tsx - Add "Manage Schema" button - Add "Browse Data" button packages/noodl-editor/src/main/src/ipc/index.ts - Register backend-schema-handlers packages/noodl-editor/src/editor/src/views/panels/index.ts - Export SchemaPanel for use in other panels ``` --- ## Testing Checklist ### Schema Viewing - [ ] Schema panel opens for running backend - [ ] All tables displayed with accurate counts - [ ] Table expand/collapse works - [ ] Column details show correct types - [ ] Record counts load asynchronously - [ ] Empty state shows when no tables exist ### Table Creation - [ ] Create table modal opens - [ ] Table name validation works - [ ] Can add multiple initial columns - [ ] Column removal works - [ ] Type dropdown shows all types - [ ] Required checkbox toggles - [ ] Pointer/Relation shows target selector - [ ] Table created successfully - [ ] Schema refreshes after creation ### Schema Editing - [ ] Schema editor opens for existing table - [ ] Existing columns displayed (read-only) - [ ] Can add new columns - [ ] Column validation works - [ ] Duplicate column names rejected - [ ] Columns saved successfully - [ ] Schema refreshes after save ### Schema Export - [ ] Export dialog opens - [ ] Format selector shows all options - [ ] PostgreSQL export generates valid SQL - [ ] MySQL export generates valid SQL - [ ] SQLite export generates valid SQL - [ ] Supabase export includes RLS policies - [ ] Copy to clipboard works - [ ] Download file works ### Integration - [ ] "Manage Schema" button disabled when backend stopped - [ ] Schema panel only accessible for running backends - [ ] Schema changes reflected in data browser - [ ] Schema changes reflected in node property dropdowns ### Edge Cases - [ ] Schema panel handles backend with no tables - [ ] Create table handles name conflicts - [ ] Schema editor handles invalid column types - [ ] Export handles large schemas (100+ tables) - [ ] UI handles backend disconnect gracefully --- ## Success Criteria 1. Users can view all tables and columns in their local backend 2. Users can create new tables with initial columns 3. Users can add columns to existing tables 4. Users can export schema to PostgreSQL, MySQL, or Supabase 5. Schema changes are immediately reflected in the UI 6. All operations properly delegate to existing SchemaManager 7. UI follows Nodegx design patterns and component standards 8. Performance: Schema loads in <500ms for 50 tables 9. Zero reimplementation of backend logic (only UI layer) --- ## Dependencies **Requires:** - TASK-007A (LocalSQL Adapter with SchemaManager) - TASK-007B (Backend Server with IPC) - TASK-007C (Backend Services Panel) **Blocked by:** None **Blocks:** - TASK-007I (Data Browser) - needs schema info for table selection - Phase 3 AI features - schema used for AI-powered suggestions --- ## Estimated Session Breakdown | Session | Focus | Hours | |---------|-------|-------| | 1 | SchemaPanel + TableRow components | 4 | | 2 | CreateTableModal + ColumnEditor | 3 | | 3 | SchemaEditor component | 4 | | 4 | IPC handlers + integration | 2 | | 5 | ExportSchemaDialog + polish | 3 | | 6 | Testing + bug fixes | 4 | | **Total** | | **20** | --- ## Future Enhancements (Phase 3) These features are **out of scope for MVP** but should be considered for Phase 3: 1. **Visual Relationship Diagram** - Canvas view showing table relationships 2. **Schema Migrations UI** - Track and apply schema changes over time 3. **AI Schema Suggestions** - Claude suggests optimal schema based on description 4. **Schema Versioning** - Integrate with git for schema history 5. **Column Removal** - Safe column deletion with data migration 6. **Index Management** - UI for creating/managing database indexes 7. **Virtual Fields** - Define computed columns using Nodegx expressions 8. **Schema Templates** - Pre-built schemas for common use cases (users, posts, products) 9. **Validation Rules UI** - Visual editor for field validation 10. **Schema Diff** - Compare schemas between dev/staging/prod --- ## Notes - This task focuses **only** on schema management UI - Data browsing/editing is covered in TASK-007I - All backend logic already exists in SchemaManager (TASK-007A) - Reuse existing Nodegx UI components wherever possible - Follow Storybook patterns from noodl-core-ui - Schema panel should feel like a natural extension of Backend Services panel - Export feature enables migration path to production databases