Files
OpenNoodl/dev-docs/tasks/phase-3-editor-ux-overhaul/TASK-007-app-config/CONFIG-006-expression-integration.md
2025-12-30 11:55:30 +01:00

404 lines
10 KiB
Markdown

# CONFIG-006: Expression System Integration
## Overview
Integrate `Noodl.Config` access into the expression evaluator with autocomplete support for config keys.
**Estimated effort:** 4-6 hours
**Dependencies:** CONFIG-001, TASK-006 (Expression System Overhaul)
**Blocks:** None
---
## Objectives
1. Make `Noodl.Config` accessible in Expression nodes
2. Add autocomplete for config keys in expression editor
3. Provide type hints for config values
4. Handle undefined config keys gracefully
---
## Expression Usage Examples
```javascript
// Simple value access
Noodl.Config.appName
// Color in style binding
Noodl.Config.primaryColor
// Conditional with config
Noodl.Config.pwaEnabled ? "Install App" : "Use in Browser"
// Template literal
`Welcome to ${Noodl.Config.appName}!`
// Array access
Noodl.Config.menuItems[0].label
// Object property
Noodl.Config.apiSettings.timeout
```
---
## Files to Modify
### 1. Expression Evaluator Context
**File:** `packages/noodl-runtime/src/nodes/std-library/expression.js` (or new expression-evaluator module from TASK-006)
Add Noodl.Config to the expression context:
```javascript
// In the expression preamble or context setup
const { configManager } = require('../../../config/config-manager');
function createExpressionContext() {
return {
// Math helpers
min: Math.min,
max: Math.max,
cos: Math.cos,
sin: Math.sin,
// ... other existing helpers ...
// Noodl globals
Noodl: {
Variables: Model.get('--ndl--global-variables'),
Objects: objectsProxy,
Arrays: arraysProxy,
Config: configManager.getConfig() // Add Config access
}
};
}
```
If following TASK-006's enhanced expression node pattern:
**File:** `packages/noodl-runtime/src/expression-evaluator.ts`
```typescript
import { configManager } from './config/config-manager';
export function createExpressionContext(nodeContext: NodeContext): ExpressionContext {
return {
// ... existing context ...
Noodl: {
// ... existing Noodl properties ...
Config: configManager.getConfig()
}
};
}
```
### 2. Expression Editor Autocomplete
**File:** `packages/noodl-editor/src/editor/src/views/panels/propertyeditor/CodeEditor/` (or relevant autocomplete module)
Add Config keys to autocomplete suggestions:
```typescript
import { ProjectModel } from '@noodl-models/projectmodel';
import { AppConfig } from '@noodl/runtime/src/config/types';
interface AutocompleteItem {
label: string;
kind: 'property' | 'method' | 'variable';
detail?: string;
documentation?: string;
}
function getConfigAutocompletions(prefix: string): AutocompleteItem[] {
const config = ProjectModel.instance.getAppConfig();
const items: AutocompleteItem[] = [];
// Built-in config keys
const builtInKeys = [
{ key: 'appName', type: 'string', doc: 'Application name' },
{ key: 'description', type: 'string', doc: 'Application description' },
{ key: 'coverImage', type: 'string', doc: 'Cover image path' },
{ key: 'ogTitle', type: 'string', doc: 'Open Graph title' },
{ key: 'ogDescription', type: 'string', doc: 'Open Graph description' },
{ key: 'ogImage', type: 'string', doc: 'Open Graph image' },
{ key: 'favicon', type: 'string', doc: 'Favicon path' },
{ key: 'themeColor', type: 'color', doc: 'Theme color' },
{ key: 'pwaEnabled', type: 'boolean', doc: 'PWA enabled state' },
{ key: 'pwaShortName', type: 'string', doc: 'PWA short name' },
{ key: 'pwaDisplay', type: 'string', doc: 'PWA display mode' },
{ key: 'pwaStartUrl', type: 'string', doc: 'PWA start URL' },
{ key: 'pwaBackgroundColor', type: 'color', doc: 'PWA background color' }
];
// Add built-in keys
for (const { key, type, doc } of builtInKeys) {
if (key.startsWith(prefix)) {
items.push({
label: key,
kind: 'property',
detail: type,
documentation: doc
});
}
}
// Add custom variables
for (const variable of config.variables) {
if (variable.key.startsWith(prefix)) {
items.push({
label: variable.key,
kind: 'property',
detail: variable.type,
documentation: variable.description
});
}
}
return items;
}
// In the autocomplete provider
function provideCompletions(
model: editor.ITextModel,
position: Position
): CompletionList {
const lineContent = model.getLineContent(position.lineNumber);
const beforeCursor = lineContent.substring(0, position.column - 1);
// Check if typing after "Noodl.Config."
const configMatch = beforeCursor.match(/Noodl\.Config\.(\w*)$/);
if (configMatch) {
const prefix = configMatch[1];
const items = getConfigAutocompletions(prefix);
return {
suggestions: items.map(item => ({
label: item.label,
kind: monaco.languages.CompletionItemKind.Property,
detail: item.detail,
documentation: item.documentation,
insertText: item.label,
range: new Range(
position.lineNumber,
position.column - prefix.length,
position.lineNumber,
position.column
)
}))
};
}
// Check if typing "Noodl."
const noodlMatch = beforeCursor.match(/Noodl\.(\w*)$/);
if (noodlMatch) {
const prefix = noodlMatch[1];
const noodlProps = ['Variables', 'Objects', 'Arrays', 'Config'];
return {
suggestions: noodlProps
.filter(p => p.startsWith(prefix))
.map(prop => ({
label: prop,
kind: monaco.languages.CompletionItemKind.Module,
insertText: prop,
range: new Range(
position.lineNumber,
position.column - prefix.length,
position.lineNumber,
position.column
)
}))
};
}
return { suggestions: [] };
}
```
### 3. Type Hints
**File:** Add type inference for config values
```typescript
function getConfigValueType(key: string): string {
const config = ProjectModel.instance.getAppConfig();
// Check custom variables first
const variable = config.variables.find(v => v.key === key);
if (variable) {
return variable.type;
}
// Built-in type mappings
const typeMap: Record<string, string> = {
appName: 'string',
description: 'string',
coverImage: 'string',
ogTitle: 'string',
ogDescription: 'string',
ogImage: 'string',
favicon: 'string',
themeColor: 'color',
pwaEnabled: 'boolean',
pwaShortName: 'string',
pwaDisplay: 'string',
pwaStartUrl: 'string',
pwaBackgroundColor: 'color'
};
return typeMap[key] || 'any';
}
```
### 4. Error Handling
**File:** `packages/noodl-runtime/src/config/config-manager.ts`
Ensure graceful handling of undefined keys:
```typescript
// In the config proxy (from CONFIG-001)
get(target, prop: string) {
if (prop in target) {
return target[prop];
}
// Log warning in development
if (process.env.NODE_ENV !== 'production') {
console.warn(
`Noodl.Config.${prop} is not defined. ` +
`Add it in App Setup or check for typos.`
);
}
return undefined;
}
```
---
## Type Declarations Update
**File:** `packages/noodl-viewer-react/static/viewer/global.d.ts.keep`
Enhance the Config type declaration with JSDoc:
```typescript
declare namespace Noodl {
/**
* App configuration values defined in App Setup.
*
* Config values are static and cannot be modified at runtime.
* Use `Noodl.Variables` for values that need to change.
*
* @example
* // Access app name
* const name = Noodl.Config.appName;
*
* // Use in template literal
* const greeting = `Welcome to ${Noodl.Config.appName}!`;
*
* // Access custom variable
* const color = Noodl.Config.primaryColor;
*
* @see https://docs.noodl.net/config
*/
const Config: Readonly<{
/** Application name */
readonly appName: string;
/** Application description */
readonly description: string;
/** Cover image path or URL */
readonly coverImage?: string;
/** Open Graph title (defaults to appName) */
readonly ogTitle: string;
/** Open Graph description (defaults to description) */
readonly ogDescription: string;
/** Open Graph image path or URL */
readonly ogImage?: string;
/** Favicon path or URL */
readonly favicon?: string;
/** Theme color (hex format) */
readonly themeColor?: string;
/** Whether PWA is enabled */
readonly pwaEnabled: boolean;
/** PWA short name */
readonly pwaShortName?: string;
/** PWA display mode */
readonly pwaDisplay?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';
/** PWA start URL */
readonly pwaStartUrl?: string;
/** PWA background color */
readonly pwaBackgroundColor?: string;
/** Custom configuration variables */
readonly [key: string]: any;
}>;
}
```
---
## Testing Checklist
### Expression Access
- [ ] `Noodl.Config.appName` returns correct value
- [ ] `Noodl.Config.primaryColor` returns color value
- [ ] `Noodl.Config.menuItems` returns array
- [ ] Nested access works: `Noodl.Config.menuItems[0].label`
- [ ] Template literals work: `` `Hello ${Noodl.Config.appName}` ``
- [ ] Undefined keys return undefined (no crash)
- [ ] Warning logged for undefined keys in dev
### Autocomplete
- [ ] Typing "Noodl." suggests "Config"
- [ ] Typing "Noodl.Config." shows all config keys
- [ ] Built-in keys show correct types
- [ ] Custom variables appear in suggestions
- [ ] Variable descriptions show in autocomplete
- [ ] Autocomplete filters as you type
### Integration
- [ ] Works in Expression nodes
- [ ] Works in Function nodes
- [ ] Works in Script nodes
- [ ] Works in inline expressions (if TASK-006 complete)
- [ ] Values update when config changes (editor refresh)
---
## Notes for Implementer
### TASK-006 Dependency
This task should be implemented alongside or after TASK-006 (Expression System Overhaul). If TASK-006 introduces a new expression evaluator module, integrate Config there. Otherwise, update the existing expression.js.
### Monaco Editor
The autocomplete examples assume Monaco editor is used. Adjust the implementation based on the actual editor component used in the expression input fields.
### Performance
The config object is frozen and created once at startup. Accessing it in expressions should have minimal performance impact. However, avoid calling `getConfig()` repeatedly - cache the reference.
### Cloud Functions
Remember to also update `packages/noodl-viewer-cloud/src/noodl-js-api.js` if cloud functions need Config access.