Finished prototype local backends and expression editor

This commit is contained in:
Richard Osborne
2026-01-16 12:00:31 +01:00
parent 94c870e5d7
commit 32a0a0885f
48 changed files with 8513 additions and 108 deletions

View File

@@ -10,6 +10,95 @@ These fundamental patterns apply across ALL Noodl development. Understanding the
---
## 📊 Property Expression Runtime Context (Jan 16, 2026)
### The Context Trap: Why evaluateExpression() Needs undefined, Not this.context
**Context**: TASK-006 Expressions Overhaul - Property expressions in the properties panel weren't evaluating. Error: "scope.get is not a function".
**The Problem**: The `evaluateExpression()` function in `expression-evaluator.js` expects either `undefined` (to use global Model) or a Model scope object. Passing `this.context` (the runtime node context with editorConnection, styles, etc.) caused the error because it lacks a `.get()` method.
**The Broken Pattern**:
```javascript
// ❌ WRONG - this.context is NOT a Model scope
Node.prototype._evaluateExpressionParameter = function (paramValue, portName) {
const compiled = compileExpression(paramValue.expression);
const result = evaluateExpression(compiled, this.context); // ☠️ scope.get is not a function
};
```
**The Correct Pattern**:
```javascript
// ✅ RIGHT - Pass undefined to use global Model
Node.prototype._evaluateExpressionParameter = function (paramValue, portName) {
const compiled = compileExpression(paramValue.expression);
const result = evaluateExpression(compiled, undefined); // ✅ Uses global Model
};
```
**Why This Works**:
The expression-evaluator's `createNoodlContext()` function does:
```javascript
const scope = modelScope || Model; // Falls back to global Model
const variablesModel = scope.get('--ndl--global-variables');
```
When `undefined` is passed, it uses the global `Model` which has the `.get()` method. The runtime's `this.context` is a completely different object containing `editorConnection`, `styles`, etc.
**Reactive Expression Subscriptions**:
To make expressions update when Variables change, the expression-evaluator already has:
- `detectDependencies(expression)` - finds what Variables/Objects/Arrays are referenced
- `subscribeToChanges(dependencies, callback)` - subscribes to Model changes
Wire these up in `_evaluateExpressionParameter`:
```javascript
// Set up reactive subscription
if (!this._expressionSubscriptions[portName]) {
const dependencies = detectDependencies(paramValue.expression);
if (dependencies.variables.length > 0) {
this._expressionSubscriptions[portName] = subscribeToChanges(
dependencies,
function () {
if (this._deleted) return;
this.queueInput(portName, paramValue); // Re-evaluate
}.bind(this)
);
}
}
```
**Critical Rules**:
1. **NEVER** pass `this.context` to `evaluateExpression()` - it's not a Model scope
2. **ALWAYS** pass `undefined` to use the global Model for Variables/Objects/Arrays
3. **Clean up subscriptions** in `_onNodeDeleted()` to prevent memory leaks
4. **Track subscriptions by port name** to avoid duplicate listeners
**Applies To**:
- Property panel expression fields
- Any runtime expression evaluation
- Future expression support in other property types
**Time Saved**: This pattern prevents 1-2 hours debugging "scope.get is not a function" errors.
**Location**:
- Fixed in: `packages/noodl-runtime/src/node.js`
- Expression evaluator: `packages/noodl-runtime/src/expression-evaluator.js`
- Task: Phase 3 TASK-006 Expressions Overhaul
**Keywords**: expression, evaluateExpression, this.context, Model scope, scope.get, Variables, reactive, subscribeToChanges, detectDependencies
---
## 🔴 Editor/Runtime Window Separation (Jan 2026)
### The Invisible Boundary: Why Editor Methods Don't Exist in Runtime

View File

