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_