720 lines
19 KiB
Markdown
720 lines
19 KiB
Markdown
# Canvas Development Guide - /italism Interactive Demo
|
|
|
|
**Last Updated**: 2025-11-07
|
|
**Status**: Phase 1 Complete ✅ - Live Arrows with Propagators Working
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Quick Start](#quick-start)
|
|
2. [What We Built](#what-we-built)
|
|
3. [Key Technical Discoveries](#key-technical-discoveries)
|
|
4. [Architecture Overview](#architecture-overview)
|
|
5. [Known Issues & Solutions](#known-issues--solutions)
|
|
6. [Next Steps](#next-steps)
|
|
7. [FolkJS Integration Roadmap](#folkjs-integration-roadmap)
|
|
|
|
---
|
|
|
|
## Quick Start
|
|
|
|
```bash
|
|
# Run dev server
|
|
pnpm dev
|
|
|
|
# Open browser
|
|
http://localhost:3000/italism
|
|
|
|
# Test the full workflow
|
|
1. Select a rectangle (click with select tool)
|
|
2. Set a value in "Shape Properties" panel (e.g., 100)
|
|
3. Use arrow tool to click source rectangle, then target rectangle
|
|
4. Select the arrow (click it with select tool)
|
|
5. Click "Test Propagation" button
|
|
6. See value appear on target rectangle
|
|
```
|
|
|
|
---
|
|
|
|
## What We Built
|
|
|
|
### Phase 1: Live Arrows with Propagators ✅
|
|
|
|
Transformed the canvas from a static drawing tool into an **interactive data flow visualization** where arrows become functional connections that propagate values between shapes.
|
|
|
|
**Working Features:**
|
|
- ✅ Arrow drawing with snap-to-shape centers
|
|
- ✅ Arrow selection with visual highlighting (cyan, 4px)
|
|
- ✅ Propagator system (inline FolkJS-inspired implementation)
|
|
- ✅ Value propagation: source → target via arrows
|
|
- ✅ Expression editing (basic text input, not parsed yet)
|
|
- ✅ EventTarget-based event system
|
|
- ✅ Shape value editor in sidebar
|
|
- ✅ Rectangle drawing with negative dimension handling
|
|
- ✅ Shape dragging (rectangles, text, ellipses)
|
|
- ✅ Erase tool with propagator cleanup
|
|
- ✅ Text tool
|
|
|
|
**File**: `app/italism/page.tsx` (769 lines)
|
|
|
|
---
|
|
|
|
## Key Technical Discoveries
|
|
|
|
### 1. React State Immutability & EventTarget Storage
|
|
|
|
**Problem**: EventTargets stored directly on shape objects were lost when React state updated.
|
|
|
|
```typescript
|
|
// ❌ BROKEN - EventTarget lost on state update
|
|
(sourceShape as any)._eventTarget = mockSource
|
|
setShapes([...shapes]) // Creates NEW shape objects!
|
|
```
|
|
|
|
**Solution**: Store EventTargets in separate Map
|
|
```typescript
|
|
const [eventTargets, setEventTargets] = useState<Map<string, { source: EventTarget; target: EventTarget }>>(new Map())
|
|
|
|
// Store by arrow ID
|
|
setEventTargets((prev) => {
|
|
const next = new Map(prev)
|
|
next.set(arrow.id, { source: mockSource, target: mockTarget })
|
|
return next
|
|
})
|
|
|
|
// Retrieve when needed
|
|
const targets = eventTargets.get(arrow.id)
|
|
```
|
|
|
|
**Location**: `app/italism/page.tsx:161, 244-248, 715`
|
|
|
|
---
|
|
|
|
### 2. Stale Closure in Event Handlers
|
|
|
|
**Problem**: Propagator handlers captured `shapes` array from when arrow was created, not current state.
|
|
|
|
```typescript
|
|
// ❌ BROKEN - Captures stale shapes array
|
|
handler: (from, to) => {
|
|
const currentSourceShape = shapes.find(...) // OLD shapes!
|
|
setShapes((prevShapes) => prevShapes.map(...))
|
|
}
|
|
```
|
|
|
|
**Solution**: Use functional setState to access current state
|
|
```typescript
|
|
// ✅ WORKS - Gets current shapes at runtime
|
|
handler: (from, to) => {
|
|
setShapes((currentShapes) => {
|
|
const currentSourceShape = currentShapes.find(...) // CURRENT shapes!
|
|
return currentShapes.map((s) =>
|
|
s.id === targetId ? { ...s, value: sourceValue } : s
|
|
)
|
|
})
|
|
}
|
|
```
|
|
|
|
**Location**: `app/italism/page.tsx:233-251`
|
|
|
|
**Key Learning**: Always use functional setState `setState((current) => ...)` when accessing state inside closures that outlive the component render (event handlers, intervals, etc.)
|
|
|
|
---
|
|
|
|
### 3. Negative Dimensions Bug
|
|
|
|
**Problem**: Drawing rectangles by dragging upward creates negative height, breaking hit detection.
|
|
|
|
```typescript
|
|
// User drags from (100, 500) to (100, 300)
|
|
// Result: { x: 100, y: 500, width: 100, height: -200 }
|
|
|
|
// Hit detection fails:
|
|
y >= shape.y && y <= shape.y + shape.height
|
|
// becomes: y >= 500 && y <= 300 (impossible!)
|
|
```
|
|
|
|
**Solution**: Normalize rectangles after drawing
|
|
```typescript
|
|
if (newShape.type === "rectangle" && newShape.width !== undefined && newShape.height !== undefined) {
|
|
if (newShape.width < 0) {
|
|
newShape.x = newShape.x + newShape.width
|
|
newShape.width = Math.abs(newShape.width)
|
|
}
|
|
if (newShape.height < 0) {
|
|
newShape.y = newShape.y + newShape.height
|
|
newShape.height = Math.abs(newShape.height)
|
|
}
|
|
}
|
|
```
|
|
|
|
**Location**: `app/italism/page.tsx:597-607`
|
|
|
|
---
|
|
|
|
### 4. Line/Arrow Hit Detection
|
|
|
|
**Problem**: Clicking arrows requires proximity detection, not exact pixel match.
|
|
|
|
**Solution**: Point-to-line distance using vector projection
|
|
```typescript
|
|
function pointToLineDistance(px: number, py: number, x1: number, y1: number, x2: number, y2: number): number {
|
|
const A = px - x1
|
|
const B = py - y1
|
|
const C = x2 - x1
|
|
const D = y2 - y1
|
|
|
|
const dot = A * C + B * D
|
|
const lenSq = C * C + D * D
|
|
let param = -1
|
|
|
|
if (lenSq !== 0) {
|
|
param = dot / lenSq
|
|
}
|
|
|
|
let xx, yy
|
|
|
|
if (param < 0) {
|
|
xx = x1
|
|
yy = y1
|
|
} else if (param > 1) {
|
|
xx = x2
|
|
yy = y2
|
|
} else {
|
|
xx = x1 + param * C
|
|
yy = y1 + param * D
|
|
}
|
|
|
|
const dx = px - xx
|
|
const dy = py - yy
|
|
|
|
return Math.sqrt(dx * dx + dy * dy)
|
|
}
|
|
|
|
// Use with tolerance
|
|
const HIT_TOLERANCE = 10 // pixels
|
|
if (pointToLineDistance(x, y, shape.x, shape.y, shape.x2, shape.y2) < HIT_TOLERANCE) {
|
|
// Arrow clicked!
|
|
}
|
|
```
|
|
|
|
**Location**: `app/italism/page.tsx:69-100, 103`
|
|
|
|
---
|
|
|
|
### 5. Code Organization Best Practices
|
|
|
|
**Extract Duplicate Logic**:
|
|
```typescript
|
|
// Before: Duplicated in select tool, erase tool
|
|
const clicked = shapes.find((shape) => {
|
|
if (shape.width && shape.height) {
|
|
return x >= shape.x && x <= shape.x + shape.width && y >= shape.y && y <= shape.y + shape.height
|
|
} else if (shape.type === "text" && shape.text) {
|
|
// ... 15 more lines
|
|
}
|
|
})
|
|
|
|
// After: Single helper function
|
|
const isPointInShape = (x: number, y: number, shape: Shape): boolean => {
|
|
// All hit detection logic here
|
|
}
|
|
|
|
const clicked = shapes.find((shape) => isPointInShape(x, y, shape))
|
|
```
|
|
|
|
**Use Constants**:
|
|
```typescript
|
|
const HIT_TOLERANCE = 10 // Not magic number scattered everywhere
|
|
```
|
|
|
|
**Cleanup Resources**:
|
|
```typescript
|
|
// When deleting an arrow, dispose propagator
|
|
if (clicked.type === "arrow") {
|
|
const propagator = propagators.get(clicked.id)
|
|
if (propagator) {
|
|
propagator.dispose() // Removes event listeners
|
|
setPropagators((prev) => { /* remove */ })
|
|
}
|
|
setEventTargets((prev) => { /* remove */ })
|
|
}
|
|
```
|
|
|
|
**Location**: `app/italism/page.tsx:194-207, 435-460`
|
|
|
|
---
|
|
|
|
## Architecture Overview
|
|
|
|
### State Management
|
|
|
|
```typescript
|
|
// Canvas shapes (rectangles, arrows, text, etc.)
|
|
const [shapes, setShapes] = useState<Shape[]>([...])
|
|
|
|
// Active propagator instances (arrow.id → Propagator)
|
|
const [propagators, setPropagators] = useState<Map<string, Propagator>>(new Map())
|
|
|
|
// EventTargets for arrows (arrow.id → { source, target })
|
|
const [eventTargets, setEventTargets] = useState<Map<string, { source: EventTarget; target: EventTarget }>>(new Map())
|
|
|
|
// UI state
|
|
const [tool, setTool] = useState<Tool>("select")
|
|
const [selectedShape, setSelectedShape] = useState<string | null>(null)
|
|
const [isDrawing, setIsDrawing] = useState(false)
|
|
const [isDragging, setIsDragging] = useState(false)
|
|
```
|
|
|
|
### Shape Interface
|
|
|
|
```typescript
|
|
interface Shape {
|
|
id: string
|
|
type: "rectangle" | "ellipse" | "line" | "text" | "arrow"
|
|
x: number
|
|
y: number
|
|
width?: number
|
|
height?: number
|
|
x2?: number
|
|
y2?: number
|
|
text?: string
|
|
color: string
|
|
|
|
// Arrow-specific
|
|
sourceShapeId?: string
|
|
targetShapeId?: string
|
|
expression?: string // "value: from.value * 2"
|
|
|
|
// Data
|
|
value?: number
|
|
}
|
|
```
|
|
|
|
### Propagator Class (Inline Implementation)
|
|
|
|
```typescript
|
|
class Propagator {
|
|
private source: EventTarget | null = null
|
|
private target: EventTarget | null = null
|
|
private eventName: string | null = null
|
|
private handler: PropagatorFunction | null = null
|
|
|
|
constructor(options: PropagatorOptions)
|
|
propagate(event?: Event): void
|
|
dispose(): void // Cleanup event listeners
|
|
}
|
|
```
|
|
|
|
**Why Inline?** The `@folkjs/propagators` package exists but isn't properly configured for Next.js import. We implemented a simplified version inline. Future: migrate to actual FolkJS package.
|
|
|
|
### Data Flow
|
|
|
|
```
|
|
User sets value on Rectangle A (value: 100)
|
|
↓
|
|
User creates arrow from A to B
|
|
↓
|
|
createPropagatorForArrow() called
|
|
↓
|
|
EventTargets created & stored in Map
|
|
↓
|
|
Propagator instance created with handler
|
|
↓
|
|
User clicks "Test Propagation"
|
|
↓
|
|
source.dispatchEvent(new Event("update"))
|
|
↓
|
|
Handler fires: setShapes((current) => ...)
|
|
↓
|
|
Finds Rectangle A in current state
|
|
↓
|
|
Updates Rectangle B with value: 100
|
|
↓
|
|
Canvas re-renders, shows "100" on Rectangle B
|
|
```
|
|
|
|
---
|
|
|
|
## Known Issues & Solutions
|
|
|
|
### Issue: "Test Propagation" Does Nothing
|
|
|
|
**Symptoms**: Click button, nothing happens, console shows warnings
|
|
|
|
**Debugging Steps**:
|
|
1. Check console: Do you see "⚠️ No source value to propagate"?
|
|
- **Fix**: Set a value on the source rectangle first
|
|
2. Check console: Do you see "⚠️ No EventTarget found"?
|
|
- **Fix**: Delete arrow and recreate it (propagator wasn't initialized)
|
|
3. Check console: Do you see "✅ Propagating..." but no visual change?
|
|
- **Fix**: Check if you're looking at the right target shape
|
|
|
|
### Issue: Can't Click Arrows
|
|
|
|
**Symptoms**: Arrow exists but clicking doesn't select it
|
|
|
|
**Cause**: Hit detection tolerance too small or `isPointInShape` not called
|
|
|
|
**Fix**: Verify `HIT_TOLERANCE = 10` and selection uses `isPointInShape()`
|
|
|
|
### Issue: Rectangles Disappear When Drawn Upward
|
|
|
|
**Symptoms**: Draw rectangle by dragging up → rectangle not clickable
|
|
|
|
**Cause**: Negative height dimensions
|
|
|
|
**Fix**: Already implemented in `handleMouseUp:597-607`, check it's still there
|
|
|
|
### Issue: Arrow Points Wrong Direction After Dragging Shape
|
|
|
|
**Symptoms**: Move a rectangle → arrow endpoint doesn't follow
|
|
|
|
**Current Status**: **Not implemented** - arrows have fixed coordinates, don't update when shapes move
|
|
|
|
**Future Fix**:
|
|
```typescript
|
|
// In render loop, calculate arrow endpoints from source/target shapes
|
|
if (shape.type === "arrow" && shape.sourceShapeId && shape.targetShapeId) {
|
|
const source = shapes.find(s => s.id === shape.sourceShapeId)
|
|
const target = shapes.find(s => s.id === shape.targetShapeId)
|
|
if (source && target) {
|
|
const startCenter = getShapeCenter(source)
|
|
const endCenter = getShapeCenter(target)
|
|
// Draw from startCenter to endCenter
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
### Immediate Priorities
|
|
|
|
1. **Expression Parser** (Medium Priority)
|
|
- Currently expressions like `"value: from.value * 2"` are stored but not parsed
|
|
- Need to implement safe expression evaluation
|
|
- Options:
|
|
- Simple string replace: `"from.value"` → `sourceValue`
|
|
- Use Function constructor (unsafe)
|
|
- Use a library like `expr-eval` or `mathjs`
|
|
|
|
2. **Arrow Auto-Update on Shape Move** (High Priority)
|
|
- When shapes move, arrows should stay connected
|
|
- Calculate endpoints dynamically from source/target shapes
|
|
|
|
3. **Visual Flow Animation** (Medium Priority)
|
|
- Show animated "pulse" along arrow when propagation happens
|
|
- Use canvas path animation or particles
|
|
|
|
### Phase 2: Enhanced Propagators
|
|
|
|
4. **Bi-directional Propagation**
|
|
- Currently one-way (source → target)
|
|
- Allow target changes to flow back
|
|
|
|
5. **Multi-Source Aggregation**
|
|
- Multiple arrows pointing to same target
|
|
- Aggregate values (sum, average, max, etc.)
|
|
|
|
6. **Conditional Propagation**
|
|
- Only propagate if condition met
|
|
- Example: `if (from.value > 100) to.value = from.value`
|
|
|
|
### Phase 3: Polish & UX
|
|
|
|
7. **Keyboard Shortcuts**
|
|
- Delete key for selected shape
|
|
- Escape to deselect
|
|
- Ctrl+Z for undo
|
|
|
|
8. **Undo/Redo System**
|
|
- History stack for shapes
|
|
- Implement command pattern
|
|
|
|
9. **Persistence**
|
|
- Save canvas to localStorage
|
|
- Export/import JSON
|
|
- URL state encoding
|
|
|
|
10. **Color Picker**
|
|
- Let users choose shape colors
|
|
- Arrow color based on data type/state
|
|
|
|
---
|
|
|
|
## FolkJS Integration Roadmap
|
|
|
|
### Current State: Inline Propagator
|
|
|
|
We have a simplified Propagator class inline in `page.tsx`. This is sufficient for Phase 1 but limits us.
|
|
|
|
### Phase 4: Migrate to Real FolkJS
|
|
|
|
**Goals**:
|
|
1. Use actual `@folkjs/propagators` package
|
|
2. Integrate `@folkjs/canvas` for DOM-based shapes (instead of `<canvas>`)
|
|
3. Use `@folkjs/geometry` for calculations
|
|
|
|
**Benefits**:
|
|
- Built-in spatial transformations (pan, zoom)
|
|
- Gizmos for resize/rotate
|
|
- Better performance with DOM elements
|
|
- Native collaboration support via `@folkjs/collab`
|
|
|
|
**Migration Steps**:
|
|
|
|
1. **Install FolkJS packages**:
|
|
```bash
|
|
cd /home/ygg/Workspace/sandbox/FlowFunding/v2/lib/post-app-website-new
|
|
pnpm add ../folkjs/packages/propagators
|
|
pnpm add ../folkjs/packages/canvas
|
|
pnpm add ../folkjs/packages/geometry
|
|
```
|
|
|
|
2. **Replace inline Propagator**:
|
|
```typescript
|
|
import { Propagator } from '@folkjs/propagators'
|
|
// Remove inline class definition
|
|
```
|
|
|
|
3. **Convert canvas shapes to folk-shape elements**:
|
|
```typescript
|
|
// Instead of drawing rectangles on canvas
|
|
const folkShape = document.createElement('folk-shape')
|
|
folkShape.setAttribute('x', shape.x.toString())
|
|
folkShape.setAttribute('width', shape.width.toString())
|
|
```
|
|
|
|
4. **Use folk-event-propagator for arrows**:
|
|
```typescript
|
|
<folk-event-propagator
|
|
source="#rect1"
|
|
target="#rect2"
|
|
trigger="change"
|
|
expression="value: from.value * 2"
|
|
/>
|
|
```
|
|
|
|
See `FOLKJS_INTEGRATION.md` for detailed integration plan.
|
|
|
|
### Phase 5: Flow Funding Visualization
|
|
|
|
**Connect to actual Flow Funding data model**:
|
|
|
|
```typescript
|
|
interface FlowFundingAccount extends Shape {
|
|
type: "rectangle"
|
|
accountId: string
|
|
balance: number
|
|
minThreshold: number
|
|
maxThreshold: number
|
|
allocations: LiveArrow[]
|
|
}
|
|
|
|
// Visual: Fill rectangle based on balance/thresholds
|
|
const fillHeight = (account.balance / account.maxThreshold) * account.height
|
|
|
|
// Color coding
|
|
if (account.balance < account.minThreshold) {
|
|
ctx.fillStyle = "#ef4444" // Red: underfunded
|
|
} else if (account.balance > account.maxThreshold) {
|
|
ctx.fillStyle = "#10b981" // Green: overflow, ready to redistribute
|
|
} else {
|
|
ctx.fillStyle = "#6366f1" // Blue: healthy
|
|
}
|
|
```
|
|
|
|
**Animate overflow redistribution**:
|
|
```typescript
|
|
const animateFlowFunding = (accounts: FlowFundingAccount[]) => {
|
|
accounts.forEach(account => {
|
|
if (account.balance > account.maxThreshold) {
|
|
const overflow = account.balance - account.maxThreshold
|
|
|
|
account.allocations.forEach(arrow => {
|
|
const allocation = overflow * arrow.allocationPercentage
|
|
animateParticleFlow(arrow, allocation) // Visual animation
|
|
arrow.propagator?.propagate({ type: 'funding', detail: { amount: allocation } })
|
|
})
|
|
}
|
|
})
|
|
}
|
|
```
|
|
|
|
### Phase 6: Scoped Propagators (Advanced)
|
|
|
|
**Orion Reed's vision: Computation on edges, not nodes**
|
|
|
|
```typescript
|
|
interface ScopedPropagatorArrow extends LiveArrow {
|
|
scope: {
|
|
variables: Record<string, any> // Local state on the edge
|
|
computations: string[] // Functions defined on this edge
|
|
constraints: string[] // Rules that must hold
|
|
}
|
|
}
|
|
|
|
// Example: Arrow that computes transfer fee
|
|
const feeArrow: ScopedPropagatorArrow = {
|
|
type: "arrow",
|
|
sourceShapeId: "account1",
|
|
targetShapeId: "account2",
|
|
scope: {
|
|
variables: {
|
|
feeRate: 0.02, // 2% fee
|
|
history: [] // Track all transfers
|
|
},
|
|
computations: [
|
|
"fee = amount * feeRate",
|
|
"netTransfer = amount - fee",
|
|
"history.push({amount, fee, timestamp: Date.now()})"
|
|
],
|
|
constraints: [
|
|
"amount > 0",
|
|
"netTransfer <= source.balance"
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Development Best Practices Learned
|
|
|
|
### 1. Never Make Multiple Changes Without Testing
|
|
|
|
**Bad Workflow**:
|
|
```
|
|
Change 1: Fix EventTarget storage
|
|
Change 2: Fix propagator handler
|
|
Change 3: Add arrow selection
|
|
Change 4: Add visual highlighting
|
|
Test all at once → Everything broken, can't isolate issue
|
|
```
|
|
|
|
**Good Workflow**:
|
|
```
|
|
Change 1: Fix EventTarget storage
|
|
Test → Works ✅
|
|
Change 2: Fix propagator handler
|
|
Test → Works ✅
|
|
Change 3: Add arrow selection
|
|
Test → Works ✅
|
|
```
|
|
|
|
### 2. Systematic Debugging with Progressive Logging
|
|
|
|
When something doesn't work:
|
|
|
|
1. **Add high-level logging**:
|
|
```typescript
|
|
console.log("🎯 Arrow tool: Looking for shape at", x, y)
|
|
```
|
|
|
|
2. **Ask user for output, analyze**
|
|
|
|
3. **Add detailed logging**:
|
|
```typescript
|
|
console.log("📦 Available shapes:", shapes.map(s => ({...})))
|
|
```
|
|
|
|
4. **Identify pattern** → Form hypothesis
|
|
|
|
5. **Add targeted logging**:
|
|
```typescript
|
|
console.log("Shape bounds:", shape.width, shape.height, shape.x, shape.y)
|
|
```
|
|
|
|
6. **User provides data** → Root cause revealed (negative dimensions!)
|
|
|
|
7. **Apply minimal fix**
|
|
|
|
8. **Remove debug logging**
|
|
|
|
### 3. Use Git Strategically
|
|
|
|
When things break badly:
|
|
```bash
|
|
# Check what changed
|
|
git status
|
|
git diff app/italism/page.tsx
|
|
|
|
# Revert to known good state
|
|
git restore app/italism/page.tsx
|
|
|
|
# Or: User manually reverts to their last known working version
|
|
```
|
|
|
|
---
|
|
|
|
## Technical Debt
|
|
|
|
### Current
|
|
|
|
1. **Inline Propagator class** - Should use `@folkjs/propagators` package
|
|
2. **No expression parsing** - Expressions stored but not evaluated
|
|
3. **Magic strings** - Tool names as strings, should be enum
|
|
4. **No tests** - Should have unit tests for calculations
|
|
5. **Performance** - Canvas redraws everything on every change
|
|
6. **No accessibility** - Keyboard navigation, ARIA labels needed
|
|
|
|
### Future
|
|
|
|
1. **Component extraction** - Split into Canvas, Toolbar, Sidebar
|
|
2. **Custom hooks** - `useCanvas`, `useShapeManipulation`, etc.
|
|
3. **State management** - Consider Zustand or Jotai for global state
|
|
4. **Canvas optimization** - Use `requestAnimationFrame`, debounce mousemove
|
|
5. **Type safety** - Remove `any` types, stricter TypeScript
|
|
|
|
---
|
|
|
|
## Philosophical Connection
|
|
|
|
This implementation embodies **Post-Appitalism** principles:
|
|
|
|
### Malleable Software
|
|
- Users can freely create, modify, delete shapes
|
|
- No rigid application structure
|
|
- Direct manipulation of visual elements
|
|
- Arrows can be edited at runtime
|
|
|
|
### Flow-Based Economics
|
|
- **Arrows = Resource Flows**: Visual metaphor for allocation preferences
|
|
- **Nodes = Accounts**: Shapes represent participants
|
|
- **Canvas = Network**: Spatial representation of economic relationships
|
|
- **Propagation = Value Transfer**: Data flows like money
|
|
|
|
### Scoped Propagators (Future)
|
|
- Arrows become **edge-based computations**
|
|
- Rather than compute on nodes, compute **on the connections**
|
|
- Aligns with Orion Reed's vision of propagators as mappings along edges
|
|
- See: https://www.orionreed.com/posts/scoped-propagators
|
|
|
|
---
|
|
|
|
## Resources
|
|
|
|
- **FolkJS Demos**: `../../folkjs/website/demos/propagators/`
|
|
- **Flow Funding Paper**: `../../../threshold-based-flow-funding.md`
|
|
- **Project Philosophy**: `../../../CLAUDE.md`
|
|
- **Scoped Propagators Article**: https://www.orionreed.com/posts/scoped-propagators
|
|
|
|
---
|
|
|
|
## Summary: What Makes This Special
|
|
|
|
This isn't just a drawing app. It's a **live, interactive, programmable canvas** where:
|
|
|
|
1. **Arrows are functional**, not decorative
|
|
2. **Data flows visually** through the network
|
|
3. **Edges have computation**, not just nodes
|
|
4. **Users can reprogram** connections at runtime
|
|
5. **Visual = Executable** - what you see is what computes
|
|
|
|
**Result**: A tool that lets people **design, visualize, and simulate** Flow Funding networks before deploying them, making the abstract concept of threshold-based resource allocation **tangible and interactive**.
|
|
|
|
The canvas demonstrates Post-Appitalism by being Post-App: **malleable, open, collaborative, and alive**.
|