@@ -0,0 +1,511 @@
# Panel & Modal UI Style Guide
This guide documents the visual patterns used in OpenNoodl's editor panels and modals. **Always follow these patterns when creating new UI components.**
---
## Core Principles
### 1. Professional, Not Playful
- **NO emojis** in UI labels, buttons, or headers
- **NO decorative icons** unless they serve a functional purpose
- Clean, minimal aesthetic that respects the user's intelligence
- Think "developer tool" not "consumer app"
### 2. Consistent Visual Language
- Use design tokens (CSS variables) for ALL colors
- Consistent spacing using the spacing system (4px base unit)
- Typography hierarchy using the Text component types
- All interactive elements must have hover/active states
### 3. Dark Theme First
- Design for dark backgrounds (`--theme-color-bg-2`, `--theme-color-bg-3`)
- Ensure sufficient contrast with light text
- Colored elements should be muted, not neon
---
## Panel Structure
### Standard Panel Layout
```tsx
<div className={css.Root}>
{/* Header with title and close button */}
<div className={css.Header}>
<HStack hasSpacing>
<Icon icon={IconName.Something} size={IconSize.Small} />
<VStack>
<Text textType={TextType.DefaultContrast}>Panel Title</Text>
<Text textType={TextType.Shy} style={{ fontSize: '11px' }}>
Subtitle or context
</Text>
</VStack>
</HStack>
<IconButton icon={IconName.Close} onClick={onClose} />
</div>
{/* Toolbar with actions */}
<div className={css.Toolbar}>{/* Filters, search, action buttons */}</div>
{/* Content area (scrollable) */}
<div className={css.Content}>{/* Main panel content */}</div>
{/* Footer (optional - pagination, status) */}
<div className={css.Footer}>{/* Page controls, counts */}</div>
</div>
```
### Panel CSS Pattern
```scss
.Root {
display: flex;
flex-direction: column;
height: 100%;
background-color: var(--theme-color-bg-2);
color: var(--theme-color-fg-default);
}
.Header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid var(--theme-color-bg-3);
flex-shrink: 0;
}
.Toolbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 16px;
background-color: var(--theme-color-bg-3);
gap: 8px;
flex-shrink: 0;
}
.Content {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.Footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 16px;
border-top: 1px solid var(--theme-color-bg-3);
background-color: var(--theme-color-bg-2);
flex-shrink: 0;
}
```
---
## Modal Structure
### Standard Modal Layout
```tsx
<div className={css.Overlay}>
<div className={css.Modal}>
{/* Header */}
<div className={css.Header}>
<Text textType={TextType.Proud}>Modal Title</Text>
<IconButton icon={IconName.Close} onClick={onClose} />
</div>
{/* Body */}
<div className={css.Body}>{/* Form fields, content */}</div>
{/* Footer with actions */}
<div className={css.Footer}>
<PrimaryButton label="Cancel" variant={PrimaryButtonVariant.Muted} onClick={onClose} />
<PrimaryButton label="Create" variant={PrimaryButtonVariant.Cta} onClick={onSubmit} />
</div>
</div>
</div>
```
### Modal CSS Pattern
```scss
.Overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.Modal {
background-color: var(--theme-color-bg-2);
border-radius: 8px;
width: 480px;
max-width: 90vw;
max-height: 80vh;
display: flex;
flex-direction: column;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
.Header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid var(--theme-color-bg-3);
}
.Body {
padding: 20px;
overflow-y: auto;
flex: 1;
}
.Footer {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 16px 20px;
border-top: 1px solid var(--theme-color-bg-3);
}
```
---
## Form Elements
### Text Inputs
```scss
.Input {
width: 100%;
padding: 8px 12px;
border: 1px solid var(--theme-color-bg-3);
border-radius: 4px;
background-color: var(--theme-color-bg-1);
color: var(--theme-color-fg-default);
font-size: 13px;
font-family: inherit;
&:focus {
outline: none;
border-color: var(--theme-color-primary);
}
&::placeholder {
color: var(--theme-color-fg-default-shy);
}
}
```
### Select Dropdowns
```scss
.Select {
padding: 8px 12px;
border: 1px solid var(--theme-color-bg-3);
border-radius: 4px;
background-color: var(--theme-color-bg-1);
color: var(--theme-color-fg-default);
font-size: 13px;
min-width: 120px;
&:focus {
outline: none;
border-color: var(--theme-color-primary);
}
}
```
### Form Groups
```scss
.FormGroup {
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
}
.Label {
display: block;
margin-bottom: 6px;
font-size: 12px;
font-weight: 500;
color: var(--theme-color-fg-default);
}
.HelpText {
margin-top: 4px;
font-size: 11px;
color: var(--theme-color-fg-default-shy);
}
```
---
## Data Tables & Grids
### Table Pattern
```scss
.Grid {
width: 100%;
border-collapse: collapse;
font-size: 13px;
th {
text-align: left;
padding: 10px 12px;
font-weight: 500;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--theme-color-fg-default-shy);
background-color: var(--theme-color-bg-3);
border-bottom: 1px solid var(--theme-color-bg-3);
position: sticky;
top: 0;
z-index: 1;
}
td {
padding: 8px 12px;
border-bottom: 1px solid var(--theme-color-bg-3);
color: var(--theme-color-fg-default);
}
tr:hover td {
background-color: var(--theme-color-bg-3);
}
}
```
### Type Badges
For showing data types, use subtle colored badges:
```scss
.TypeBadge {
display: inline-block;
padding: 2px 6px;
border-radius: 3px;
font-size: 10px;
font-weight: 500;
color: white;
}
// Use semantic colors, not hardcoded
.TypeString {
background-color: var(--theme-color-primary);
}
.TypeNumber {
background-color: var(--theme-color-success);
}
.TypeBoolean {
background-color: var(--theme-color-notice);
}
.TypeDate {
background-color: #8b5cf6;
} // Purple - no token available
.TypePointer {
background-color: var(--theme-color-danger);
}
```
---
## Expandable Rows
For tree-like or expandable content:
```scss
.ExpandableRow {
border: 1px solid var(--theme-color-bg-3);
border-radius: 6px;
margin-bottom: 8px;
overflow: hidden;
background-color: var(--theme-color-bg-2);
&[data-expanded='true'] {
border-color: var(--theme-color-primary);
}
}
.RowHeader {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
cursor: pointer;
transition: background-color 0.15s;
&:hover {
background-color: var(--theme-color-bg-3);
}
}
.RowContent {
padding: 0 16px 16px;
background-color: var(--theme-color-bg-1);
border-top: 1px solid var(--theme-color-bg-3);
}
.ExpandIcon {
transition: transform 0.2s;
color: var(--theme-color-fg-default-shy);
}
```
---
## Empty States
When there's no content to show:
```scss
.EmptyState {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 48px 24px;
text-align: center;
color: var(--theme-color-fg-default-shy);
.EmptyIcon {
font-size: 48px;
margin-bottom: 16px;
opacity: 0.5;
}
.EmptyText {
margin-bottom: 8px;
}
}
```
---
## Loading & Error States
### Loading
```scss
.Loading {
display: flex;
align-items: center;
justify-content: center;
padding: 48px;
color: var(--theme-color-fg-default-shy);
}
```
### Error
```scss
.Error {
padding: 12px 16px;
background-color: rgba(239, 68, 68, 0.1);
border: 1px solid var(--theme-color-danger);
border-radius: 4px;
color: var(--theme-color-danger);
font-size: 13px;
}
```
---
## Button Patterns
### Use PrimaryButton Variants Correctly
| Variant | Use For |
| -------- | ------------------------------------------ |
| `Cta` | Primary action (Create, Save, Submit) |
| `Muted` | Secondary action (Cancel, Close, Refresh) |
| `Ghost` | Tertiary action (Edit, View, minor action) |
| `Danger` | Destructive action (Delete) |
### Button Sizing
- `Small` - In toolbars, table rows, compact spaces
- `Medium` - Modal footers, standalone actions
- `Large` - Rarely used, hero actions only
---
## Spacing System
Use consistent spacing based on 4px unit:
| Token | Value | Use For |
| ----- | ----- | ------------------------ |
| `xs` | 4px | Tight spacing, icon gaps |
| `sm` | 8px | Related elements |
| `md` | 12px | Standard padding |
| `lg` | 16px | Section padding |
| `xl` | 24px | Large gaps |
| `xxl` | 32px | Major sections |
---
## Typography
### Use Text Component Types
| Type | Use For |
| ----------------- | ------------------------------- |
| `Proud` | Panel titles, modal headers |
| `DefaultContrast` | Primary content, item names |
| `Default` | Body text, descriptions |
| `Shy` | Secondary text, hints, metadata |
### Font Sizes
- Headers: 14-16px
- Body: 13px
- Labels: 12px
- Small text: 11px
- Badges: 10px
---
## Don'ts
**Don't use emojis** in buttons or labels
**Don't use hardcoded colors** - always use CSS variables
**Don't use bright/neon colors** - keep it muted
**Don't use decorative icons** that don't convey meaning
**Don't use rounded corners > 8px** - keep it subtle
**Don't use shadows > 0.4 opacity** - stay subtle
**Don't use animation duration > 200ms** - keep it snappy
**Don't mix different styling approaches** - be consistent
---
## Reference Components
For working examples, see:
- `packages/noodl-editor/src/editor/src/views/panels/schemamanager/`
- `packages/noodl-editor/src/editor/src/views/panels/databrowser/`
- `packages/noodl-editor/src/editor/src/views/panels/BackendServicesPanel/`
---
_Last Updated: January 2026_

