e.stopPropagation()}
+ >
+
+
+
+
+ ${Math.floor(currentValue).toLocaleString()}
+
+ / ${maxCapacity.toLocaleString()}
+
+
+
+
+ MIN: ${(minThreshold/1000).toFixed(0)}k
+ MAX: ${(maxThreshold/1000).toFixed(0)}k
+
+
+
+
+
+
+
handleThresholdMouseDown(e, 'min')}
+ />
+
+
handleThresholdMouseDown(e, 'max')}
+ />
+
+
+ $0
+ Drag handles to adjust
+ ${(maxCapacity/1000).toFixed(0)}k
+
+
+
+
+
+
+
→ Outflows
+
+
+
+ {localOverflow.length > 0 ? (
+ <>
+
+
+ {localOverflow.map((alloc, idx) => (
+
+
+
{alloc.targetId}
+
{alloc.percentage}%
+
+
+ ))}
+
+ >
+ ) : (
+
No outflows yet
+ )}
+
+ {showAddOutflow && (
+
+
setNewItemName(e.target.value)}
+ placeholder="New funnel name..."
+ className="w-full text-xs px-2 py-1 border border-slate-200 rounded mb-2"
+ autoFocus
+ />
+
+
+
+
+
+ )}
+
+
+
+
+
↓ Outcomes
+
+
+
+ {localSpending.length > 0 ? (
+ <>
+
+
+ {localSpending.map((alloc, idx) => (
+
+
+
{alloc.targetId}
+
{alloc.percentage}%
+
+
+ ))}
+
+ >
+ ) : (
+
No outcomes yet
+ )}
+
+ {showAddOutcome && (
+
+
setNewItemName(e.target.value)}
+ placeholder="New outcome name..."
+ className="w-full text-xs px-2 py-1 border border-slate-200 rounded mb-2"
+ autoFocus
+ />
+
+
+
+
+
+ )}
+
+
+
+
+ Drag pie slices to adjust • Click + to add new items
+
+
+
+
+
+
+
+ )}
+ >
+ )
+}
+
+export default memo(FunnelNode)
diff --git a/components/nodes/OutcomeNode.tsx b/components/nodes/OutcomeNode.tsx
new file mode 100644
index 0000000..ef52501
--- /dev/null
+++ b/components/nodes/OutcomeNode.tsx
@@ -0,0 +1,90 @@
+'use client'
+
+import { memo } from 'react'
+import { Handle, Position } from '@xyflow/react'
+import type { NodeProps } from '@xyflow/react'
+import type { OutcomeNodeData } from '@/lib/types'
+
+function OutcomeNode({ data, selected }: NodeProps) {
+ const nodeData = data as OutcomeNodeData
+ const { label, description, fundingReceived, fundingTarget, status } = nodeData
+
+ const progress = fundingTarget > 0 ? Math.min(100, (fundingReceived / fundingTarget) * 100) : 0
+ const isFunded = fundingReceived >= fundingTarget
+ const isPartial = fundingReceived > 0 && fundingReceived < fundingTarget
+
+ const statusColors = {
+ 'not-started': { bg: 'bg-slate-100', text: 'text-slate-600', border: 'border-slate-300' },
+ 'in-progress': { bg: 'bg-blue-100', text: 'text-blue-700', border: 'border-blue-300' },
+ 'completed': { bg: 'bg-emerald-100', text: 'text-emerald-700', border: 'border-emerald-300' },
+ 'blocked': { bg: 'bg-red-100', text: 'text-red-700', border: 'border-red-300' },
+ }
+
+ const colors = statusColors[status] || statusColors['not-started']
+
+ return (
+
+
+
+
+
+
+ {description && (
+
{description}
+ )}
+
+
+
+ {status.replace('-', ' ')}
+
+ {isFunded && (
+
+ )}
+
+
+
+
+ Funding
+
+ ${Math.floor(fundingReceived).toLocaleString()} / ${fundingTarget.toLocaleString()}
+
+
+
+
+ {progress.toFixed(0)}%
+
+
+
+
+ )
+}
+
+export default memo(OutcomeNode)
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..d82659e
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,14 @@
+services:
+ rfunds-online:
+ build: .
+ restart: unless-stopped
+ labels:
+ - "traefik.enable=true"
+ - "traefik.http.routers.rfunds.rule=Host(`rfunds.online`) || Host(`www.rfunds.online`)"
+ - "traefik.http.services.rfunds.loadbalancer.server.port=3000"
+ networks:
+ - traefik-public
+
+networks:
+ traefik-public:
+ external: true
diff --git a/lib/presets.ts b/lib/presets.ts
new file mode 100644
index 0000000..2e487df
--- /dev/null
+++ b/lib/presets.ts
@@ -0,0 +1,190 @@
+import type { FlowNode, FunnelNodeData, OutcomeNodeData } from './types'
+
+// Colors for allocations
+export const SPENDING_COLORS = ['#3b82f6', '#8b5cf6', '#ec4899', '#06b6d4', '#10b981', '#6366f1']
+export const OVERFLOW_COLORS = ['#f59e0b', '#ef4444', '#f97316', '#eab308', '#dc2626', '#ea580c']
+
+// Demo preset: Treasury → 3 sub-funnels → 7 outcomes
+export const demoNodes: FlowNode[] = [
+ // Main Treasury Funnel (top center)
+ {
+ id: 'treasury',
+ type: 'funnel',
+ position: { x: 630, y: 0 },
+ data: {
+ label: 'Treasury',
+ currentValue: 85000,
+ minThreshold: 20000,
+ maxThreshold: 70000,
+ maxCapacity: 100000,
+ inflowRate: 1000,
+ overflowAllocations: [
+ { targetId: 'public-goods', percentage: 40, color: OVERFLOW_COLORS[0] },
+ { targetId: 'research', percentage: 35, color: OVERFLOW_COLORS[1] },
+ { targetId: 'emergency', percentage: 25, color: OVERFLOW_COLORS[2] },
+ ],
+ spendingAllocations: [
+ { targetId: 'treasury-ops', percentage: 100, color: SPENDING_COLORS[0] },
+ ],
+ } as FunnelNodeData,
+ },
+ // Sub-funnels (middle row)
+ {
+ id: 'public-goods',
+ type: 'funnel',
+ position: { x: 170, y: 450 },
+ data: {
+ label: 'Public Goods',
+ currentValue: 45000,
+ minThreshold: 15000,
+ maxThreshold: 50000,
+ maxCapacity: 70000,
+ inflowRate: 400,
+ overflowAllocations: [],
+ spendingAllocations: [
+ { targetId: 'pg-infra', percentage: 50, color: SPENDING_COLORS[0] },
+ { targetId: 'pg-education', percentage: 30, color: SPENDING_COLORS[1] },
+ { targetId: 'pg-tooling', percentage: 20, color: SPENDING_COLORS[2] },
+ ],
+ } as FunnelNodeData,
+ },
+ {
+ id: 'research',
+ type: 'funnel',
+ position: { x: 975, y: 450 },
+ data: {
+ label: 'Research',
+ currentValue: 28000,
+ minThreshold: 20000,
+ maxThreshold: 45000,
+ maxCapacity: 60000,
+ inflowRate: 350,
+ overflowAllocations: [],
+ spendingAllocations: [
+ { targetId: 'research-grants', percentage: 70, color: SPENDING_COLORS[0] },
+ { targetId: 'research-papers', percentage: 30, color: SPENDING_COLORS[1] },
+ ],
+ } as FunnelNodeData,
+ },
+ {
+ id: 'emergency',
+ type: 'funnel',
+ position: { x: 1320, y: 450 },
+ data: {
+ label: 'Emergency',
+ currentValue: 12000,
+ minThreshold: 25000,
+ maxThreshold: 60000,
+ maxCapacity: 80000,
+ inflowRate: 250,
+ overflowAllocations: [],
+ spendingAllocations: [
+ { targetId: 'emergency-response', percentage: 100, color: SPENDING_COLORS[0] },
+ ],
+ } as FunnelNodeData,
+ },
+ // Outcome nodes (bottom row)
+ {
+ id: 'pg-infra',
+ type: 'outcome',
+ position: { x: -50, y: 900 },
+ data: {
+ label: 'Infrastructure',
+ description: 'Core infrastructure development',
+ fundingReceived: 22000,
+ fundingTarget: 30000,
+ status: 'in-progress',
+ } as OutcomeNodeData,
+ },
+ {
+ id: 'pg-education',
+ type: 'outcome',
+ position: { x: 180, y: 900 },
+ data: {
+ label: 'Education',
+ description: 'Developer education programs',
+ fundingReceived: 12000,
+ fundingTarget: 20000,
+ status: 'in-progress',
+ } as OutcomeNodeData,
+ },
+ {
+ id: 'pg-tooling',
+ type: 'outcome',
+ position: { x: 410, y: 900 },
+ data: {
+ label: 'Dev Tooling',
+ description: 'Open-source developer tools',
+ fundingReceived: 5000,
+ fundingTarget: 15000,
+ status: 'not-started',
+ } as OutcomeNodeData,
+ },
+ {
+ id: 'treasury-ops',
+ type: 'outcome',
+ position: { x: 640, y: 900 },
+ data: {
+ label: 'Treasury Ops',
+ description: 'Day-to-day treasury management',
+ fundingReceived: 15000,
+ fundingTarget: 25000,
+ status: 'in-progress',
+ } as OutcomeNodeData,
+ },
+ {
+ id: 'research-grants',
+ type: 'outcome',
+ position: { x: 870, y: 900 },
+ data: {
+ label: 'Grants',
+ description: 'Academic research grants',
+ fundingReceived: 18000,
+ fundingTarget: 25000,
+ status: 'in-progress',
+ } as OutcomeNodeData,
+ },
+ {
+ id: 'research-papers',
+ type: 'outcome',
+ position: { x: 1100, y: 900 },
+ data: {
+ label: 'Papers',
+ description: 'Peer-reviewed publications',
+ fundingReceived: 8000,
+ fundingTarget: 10000,
+ status: 'in-progress',
+ } as OutcomeNodeData,
+ },
+ {
+ id: 'emergency-response',
+ type: 'outcome',
+ position: { x: 1330, y: 900 },
+ data: {
+ label: 'Response Fund',
+ description: 'Rapid response for critical issues',
+ fundingReceived: 5000,
+ fundingTarget: 50000,
+ status: 'not-started',
+ } as OutcomeNodeData,
+ },
+]
+
+// Empty starter for user-created spaces
+export const starterNodes: FlowNode[] = [
+ {
+ id: 'treasury-1',
+ type: 'funnel',
+ position: { x: 400, y: 50 },
+ data: {
+ label: 'My Treasury',
+ currentValue: 50000,
+ minThreshold: 10000,
+ maxThreshold: 40000,
+ maxCapacity: 60000,
+ inflowRate: 500,
+ overflowAllocations: [],
+ spendingAllocations: [],
+ } as FunnelNodeData,
+ },
+]
diff --git a/lib/state.ts b/lib/state.ts
new file mode 100644
index 0000000..df4a230
--- /dev/null
+++ b/lib/state.ts
@@ -0,0 +1,73 @@
+import { compressToEncodedURIComponent, decompressFromEncodedURIComponent } from 'lz-string'
+import type { FlowNode, SpaceConfig } from './types'
+
+const STORAGE_PREFIX = 'rfunds-space-'
+
+interface SerializableState {
+ nodes: FlowNode[]
+}
+
+export function serializeState(nodes: FlowNode[]): string {
+ const state: SerializableState = { nodes }
+ const json = JSON.stringify(state)
+ return compressToEncodedURIComponent(json)
+}
+
+export function deserializeState(compressed: string): { nodes: FlowNode[] } | null {
+ try {
+ const json = decompressFromEncodedURIComponent(compressed)
+ if (!json) return null
+ const state = JSON.parse(json) as SerializableState
+ if (!state.nodes || !Array.isArray(state.nodes)) return null
+ return { nodes: state.nodes }
+ } catch {
+ return null
+ }
+}
+
+export function saveToLocal(name: string, nodes: FlowNode[]): void {
+ const config: SpaceConfig = {
+ name,
+ nodes,
+ createdAt: Date.now(),
+ updatedAt: Date.now(),
+ }
+
+ // Check if exists to preserve createdAt
+ const existing = loadFromLocal(name)
+ if (existing) {
+ config.createdAt = existing.createdAt
+ }
+
+ localStorage.setItem(STORAGE_PREFIX + name, JSON.stringify(config))
+}
+
+export function loadFromLocal(name: string): SpaceConfig | null {
+ try {
+ const raw = localStorage.getItem(STORAGE_PREFIX + name)
+ if (!raw) return null
+ return JSON.parse(raw) as SpaceConfig
+ } catch {
+ return null
+ }
+}
+
+export function deleteFromLocal(name: string): void {
+ localStorage.removeItem(STORAGE_PREFIX + name)
+}
+
+export function listSavedSpaces(): SpaceConfig[] {
+ const spaces: SpaceConfig[] = []
+ for (let i = 0; i < localStorage.length; i++) {
+ const key = localStorage.key(i)
+ if (key && key.startsWith(STORAGE_PREFIX)) {
+ try {
+ const config = JSON.parse(localStorage.getItem(key)!) as SpaceConfig
+ spaces.push(config)
+ } catch {
+ // skip corrupt entries
+ }
+ }
+ }
+ return spaces.sort((a, b) => b.updatedAt - a.updatedAt)
+}
diff --git a/lib/types.ts b/lib/types.ts
new file mode 100644
index 0000000..c802919
--- /dev/null
+++ b/lib/types.ts
@@ -0,0 +1,57 @@
+import type { Node, Edge } from '@xyflow/react'
+
+// Overflow allocation - funds flowing to OTHER FUNNELS when above max threshold
+export interface OverflowAllocation {
+ targetId: string
+ percentage: number // 0-100
+ color: string
+}
+
+// Spending allocation - funds flowing DOWN to OUTCOMES/OUTPUTS
+export interface SpendingAllocation {
+ targetId: string
+ percentage: number // 0-100
+ color: string
+}
+
+export interface FunnelNodeData {
+ label: string
+ currentValue: number
+ minThreshold: number
+ maxThreshold: number
+ maxCapacity: number
+ inflowRate: number
+ // Overflow goes SIDEWAYS to other funnels
+ overflowAllocations: OverflowAllocation[]
+ // Spending goes DOWN to outcomes/outputs
+ spendingAllocations: SpendingAllocation[]
+ [key: string]: unknown
+}
+
+export interface OutcomeNodeData {
+ label: string
+ description?: string
+ fundingReceived: number
+ fundingTarget: number
+ status: 'not-started' | 'in-progress' | 'completed' | 'blocked'
+ [key: string]: unknown
+}
+
+export type FlowNode = Node
+
+export interface FlowEdgeData {
+ allocation: number // percentage 0-100
+ color: string
+ edgeType: 'overflow' | 'spending' // overflow = sideways, spending = downward
+ [key: string]: unknown
+}
+
+export type FlowEdge = Edge
+
+// Serializable space config (no xyflow internals)
+export interface SpaceConfig {
+ name: string
+ nodes: FlowNode[]
+ createdAt: number
+ updatedAt: number
+}
diff --git a/next.config.mjs b/next.config.mjs
index 4678774..e25a6a2 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -1,4 +1,6 @@
/** @type {import('next').NextConfig} */
-const nextConfig = {};
+const nextConfig = {
+ output: 'standalone',
+};
export default nextConfig;
diff --git a/package.json b/package.json
index 6f03cfb..91a931d 100644
--- a/package.json
+++ b/package.json
@@ -9,16 +9,20 @@
"lint": "next lint"
},
"dependencies": {
+ "@xyflow/react": "^12.10.0",
+ "lz-string": "^1.5.0",
+ "next": "14.2.35",
"react": "^18",
"react-dom": "^18",
- "next": "14.2.35"
+ "zustand": "^5.0.11"
},
"devDependencies": {
- "typescript": "^5",
+ "@types/lz-string": "^1.5.0",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"postcss": "^8",
- "tailwindcss": "^3.4.1"
+ "tailwindcss": "^3.4.1",
+ "typescript": "^5"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a4a26db..e5fcbef 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,6 +8,12 @@ importers:
.:
dependencies:
+ '@xyflow/react':
+ specifier: ^12.10.0
+ version: 12.10.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ lz-string:
+ specifier: ^1.5.0
+ version: 1.5.0
next:
specifier: 14.2.35
version: 14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -17,7 +23,13 @@ importers:
react-dom:
specifier: ^18
version: 18.3.1(react@18.3.1)
+ zustand:
+ specifier: ^5.0.11
+ version: 5.0.11(@types/react@18.3.28)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1))
devDependencies:
+ '@types/lz-string':
+ specifier: ^1.5.0
+ version: 1.5.0
'@types/node':
specifier: ^20
version: 20.19.33
@@ -131,6 +143,28 @@ packages:
'@swc/helpers@0.5.5':
resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
+ '@types/d3-color@3.1.3':
+ resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
+
+ '@types/d3-drag@3.0.7':
+ resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==}
+
+ '@types/d3-interpolate@3.0.4':
+ resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
+
+ '@types/d3-selection@3.0.11':
+ resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==}
+
+ '@types/d3-transition@3.0.9':
+ resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==}
+
+ '@types/d3-zoom@3.0.8':
+ resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==}
+
+ '@types/lz-string@1.5.0':
+ resolution: {integrity: sha512-s84fKOrzqqNCAPljhVyC5TjAo6BH4jKHw9NRNFNiRUY5QSgZCmVm5XILlWbisiKl+0OcS7eWihmKGS5akc2iQw==}
+ deprecated: This is a stub types definition. lz-string provides its own type definitions, so you do not need this installed.
+
'@types/node@20.19.33':
resolution: {integrity: sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==}
@@ -145,6 +179,15 @@ packages:
'@types/react@18.3.28':
resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==}
+ '@xyflow/react@12.10.0':
+ resolution: {integrity: sha512-eOtz3whDMWrB4KWVatIBrKuxECHqip6PfA8fTpaS2RUGVpiEAe+nqDKsLqkViVWxDGreq0lWX71Xth/SPAzXiw==}
+ peerDependencies:
+ react: '>=17'
+ react-dom: '>=17'
+
+ '@xyflow/system@0.0.74':
+ resolution: {integrity: sha512-7v7B/PkiVrkdZzSbL+inGAo6tkR/WQHHG0/jhSvLQToCsfa8YubOGmBYd1s08tpKpihdHDZFwzQZeR69QSBb4Q==}
+
any-promise@1.3.0:
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
@@ -178,6 +221,9 @@ packages:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
+ classcat@5.0.5:
+ resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==}
+
client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
@@ -193,6 +239,44 @@ packages:
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+ d3-color@3.1.0:
+ resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+ engines: {node: '>=12'}
+
+ d3-dispatch@3.0.1:
+ resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
+ engines: {node: '>=12'}
+
+ d3-drag@3.0.0:
+ resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
+ engines: {node: '>=12'}
+
+ d3-ease@3.0.1:
+ resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
+ engines: {node: '>=12'}
+
+ d3-interpolate@3.0.1:
+ resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+ engines: {node: '>=12'}
+
+ d3-selection@3.0.0:
+ resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
+ engines: {node: '>=12'}
+
+ d3-timer@3.0.1:
+ resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+ engines: {node: '>=12'}
+
+ d3-transition@3.0.1:
+ resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ d3-selection: 2 - 3
+
+ d3-zoom@3.0.0:
+ resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
+ engines: {node: '>=12'}
+
didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
@@ -280,6 +364,10 @@ packages:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
+ lz-string@1.5.0:
+ resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
+ hasBin: true
+
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
@@ -497,9 +585,47 @@ packages:
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+ use-sync-external-store@1.6.0:
+ resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+ zustand@4.5.7:
+ resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==}
+ engines: {node: '>=12.7.0'}
+ peerDependencies:
+ '@types/react': '>=16.8'
+ immer: '>=9.0.6'
+ react: '>=16.8'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+
+ zustand@5.0.11:
+ resolution: {integrity: sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==}
+ engines: {node: '>=12.20.0'}
+ peerDependencies:
+ '@types/react': '>=18.0.0'
+ immer: '>=9.0.6'
+ react: '>=18.0.0'
+ use-sync-external-store: '>=1.2.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+ use-sync-external-store:
+ optional: true
+
snapshots:
'@alloc/quick-lru@5.2.0': {}
@@ -566,6 +692,31 @@ snapshots:
'@swc/counter': 0.1.3
tslib: 2.8.1
+ '@types/d3-color@3.1.3': {}
+
+ '@types/d3-drag@3.0.7':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-interpolate@3.0.4':
+ dependencies:
+ '@types/d3-color': 3.1.3
+
+ '@types/d3-selection@3.0.11': {}
+
+ '@types/d3-transition@3.0.9':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-zoom@3.0.8':
+ dependencies:
+ '@types/d3-interpolate': 3.0.4
+ '@types/d3-selection': 3.0.11
+
+ '@types/lz-string@1.5.0':
+ dependencies:
+ lz-string: 1.5.0
+
'@types/node@20.19.33':
dependencies:
undici-types: 6.21.0
@@ -581,6 +732,29 @@ snapshots:
'@types/prop-types': 15.7.15
csstype: 3.2.3
+ '@xyflow/react@12.10.0(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@xyflow/system': 0.0.74
+ classcat: 5.0.5
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ zustand: 4.5.7(@types/react@18.3.28)(react@18.3.1)
+ transitivePeerDependencies:
+ - '@types/react'
+ - immer
+
+ '@xyflow/system@0.0.74':
+ dependencies:
+ '@types/d3-drag': 3.0.7
+ '@types/d3-interpolate': 3.0.4
+ '@types/d3-selection': 3.0.11
+ '@types/d3-transition': 3.0.9
+ '@types/d3-zoom': 3.0.8
+ d3-drag: 3.0.0
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-zoom: 3.0.0
+
any-promise@1.3.0: {}
anymatch@3.1.3:
@@ -616,6 +790,8 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
+ classcat@5.0.5: {}
+
client-only@0.0.1: {}
commander@4.1.1: {}
@@ -624,6 +800,42 @@ snapshots:
csstype@3.2.3: {}
+ d3-color@3.1.0: {}
+
+ d3-dispatch@3.0.1: {}
+
+ d3-drag@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-selection: 3.0.0
+
+ d3-ease@3.0.1: {}
+
+ d3-interpolate@3.0.1:
+ dependencies:
+ d3-color: 3.1.0
+
+ d3-selection@3.0.0: {}
+
+ d3-timer@3.0.1: {}
+
+ d3-transition@3.0.1(d3-selection@3.0.0):
+ dependencies:
+ d3-color: 3.1.0
+ d3-dispatch: 3.0.1
+ d3-ease: 3.0.1
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-timer: 3.0.1
+
+ d3-zoom@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-drag: 3.0.0
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+
didyoumean@1.2.2: {}
dlv@1.1.3: {}
@@ -695,6 +907,8 @@ snapshots:
dependencies:
js-tokens: 4.0.0
+ lz-string@1.5.0: {}
+
merge2@1.4.1: {}
micromatch@4.0.8:
@@ -906,4 +1120,21 @@ snapshots:
undici-types@6.21.0: {}
+ use-sync-external-store@1.6.0(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+
util-deprecate@1.0.2: {}
+
+ zustand@4.5.7(@types/react@18.3.28)(react@18.3.1):
+ dependencies:
+ use-sync-external-store: 1.6.0(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.28
+ react: 18.3.1
+
+ zustand@5.0.11(@types/react@18.3.28)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)):
+ optionalDependencies:
+ '@types/react': 18.3.28
+ react: 18.3.1
+ use-sync-external-store: 1.6.0(react@18.3.1)