Files
OpenNoodl/dev-docs/tasks/phase-9-styles-overhaul/CLEANUP-SUBTASKS/TASK-000I-node-graph-visual-improvements/TASK-000I-A-visual-polish.md

473 lines
11 KiB
Markdown

# TASK-000I-A: Node Graph Visual Polish
**Parent Task:** TASK-009I Node Graph Visual Improvements
**Estimated Time:** 8-12 hours
**Risk Level:** Low
**Dependencies:** None
---
## Objective
Modernize the visual appearance of nodes on the canvas without changing functionality. This is a purely cosmetic update that improves the perceived quality and modernity of the editor.
---
## Scope
1. **Rounded corners** on all node rectangles
2. **Updated color palette** following design system
3. **Refined connection points** (port dots/arrows)
4. **Port label truncation** with ellipsis for overflow
### Out of Scope
- Node sizing changes
- Layout algorithm changes
- New functionality
- Port grouping (Sub-Task C)
---
## Implementation Phases
### Phase A1: Rounded Corners (2-3 hours)
#### Current Code
In `NodeGraphEditorNode.ts` paint() method:
```typescript
// Background - sharp corners
ctx.fillStyle = nc.header;
ctx.fillRect(x, y, this.nodeSize.width, this.nodeSize.height);
// Border - sharp corners
ctx.rect(x, y, this.nodeSize.width, this.nodeSize.height);
ctx.stroke();
```
#### New Approach
**Create helper file** `canvasHelpers.ts`:
```typescript
/**
* Draw a rounded rectangle path
* Uses native roundRect if available, falls back to arcTo
*/
export function roundRect(
ctx: CanvasRenderingContext2D,
x: number,
y: number,
width: number,
height: number,
radius: number | { tl: number; tr: number; br: number; bl: number }
): void {
const r = typeof radius === 'number' ? { tl: radius, tr: radius, br: radius, bl: radius } : radius;
ctx.beginPath();
ctx.moveTo(x + r.tl, y);
ctx.lineTo(x + width - r.tr, y);
ctx.arcTo(x + width, y, x + width, y + r.tr, r.tr);
ctx.lineTo(x + width, y + height - r.br);
ctx.arcTo(x + width, y + height, x + width - r.br, y + height, r.br);
ctx.lineTo(x + r.bl, y + height);
ctx.arcTo(x, y + height, x, y + height - r.bl, r.bl);
ctx.lineTo(x, y + r.tl);
ctx.arcTo(x, y, x + r.tl, y, r.tl);
ctx.closePath();
}
/**
* Fill a rounded rectangle
*/
export function fillRoundRect(
ctx: CanvasRenderingContext2D,
x: number,
y: number,
width: number,
height: number,
radius: number
): void {
roundRect(ctx, x, y, width, height, radius);
ctx.fill();
}
/**
* Stroke a rounded rectangle
*/
export function strokeRoundRect(
ctx: CanvasRenderingContext2D,
x: number,
y: number,
width: number,
height: number,
radius: number
): void {
roundRect(ctx, x, y, width, height, radius);
ctx.stroke();
}
```
#### Changes to NodeGraphEditorNode.ts
```typescript
import { fillRoundRect, strokeRoundRect } from './canvasHelpers';
// Constants
const NODE_CORNER_RADIUS = 6;
// In paint() method:
// Background - replace fillRect
ctx.fillStyle = nc.header;
fillRoundRect(ctx, x, y, this.nodeSize.width, this.nodeSize.height, NODE_CORNER_RADIUS);
// Body area - need to clip to rounded shape
ctx.save();
roundRect(ctx, x, y, this.nodeSize.width, this.nodeSize.height, NODE_CORNER_RADIUS);
ctx.clip();
ctx.fillStyle = nc.base;
ctx.fillRect(x, y + titlebarHeight, this.nodeSize.width, this.nodeSize.height - titlebarHeight);
ctx.restore();
// Selection border
if (this.selected || this.borderHighlighted) {
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 2;
strokeRoundRect(ctx, x, y, this.nodeSize.width, this.nodeSize.height, NODE_CORNER_RADIUS);
}
// Error border
if (!health.healthy) {
ctx.setLineDash([5]);
ctx.strokeStyle = '#F57569';
strokeRoundRect(ctx, x - 1, y - 1, this.nodeSize.width + 2, this.nodeSize.height + 2, NODE_CORNER_RADIUS + 1);
ctx.setLineDash([]);
}
```
#### Locations to Update
1. **Node background** (~line 220)
2. **Node body fill** (~line 230)
3. **Highlight overlay** (~line 240)
4. **Selection border** (~line 290)
5. **Error/unhealthy border** (~line 280)
6. **Annotation borders** (~line 300)
#### Testing
- [ ] Nodes render with rounded corners at 100% zoom
- [ ] Corners visible at 50% zoom
- [ ] Corners not distorted at 150% zoom
- [ ] Selection highlight follows rounded shape
- [ ] Error dashed border follows rounded shape
- [ ] No visual artifacts at corner intersections
---
### Phase A2: Color Palette Update (2-3 hours)
#### File to Modify
`packages/noodl-core-ui/src/styles/custom-properties/colors.css`
#### Current vs Proposed
Document current values first, then update:
```css
/* ===== NODE COLORS ===== */
/* Data nodes - Green */
/* Current: muted olive */
/* Proposed: richer emerald */
--base-color-node-green-900: #052e16;
--base-color-node-green-700: #166534;
--base-color-node-green-600: #16a34a;
--base-color-node-green-500: #22c55e;
/* Visual nodes - Blue */
/* Current: muted blue */
/* Proposed: cleaner slate */
--base-color-node-blue-900: #0f172a;
--base-color-node-blue-700: #334155;
--base-color-node-blue-600: #475569;
--base-color-node-blue-500: #64748b;
--base-color-node-blue-400: #94a3b8;
--base-color-node-blue-300: #cbd5e1;
--base-color-node-blue-200: #e2e8f0;
/* Logic nodes - Grey */
/* Current: flat grey */
/* Proposed: warmer zinc */
--base-color-node-grey-900: #18181b;
--base-color-node-grey-700: #3f3f46;
--base-color-node-grey-600: #52525b;
/* Custom nodes - Pink */
/* Current: magenta */
/* Proposed: refined rose */
--base-color-node-pink-900: #4c0519;
--base-color-node-pink-700: #be123c;
--base-color-node-pink-600: #e11d48;
/* Component nodes - Purple */
/* Current: muted purple */
/* Proposed: cleaner violet */
--base-color-node-purple-900: #2e1065;
--base-color-node-purple-700: #6d28d9;
--base-color-node-purple-600: #7c3aed;
```
#### Process
1. **Document current** - Screenshot and hex values
2. **Design new palette** - Use design system principles
3. **Update CSS variables** - One category at a time
4. **Test contrast** - WCAG AA minimum (4.5:1 for text)
5. **Visual review** - Check all node types
#### Contrast Checking
Use browser dev tools or online checker:
- Header text on header background
- Port labels on body background
- Selection highlight visibility
#### Testing
- [ ] Data nodes (green) - legible, modern
- [ ] Visual nodes (blue) - legible, modern
- [ ] Logic nodes (grey) - legible, modern
- [ ] Custom nodes (pink) - legible, modern
- [ ] Component nodes (purple) - legible, modern
- [ ] All text passes contrast check
- [ ] Colors distinguish node types clearly
---
### Phase A3: Connection Point Styling (2-3 hours)
#### Current Implementation
In `NodeGraphEditorNode.ts` drawPlugs():
```typescript
function dot(side, color) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x + (side === 'left' ? 0 : _this.nodeSize.width), ty, 4, 0, 2 * Math.PI, false);
ctx.fill();
}
function arrow(side, color) {
const dx = side === 'left' ? 4 : -4;
const cx = x + (side === 'left' ? 0 : _this.nodeSize.width);
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(cx - dx, ty - 4);
ctx.lineTo(cx + dx, ty);
ctx.lineTo(cx - dx, ty + 4);
ctx.fill();
}
```
#### Improvements
```typescript
const PORT_RADIUS = 5; // Increased from 4
const PORT_INNER_RADIUS = 2;
function drawPort(side: 'left' | 'right', type: 'dot' | 'arrow', color: string, connected: boolean) {
const cx = x + (side === 'left' ? 0 : _this.nodeSize.width);
ctx.save();
if (type === 'dot') {
// Outer circle
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(cx, ty, PORT_RADIUS, 0, 2 * Math.PI);
ctx.fill();
// Inner highlight (connected state)
if (connected) {
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.beginPath();
ctx.arc(cx, ty, PORT_INNER_RADIUS, 0, 2 * Math.PI);
ctx.fill();
}
} else {
// Arrow (signal)
const dx = side === 'left' ? PORT_RADIUS : -PORT_RADIUS;
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(cx - dx, ty - PORT_RADIUS);
ctx.lineTo(cx + dx, ty);
ctx.lineTo(cx - dx, ty + PORT_RADIUS);
ctx.closePath();
ctx.fill();
}
ctx.restore();
}
```
#### Testing
- [ ] Port dots larger and easier to click
- [ ] Connected ports have visual distinction
- [ ] Arrows properly sized
- [ ] Hit detection still works
- [ ] Dragging connections works
- [ ] Hover states visible
---
### Phase A4: Port Label Truncation (2-3 hours)
#### Problem
Long port names overflow the node boundary, appearing outside the node rectangle.
#### Solution
**Add to canvasHelpers.ts:**
```typescript
/**
* Truncate text to fit within maxWidth, adding ellipsis if needed
*/
export function truncateText(ctx: CanvasRenderingContext2D, text: string, maxWidth: number): string {
if (ctx.measureText(text).width <= maxWidth) {
return text;
}
const ellipsis = '…';
let truncated = text;
while (truncated.length > 0) {
truncated = truncated.slice(0, -1);
if (ctx.measureText(truncated + ellipsis).width <= maxWidth) {
return truncated + ellipsis;
}
}
return ellipsis;
}
```
#### Integration in drawPlugs()
```typescript
// Calculate available width for label
const labelMaxWidth =
side === 'left'
? _this.nodeSize.width / 2 - horizontalSpacing - PORT_RADIUS
: _this.nodeSize.width / 2 - horizontalSpacing - PORT_RADIUS;
// Truncate if needed
const displayName = truncateText(ctx, p.displayName || p.property, labelMaxWidth);
ctx.fillText(displayName, tx, ty);
// Store full name for tooltip
p.fullDisplayName = p.displayName || p.property;
```
#### Tooltip Integration
Verify existing tooltip system shows full port name on hover. If not working:
```typescript
// In handleMouseEvent, on port hover:
if (p.fullDisplayName !== displayName) {
PopupLayer.instance.showTooltip({
content: p.fullDisplayName,
position: { x: mouseX, y: mouseY }
});
}
```
#### Testing
- [ ] Long labels truncate with ellipsis
- [ ] Short labels unchanged
- [ ] Truncation respects node width
- [ ] Tooltip shows full name on hover
- [ ] Left and right aligned labels both work
- [ ] No text overflow outside node bounds
---
## Files to Create
```
packages/noodl-editor/src/editor/src/views/nodegrapheditor/
└── canvasHelpers.ts # Utility functions
```
## Files to Modify
```
packages/noodl-editor/src/editor/src/views/nodegrapheditor/
└── NodeGraphEditorNode.ts # Main rendering changes
packages/noodl-core-ui/src/styles/custom-properties/
└── colors.css # Color palette updates
```
---
## Testing Checklist
### Visual Verification
- [ ] Open existing project with many node types
- [ ] All nodes render with rounded corners
- [ ] Colors updated and consistent
- [ ] Port indicators refined
- [ ] Labels truncate properly
### Functional Verification
- [ ] Node selection works
- [ ] Connection dragging works
- [ ] Copy/paste works
- [ ] Undo/redo works
- [ ] Zoom in/out renders correctly
### Performance
- [ ] No noticeable slowdown
- [ ] Smooth panning with 50+ nodes
- [ ] Profile render time if concerned
---
## Success Criteria
- [ ] All nodes have rounded corners (6px radius)
- [ ] Color palette modernized
- [ ] Port indicators larger and cleaner
- [ ] Long labels truncate with ellipsis
- [ ] Full port name visible on hover
- [ ] No visual regressions
- [ ] No functional regressions
- [ ] Performance unchanged
---
## Rollback Plan
If issues arise:
1. Revert `NodeGraphEditorNode.ts` changes
2. Revert `colors.css` changes
3. Delete `canvasHelpers.ts`
All changes are isolated to rendering code with no data model changes.