View File

@@ -0,0 +1,219 @@
# Phase 2B: Inline Property Expressions - COMPLETE ✅
**Started:** 2026-01-10
**Completed:** 2026-01-16
**Status:** ✅ COMPLETE
---
## Summary
Inline property expressions are now fully functional! Users can set property values using JavaScript expressions that reference Noodl.Variables, Noodl.Objects, and Noodl.Arrays. Expressions update reactively when their dependencies change.
---
## ✅ What Was Implemented
### 1. ExpressionInput Component - COMPLETE ✅
**Files Created:**
- `packages/noodl-core-ui/src/components/property-panel/ExpressionInput/ExpressionInput.tsx`
- `packages/noodl-core-ui/src/components/property-panel/ExpressionInput/ExpressionInput.module.scss`
**Features:**
- Monospace input field with "fx" badge
- Expression mode toggle
- Expand button to open full editor modal
- Error state display
---
### 2. ExpressionEditorModal - COMPLETE ✅
**Files Created:**
- `packages/noodl-editor/src/editor/src/views/panels/propertyeditor/ExpressionEditorModal/ExpressionEditorModal.tsx`
- `packages/noodl-editor/src/editor/src/views/panels/propertyeditor/ExpressionEditorModal/ExpressionEditorModal.module.scss`
- `packages/noodl-editor/src/editor/src/views/panels/propertyeditor/ExpressionEditorModal/index.ts`
**Features:**
- Full-screen modal for editing complex expressions
- Uses JavaScriptEditor (CodeMirror) for syntax highlighting
- Help documentation showing available globals (Noodl.Variables, etc.)
- Apply/Cancel buttons
---
### 3. PropertyPanelInput Integration - COMPLETE ✅
**Files Modified:**
- `packages/noodl-core-ui/src/components/property-panel/PropertyPanelInput/PropertyPanelInput.tsx`
**Features:**
- Added expression-related props (supportsExpression, expressionMode, expression, etc.)
- Conditional rendering: expression input vs fixed input
- Mode change handlers
---
### 4. PropertyPanelInputWithExpressionModal Wrapper - COMPLETE ✅
**Files Created:**
- `packages/noodl-editor/src/editor/src/views/panels/propertyeditor/components/PropertyPanelInputWithExpressionModal.tsx`
**Features:**
- Combines PropertyPanelInput with ExpressionEditorModal
- Manages modal open/close state
- Handles expression expand functionality
---
### 5. BasicType Property Editor Wiring - COMPLETE ✅
**Files Modified:**
- `packages/noodl-editor/src/editor/src/views/panels/propertyeditor/DataTypes/BasicType.ts`
**Features:**
- Uses React for rendering via createRoot
- Supports expression mode toggle
- Integrates with ExpressionParameter model
- Uses ParameterValueResolver for safe value display
---
### 6. Runtime Reactive Subscriptions - COMPLETE ✅
**Files Modified:**
- `packages/noodl-runtime/src/node.js`
**Features:**
- `_evaluateExpressionParameter()` now sets up reactive subscriptions
- Uses `detectDependencies()` to find referenced Variables/Objects/Arrays
- Uses `subscribeToChanges()` to listen for dependency updates
- Re-queues input when dependencies change
- Cleanup in `_onNodeDeleted()`
---
## 🎯 How It Works
### User Experience
1. Select a node with text/number properties
2. Click the "fx" button to toggle expression mode
3. Enter an expression like `Noodl.Variables.userName`
4. The value updates automatically when the variable changes
5. Click the expand button for a full code editor modal
### Supported Expressions
```javascript
// Simple variable access
Noodl.Variables.userName;
// Math operations
Noodl.Variables.count * 2 + 1;
// Ternary conditionals
Noodl.Variables.isLoggedIn ? 'Logout' : 'Login';
// String concatenation
'Hello, ' + Noodl.Variables.userName + '!';
// Math helpers
min(Noodl.Variables.price, 100);
max(10, Noodl.Variables.quantity);
round(Noodl.Variables.total);
// Object/Array access
Noodl.Objects['user'].name;
Noodl.Arrays['items'].length;
```
---
## 📁 Files Changed
### Created
- `packages/noodl-core-ui/src/components/property-panel/ExpressionInput/ExpressionInput.tsx`
- `packages/noodl-core-ui/src/components/property-panel/ExpressionInput/ExpressionInput.module.scss`
- `packages/noodl-editor/src/editor/src/views/panels/propertyeditor/ExpressionEditorModal/ExpressionEditorModal.tsx`
- `packages/noodl-editor/src/editor/src/views/panels/propertyeditor/ExpressionEditorModal/ExpressionEditorModal.module.scss`
- `packages/noodl-editor/src/editor/src/views/panels/propertyeditor/ExpressionEditorModal/index.ts`
- `packages/noodl-editor/src/editor/src/views/panels/propertyeditor/components/PropertyPanelInputWithExpressionModal.tsx`
### Modified
- `packages/noodl-core-ui/src/components/property-panel/PropertyPanelInput/PropertyPanelInput.tsx`
- `packages/noodl-editor/src/editor/src/views/panels/propertyeditor/DataTypes/BasicType.ts`
- `packages/noodl-runtime/src/node.js` (added reactive subscriptions)
---
## 🐛 Bug Fixes (2026-01-16)
### Modal Showing Stale Expression
**Problem:** When editing an expression in the inline input field and then opening the modal, the modal showed the old expression text instead of the current one.
**Root Cause:** `BasicType.ts` was not re-rendering after `onExpressionChange`, so the modal component never received the updated expression prop.
**Fix:** Added `setTimeout(() => this.renderReact(), 0)` at the end of `onExpressionChange` in `BasicType.ts` to ensure the React tree updates with the new expression value.
### Variable Changes Not Updating Node Values
**Problem:** When using an expression like `Noodl.Variables.label_text`, changing the variable value didn't update the node's property until the expression was manually saved again.
**Root Cause:** The subscription callback in `node.js` captured the old `paramValue` object in a closure. When the subscription fired, it re-queued the old expression, not the current one stored in `_inputValues`.
**Fix:** Updated subscription management in `_evaluateExpressionParameter()`:
- Subscriptions now track both the unsubscribe function AND the expression string
- When expression changes, old subscription is unsubscribed before creating a new one
- The callback now reads from `this._inputValues[portName]` (current value) instead of using a closure
**Files Modified:**
- `packages/noodl-editor/src/editor/src/views/panels/propertyeditor/DataTypes/BasicType.ts`
- `packages/noodl-runtime/src/node.js`
---
## 🎓 Key Learnings
### Context Issue
The `evaluateExpression()` function in expression-evaluator.js expects either `undefined` or a Model scope as the second parameter. Passing `this.context` (the runtime context with editorConnection, styles, etc.) caused "scope.get is not a function" errors. **Solution:** Pass `undefined` to use the global Model.
### Reactive Updates
The expression-evaluator module already had `detectDependencies()` and `subscribeToChanges()` functions. We just needed to wire them into `_evaluateExpressionParameter()` in node.js to enable reactive updates.
### Value Display
Expression parameters are objects (`{ mode: 'expression', expression: '...', fallback: ... }`). When displaying the value in the properties panel, we need to extract the fallback value, not display the object. The `ParameterValueResolver.toString()` helper handles this.
---
## 🚀 Future Enhancements
1. **Canvas rendering** - Show expression indicator on node ports (TASK-006B)
2. **More property types** - Extend to color, enum, and other types
3. **Expression autocomplete** - IntelliSense for Noodl.Variables names
4. **Expression validation** - Real-time syntax checking
---
**Last Updated:** 2026-01-16

View File

@@ -0,0 +1,107 @@
# TASK-007H: Schema Manager UI - COMPLETE
## Summary
Implemented a Schema Manager UI panel for viewing and managing database schemas in local SQLite backends.
## Files Created
### Schema Manager Panel Components
- `packages/noodl-editor/src/editor/src/views/panels/schemamanager/SchemaPanel.tsx` - Main panel showing tables list
- `packages/noodl-editor/src/editor/src/views/panels/schemamanager/SchemaPanel.module.scss` - Panel styles
- `packages/noodl-editor/src/editor/src/views/panels/schemamanager/TableRow.tsx` - Expandable table row with columns
- `packages/noodl-editor/src/editor/src/views/panels/schemamanager/TableRow.module.scss` - Table row styles
- `packages/noodl-editor/src/editor/src/views/panels/schemamanager/index.ts` - Module exports
## Files Modified
### BackendManager.js
Added IPC handlers for schema operations:
- `backend:getSchema` - Get full schema for a backend
- `backend:getTableSchema` - Get single table schema
- `backend:getRecordCount` - Get record count for a table
- `backend:createTable` - Create a new table
- `backend:addColumn` - Add column to existing table
### LocalBackendCard.tsx
- Added "Schema" button (shows when backend is running)
- Added schema panel overlay state
- Integrated SchemaPanel component
### LocalBackendCard.module.scss
- Added `.SchemaPanelOverlay` styles for full-screen modal:
- Uses React Portal (`createPortal`) to render at `document.body`
- `z-index: 9999` ensures overlay above all UI elements
- Dark backdrop (`rgba(0, 0, 0, 0.85)`)
- Centered modal with max-width 900px
- Proper border-radius, shadow, and background styling
## Features Implemented
### SchemaPanel
- Shows list of all tables in the backend
- Displays table count in header
- Refresh button to reload schema
- Empty state with "Create First Table" prompt (placeholder)
- Close button to dismiss panel
### TableRow
- Collapsible rows with expand/collapse toggle
- Table icon with "T" badge
- Shows field count and record count
- Expanded view shows:
- System columns (objectId, createdAt, updatedAt)
- User-defined columns with type badges
- Required indicator
- Default value display
- Type badges with color-coded data types:
- String (primary)
- Number (success/green)
- Boolean (notice/yellow)
- Date (purple)
- Object (pink)
- Array (indigo)
- Pointer/Relation (red)
- GeoPoint (teal)
- File (orange)
## Usage
1. Create and start a local backend
2. Click "Schema" button on the backend card
3. View tables and expand rows to see columns
4. Click "Refresh" to reload schema
5. Click "Close" to dismiss panel
## Future Enhancements (Deferred)
The following were scoped but not implemented in this initial version:
- CreateTableModal - For creating new tables
- ColumnEditor - For editing column definitions
- SchemaEditor - Full table editor
- ExportDialog - Export schema to SQL formats (handlers exist in BackendManager)
These can be added incrementally as needed.
## Verification
The implementation follows project patterns:
- ✅ Uses theme tokens (no hardcoded colors)
- ✅ Uses IPC pattern from useLocalBackends
- ✅ Proper TypeScript types
- ✅ JSDoc documentation
- ✅ Module SCSS with CSS modules
- ✅ Follows existing component structure
---
Completed: 15/01/2026

View File

@@ -0,0 +1,205 @@
# TASK-007I: Data Browser Panel - COMPLETE
## Summary
Implemented a full-featured data browser panel for viewing and editing records in local backend SQLite tables, providing a spreadsheet-like interface with inline editing, search, pagination, and CRUD operations.
## Status: ✅ Complete
## Date: 2026-01-15
---
## What Was Implemented
### 1. Backend IPC Handlers (BackendManager.js)
Added four new IPC handlers for data operations:
- **`backend:queryRecords`** - Query records with pagination, sorting, search filters
- **`backend:createRecord`** - Create a new record in a table
- **`backend:saveRecord`** - Update an existing record (inline edit)
- **`backend:deleteRecord`** - Delete a single record
These handlers wrap the existing LocalSQLAdapter CRUD methods (query, create, save, delete) and expose them to the renderer process.
### 2. DataBrowser Component (`panels/databrowser/`)
Created a complete data browser panel with the following components:
#### DataBrowser.tsx (Main Component)
- Table selector dropdown
- Search input with cross-field text search
- Pagination with page navigation (50 records per page)
- Bulk selection and delete operations
- CSV export functionality
- Loading states and error handling
- New record modal trigger
#### DataGrid.tsx (Spreadsheet View)
- Column headers with type badges (String, Number, Boolean, Date, Object, Array)
- System columns (objectId, createdAt, updatedAt) displayed as read-only
- Inline editing - click any editable cell to edit
- Row selection with checkbox
- Select all/none toggle
- Delete action per row
- Scrollable with sticky header
#### CellEditor.tsx (Inline Editor)
- Type-aware input controls:
- **String**: Text input
- **Number**: Number input with step="any"
- **Boolean**: Checkbox with immediate save
- **Date**: datetime-local input
- **Object/Array**: Multi-line JSON editor with Save/Cancel buttons
- Auto-focus and select on mount
- Enter to save, Escape to cancel (for simple types)
- Validation for numbers and JSON parsing
- Error display for invalid input
#### NewRecordModal.tsx (Create Record Form)
- Form fields generated from column schema
- Type-aware input controls for each field type
- Required field validation
- Default values based on column defaults or type defaults
- Success callback to refresh data grid
### 3. Styling
All components use theme CSS variables per UI-STYLING-GUIDE.md:
- `--theme-color-bg-*` for backgrounds
- `--theme-color-fg-*` for text
- `--theme-color-border-*` for borders
- `--theme-color-primary*` for accents
- `--theme-color-success/danger/notice` for status colors
### 4. Integration
Added "Data" button to LocalBackendCard (visible when backend is running):
- Opens DataBrowser in a full-screen portal overlay
- Same pattern as Schema panel
---
## Files Created/Modified
### Created Files:
```
packages/noodl-editor/src/editor/src/views/panels/databrowser/
├── DataBrowser.tsx - Main data browser component
├── DataBrowser.module.scss - Styles for DataBrowser
├── DataGrid.tsx - Spreadsheet grid component
├── DataGrid.module.scss - Styles for DataGrid
├── CellEditor.tsx - Inline cell editor
├── CellEditor.module.scss - Styles for CellEditor
├── NewRecordModal.tsx - Create record modal
├── NewRecordModal.module.scss - Styles for modal
└── index.ts - Exports
```
### Modified Files:
```
packages/noodl-editor/src/main/src/local-backend/BackendManager.js
- Added queryRecords(), createRecord(), saveRecord(), deleteRecord() methods
- Added corresponding IPC handlers
packages/noodl-editor/src/editor/src/views/panels/BackendServicesPanel/LocalBackendCard/LocalBackendCard.tsx
- Added DataBrowser import
- Added showDataBrowser state
- Added "Data" button
- Added DataBrowser portal rendering
```
---
## How To Use
1. Start a local backend from the Backend Services panel
2. Click the "Data" button on the running backend card
3. Select a table from the dropdown (auto-selects first table if available)
4. Browse records with pagination
5. Click any editable cell to edit inline
6. Use "+ New Record" to add records
7. Use checkboxes for bulk selection and delete
8. Use "Export CSV" to download current view
---
## Technical Notes
### Query API
The query API supports:
```javascript
{
collection: string, // Table name
limit: number, // Max records (default 50)
skip: number, // Pagination offset
where: object, // Filter conditions ($or, contains, etc.)
sort: string[], // Sort order (e.g., ['-createdAt'])
count: boolean // Return total count for pagination
}
```
### Search Implementation
Search uses `$or` with `contains` operator across all String columns and objectId. The LocalSQLAdapter converts this to SQLite LIKE queries.
### Type Handling
- System fields (objectId, createdAt, updatedAt) are always read-only
- Boolean cells save immediately on checkbox toggle
- Object/Array cells require explicit Save button due to JSON editing
- Dates are stored as ISO8601 strings
---
## Future Improvements (Not in scope)
- Column sorting by clicking headers
- Filter by specific field values
- Relation/Pointer field expansion
- Inline record duplication
- Undo for delete operations
- Import CSV/JSON
---
## Verification Checklist
- [x] IPC handlers added to BackendManager.js
- [x] DataBrowser component created
- [x] DataGrid with inline editing
- [x] CellEditor with type-aware controls
- [x] NewRecordModal for creating records
- [x] CSV export functionality
- [x] Pagination working
- [x] Search functionality
- [x] Bulk delete operations
- [x] Integration with LocalBackendCard
- [x] Theme-compliant styling
- [x] No hardcoded colors
- [x] JSDoc comments added
---
## Related Tasks
- **TASK-007A**: Core SQLite adapter ✅
- **TASK-007B**: Workflow integration ✅
- **TASK-007C**: Schema management ✅
- **TASK-007D**: Backend lifecycle ✅
- **TASK-007E**: Express HTTP API ✅
- **TASK-007F**: Node integration ✅
- **TASK-007G**: UI components ✅
- **TASK-007H**: Schema Panel UI ✅
- **TASK-007I**: Data Browser ✅ (this task)

View File

@@ -0,0 +1,90 @@
# TASK-007J: Schema/Database Management UX - COMPLETE
## Status: ✅ COMPLETE (with Known Issues)
## Summary
Built Schema Manager and Data Browser UI panels with basic CRUD functionality. Uses `id` (UUID v4) as the primary key field to be compatible with Noodl frontend objects/arrays.
## What Was Built
### Schema Manager Panel
- **SchemaPanel** - Main panel for viewing/managing database schemas
- **TableRow** - Expandable row showing table name, columns, record count
- **CreateTableModal** - Modal for creating new tables with initial columns
- **AddColumnForm** - Form for adding columns (type dropdown, required checkbox)
### Data Browser Panel
- **DataBrowser** - Main data browsing UI with table selector, search, pagination
- **DataGrid** - Spreadsheet-style grid with inline editing
- **CellEditor** - Inline cell editor with type-aware input handling
- **NewRecordModal** - Modal for creating new records
### Backend Changes
- Changed primary key from `objectId` to `id` with UUID v4
- QueryBuilder uses `id` field for all operations
- LocalSQLAdapter generates RFC 4122 UUIDs
## Files Created/Modified
### New UI Components
- `packages/noodl-editor/src/editor/src/views/panels/schemamanager/`
- `SchemaPanel.tsx`, `SchemaPanel.module.scss`
- `TableRow.tsx`, `TableRow.module.scss`
- `CreateTableModal.tsx`, `CreateTableModal.module.scss`
- `AddColumnForm.tsx`, `AddColumnForm.module.scss`
- `index.ts`
- `packages/noodl-editor/src/editor/src/views/panels/databrowser/`
- `DataBrowser.tsx`, `DataBrowser.module.scss`
- `DataGrid.tsx`, `DataGrid.module.scss`
- `CellEditor.tsx`, `CellEditor.module.scss`
- `NewRecordModal.tsx`, `NewRecordModal.module.scss`
- `index.ts`
### Modified Backend Files
- `packages/noodl-runtime/src/api/adapters/local-sql/QueryBuilder.js`
- `packages/noodl-runtime/src/api/adapters/local-sql/LocalSQLAdapter.js`
## Known Issues (Future Task)
The following bugs need to be addressed in a follow-up task:
### Data Browser Bugs
1. **Object/Array fields don't work** - Can't type into object/array field editors
2. **Cell editor focus issues** - Sometimes loses focus unexpectedly
3. **Search functionality limited** - Only searches String fields
### Schema Manager Bugs
1. **Can't edit existing tables** - Edit button only expands row, no add/remove columns
2. **Can't delete tables** - No delete table functionality
### SQLite/Backend Bugs
1. **Real SQLite database doesn't work** - Falls back to in-memory mock
2. **better-sqlite3 import issues** - Electron compatibility problems
3. **Schema persistence** - Schema changes don't persist properly
4. **Query filtering** - Complex WHERE clauses may not work correctly
### UI/UX Issues
1. **Boolean toggle needs improvement** - Checkbox isn't very intuitive
2. **Date picker needed** - Currently just text input for dates
3. **Pointer/Relation fields** - No UI for selecting related records
4. **File upload** - No file upload/browse functionality
## Next Steps
See **TASK-007K-DRAFT.md** for bug fixes and improvements.
## Completion Date
January 15, 2026

View File

@@ -0,0 +1,260 @@
# TASK-007J: Schema & Data Creation UX
## Status: 📋 Draft
## Summary
Add the missing creation UX to the Schema and Data Browser panels - create tables, add/edit/delete columns, and create data records with proper type handling.
---
## Problem
The current implementation (007H + 007I) provides excellent **viewing** and **inline editing** capabilities, but is missing the ability to **create new structures from scratch**:
- Schema Panel shows tables but no way to create a new table
- No way to add/edit/delete columns in existing tables
- Data Browser has "+ New Record" but useless without tables
- Field types need proper UI for creation (default values, constraints)
---
## Dependencies
**Backend (Already Exists ✅):**
- `backend:createTable` - IPC handler in BackendManager.js
- `backend:addColumn` - IPC handler in BackendManager.js
- LocalSQLAdapter has all CRUD methods for schema changes
**UI Components (Already Exists ✅):**
- SchemaPanel - just needs buttons and modals wired up
- DataBrowser - just needs better empty state
---
## Implementation Plan
### Part 1: Create Table Modal
**New Component: `CreateTableModal.tsx`**
Features:
- Table name input (validated: alphanumeric, no spaces, unique)
- Initial columns list with ability to add/remove
- For each column:
- Name input
- Type dropdown (String, Number, Boolean, Date, Object, Array)
- Required checkbox
- Default value input (type-aware)
- "Create Table" button calls `backend:createTable`
**Supported Field Types:**
| Type | SQLite | Default Input |
|------|--------|---------------|
| String | TEXT | Text input |
| Number | REAL | Number input |
| Boolean | INTEGER | Checkbox |
| Date | TEXT (ISO) | Date picker |
| Object | TEXT (JSON) | JSON editor |
| Array | TEXT (JSON) | JSON editor |
**Integration:**
- Add `showCreateTableModal` state to SchemaPanel
- Wire up "+ New Table" button (already in header)
- Call `loadSchema()` on success
### Part 2: Add Column to Existing Table
**New Component: `AddColumnForm.tsx`**
Features:
- Inline form that appears in expanded table view
- Column name input
- Type dropdown
- Required checkbox
- Default value (required for non-nullable columns if table has data)
- "Add Column" button calls `backend:addColumn`
**Integration:**
- Add to TableRow component's expanded view
- Add "+ Add Column" button to expanded section
- Handles the case where existing records need default values
### Part 3: Edit Column (Rename/Change Type)
**Note:** SQLite has limited ALTER TABLE support. Only RENAME COLUMN is safe.
**Features:**
- Edit icon next to each column in expanded view
- Opens inline editor for column name
- Type change is **not supported** (would need table recreation)
- Shows tooltip: "Type cannot be changed after creation"
### Part 4: Delete Column
**New IPC Handler Needed:** `backend:deleteColumn`
**Note:** SQLite doesn't support DROP COLUMN directly - needs table recreation.
**Implementation options:**
1. **Not supported** - show tooltip "SQLite doesn't support column deletion"
2. **Recreate table** - expensive but possible:
- Create new table without column
- Copy data
- Drop old table
- Rename new table
**Recommendation:** Option 1 for MVP, Option 2 as enhancement
### Part 5: Delete Table
**New IPC Handler Needed:** `backend:deleteTable` (or use existing drop)
**Features:**
- Delete/trash icon on each table row
- Confirmation dialog: "Delete table {name}? This will permanently delete all data."
- Calls `backend:deleteTable`
### Part 6: Improved Empty States
**Schema Panel (when no tables):**
```
┌─────────────────────────────────────┐
│ 🗄️ No tables yet │
│ │
│ Create your first table to start │
│ storing data in your backend. │
│ │
│ [+ Create Table] │
└─────────────────────────────────────┘
```
**Data Browser (when no tables):**
```
┌─────────────────────────────────────┐
│ 📊 No tables in database │
│ │
│ Go to Schema panel to create │
│ your first table, then return │
│ here to browse and edit data. │
│ │
│ [Open Schema Panel] │
└─────────────────────────────────────┘
```
---
## Files to Create/Modify
### New Files:
```
panels/schemamanager/
├── CreateTableModal.tsx - Modal for creating new tables
├── CreateTableModal.module.scss
├── AddColumnForm.tsx - Inline form for adding columns
├── AddColumnForm.module.scss
├── ColumnEditor.tsx - Inline column name editor
└── ColumnEditor.module.scss
```
### Modified Files:
```
main/src/local-backend/BackendManager.js
- Add deleteColumn() if implementing column deletion
- Add deleteTable() IPC handler
panels/schemamanager/SchemaPanel.tsx
- Add showCreateTableModal state
- Wire up modal
panels/schemamanager/TableRow.tsx
- Add AddColumnForm integration
- Add column edit/delete actions
- Add table delete action
panels/databrowser/DataBrowser.tsx
- Improve empty state with link to Schema panel
```
---
## Estimation
| Part | Complexity | Time Est |
| --------------------- | ---------- | ----------- |
| CreateTableModal | Medium | 1-2 hrs |
| AddColumnForm | Medium | 1 hr |
| ColumnEditor (rename) | Simple | 30 min |
| Delete table | Simple | 30 min |
| Empty states | Simple | 15 min |
| Testing & polish | - | 1 hr |
| **Total** | - | **4-5 hrs** |
---
## Acceptance Criteria
- [ ] Can create a new table with name and columns
- [ ] Can add columns to existing tables
- [ ] Can rename columns
- [ ] Can delete tables (with confirmation)
- [ ] Field types have proper UI controls
- [ ] Empty states guide users to create structures
- [ ] All actions refresh the panel after success
- [ ] Error states for invalid names/constraints
- [ ] Theme-compliant styling (no hardcoded colors)
---
## Technical Notes
### Table Name Validation
```javascript
const isValidTableName = (name) => {
// Must start with letter, alphanumeric + underscore only
// Cannot be SQLite reserved words
return /^[a-zA-Z][a-zA-Z0-9_]*$/.test(name) && !SQLITE_RESERVED_WORDS.includes(name.toUpperCase());
};
```
### Default Values for Types
```javascript
const getDefaultForType = (type) =>
({
String: '',
Number: 0,
Boolean: false,
Date: new Date().toISOString(),
Object: {},
Array: []
}[type]);
```
### SQLite Limitations
- No DROP COLUMN (before SQLite 3.35.0)
- No ALTER COLUMN TYPE
- RENAME COLUMN supported (SQLite 3.25.0+)
---
## Related Tasks
- **007H**: Schema Panel (viewing) ✅
- **007I**: Data Browser (viewing/editing) ✅
- **007J**: Schema & Data Creation UX ← This task

View File

@@ -0,0 +1,120 @@
# TASK-007K: Local Backend Bug Fixes & Polish
## Status: 📋 DRAFT
## Overview
This task addresses the known bugs and UX issues from TASK-007J. The Schema Manager and Data Browser are functional but have rough edges that need polish.
## Priority 1: Critical Bugs
### 1.1 Object/Array Field Editor
**Problem:** Can't type into Object/Array field text areas in CellEditor.
**Files:** `CellEditor.tsx`
**Fix:** Review the textarea handling, may need to prevent event bubbling or fix focus management.
### 1.2 Real SQLite Database
**Problem:** Falls back to in-memory mock because better-sqlite3 import fails in Electron renderer.
**Files:** `LocalSQLAdapter.js`, `BackendManager.js`
**Fix Options:**
- Move SQLite operations to main process via IPC
- Use sql.js (pure JS SQLite) in renderer
- Configure better-sqlite3 for Electron properly
### 1.3 Schema Persistence
**Problem:** Schema changes (new tables, columns) don't persist across restarts.
**Files:** `SchemaManager.js`, `LocalBackendServer.js`
**Fix:** Ensure schema table is properly created and migrations are stored.
## Priority 2: Missing Features
### 2.1 Edit Existing Tables
**Problem:** No UI for adding/removing columns from existing tables.
**Files:** `SchemaPanel.tsx`, `TableRow.tsx`
**Add:**
- "Add Column" button in expanded table row
- Delete column button per column
- Confirmation for destructive actions
### 2.2 Delete Tables
**Problem:** No way to delete a table.
**Files:** `SchemaPanel.tsx`, `TableRow.tsx`
**Add:**
- Delete button in table row
- Confirmation dialog
- Backend `backend:deleteTable` IPC handler
### 2.3 Better Boolean Toggle
**Problem:** Checkbox not intuitive for boolean fields.
**Files:** `CellEditor.tsx`
**Add:** Toggle switch component instead of checkbox.
### 2.4 Date Picker
**Problem:** Text input for dates is error-prone.
**Files:** `CellEditor.tsx`
**Add:** Date picker component (can use core-ui DateInput if available).
## Priority 3: UX Improvements
### 3.1 Pointer/Relation Field Editor
Add dropdown to select from related records.
### 3.2 File Field Editor
Add file picker/upload UI.
### 3.3 Search All Fields
Extend search to Number, Date fields (not just String).
### 3.4 Keyboard Navigation
- Arrow keys to navigate grid
- Enter to edit cell
- Escape to cancel
- Tab to move between cells
## Estimated Effort
| Priority | Items | Effort |
| --------- | ----- | ------------- |
| P1 | 3 | 4-6 hrs |
| P2 | 4 | 3-4 hrs |
| P3 | 4 | 4-6 hrs |
| **Total** | | **11-16 hrs** |
## Dependencies
- Core UI components (Toggle, DatePicker)
- May need main process changes for SQLite
## Notes
- Consider splitting this into multiple sub-tasks if scope is too large
- SQLite issue may require significant architecture change
- Focus on P1 bugs first for usable MVP

View File

@@ -71,13 +71,15 @@
## Recent Updates
| Date | Update |
| ---------- | --------------------------------------------------------- |
| 2026-01-15 | TASK-007 Integrated Backend core infrastructure complete |
| 2026-01-07 | Corrected PROGRESS.md to reflect actual completion status |
| 2025-12-30 | TASK-002 bug fixes and system table support |
| 2025-12-29 | TASK-001 Backend Services Panel completed |
| 2025-12-29 | TASK-002 Data Nodes completed |
| Date | Update |
| ---------- | ----------------------------------------------------------- |
| 2026-01-15 | TASK-007J Schema Manager & Data Browser UX complete w/ bugs |
| 2026-01-15 | TASK-007K Draft created for bug fixes |
| 2026-01-15 | TASK-007 Integrated Backend core infrastructure complete |
| 2026-01-07 | Corrected PROGRESS.md to reflect actual completion status |
| 2025-12-30 | TASK-002 bug fixes and system table support |
| 2025-12-29 | TASK-001 Backend Services Panel completed |
| 2025-12-29 | TASK-002 Data Nodes completed |
---
@@ -103,7 +105,7 @@
### TASK-007: Integrated Local Backend ✅
Zero-config local SQLite backend system - infrastructure complete.
Zero-config local SQLite backend system - infrastructure complete with Schema Manager & Data Browser UI.
**Completed subtasks:**
@@ -111,6 +113,9 @@ Zero-config local SQLite backend system - infrastructure complete.
- TASK-007B: Local Backend Server (Express + IPC handlers)
- TASK-007C: WorkflowRunner (Cloud function execution)
- TASK-007D: Launcher UI (BackendServicesPanel integration)
- TASK-007H: Backend Manager IPC Handlers
- TASK-007I: Data Browser Panel (spreadsheet-style grid)
- TASK-007J: Schema Manager Panel (create tables, add columns)
**What's working:**
@@ -118,12 +123,16 @@ Zero-config local SQLite backend system - infrastructure complete.
- Start/stop backend servers via IPC
- REST API compatible with Parse Server
- Auto-schema (tables/columns created on first write)
- Schema Manager: create tables, add columns, view schema
- Data Browser: view records, inline editing, create/delete records
- UUID `id` field (compatible with Noodl frontend objects)
**Future work (separate task):**
**Known Issues (TASK-007K):**
- Schema viewer/editor panel for visual data management
- Data browser with grid view
- Migration/export tools (optional)
- Object/Array field editors don't work
- Real SQLite falls back to in-memory mock (better-sqlite3 issues)
- Can't edit/delete existing tables
- Cell editor focus issues
**Implementation Files:**
@@ -143,6 +152,20 @@ packages/noodl-editor/src/main/src/local-backend/
packages/noodl-editor/src/editor/src/views/panels/BackendServicesPanel/
├── hooks/useLocalBackends.ts
└── LocalBackendCard/LocalBackendCard.tsx
packages/noodl-editor/src/editor/src/views/panels/schemamanager/
├── SchemaPanel.tsx, SchemaPanel.module.scss
├── TableRow.tsx, TableRow.module.scss
├── CreateTableModal.tsx, CreateTableModal.module.scss
├── AddColumnForm.tsx, AddColumnForm.module.scss
└── index.ts
packages/noodl-editor/src/editor/src/views/panels/databrowser/
├── DataBrowser.tsx, DataBrowser.module.scss
├── DataGrid.tsx, DataGrid.module.scss
├── CellEditor.tsx, CellEditor.module.scss
├── NewRecordModal.tsx, NewRecordModal.module.scss
└── index.ts
```
---