granite-mem 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +124 -207
- package/dist/index.js +4997 -1444
- package/dist/public/app.js +17 -588
- package/dist/public/graph-engine.js +102 -0
- package/dist/public/index.html +117 -126
- package/dist/public/markdown-renderer.js +1 -189
- package/dist/public/style.css +1 -923
- package/dist/templates/founder-os.yml +204 -0
- package/package.json +9 -3
- package/templates/founder-os.yml +204 -0
- package/dist/public/canvas-renderer.js +0 -524
- package/dist/public/graph.js +0 -380
package/dist/public/graph.js
DELETED
|
@@ -1,380 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Granite — Force-Directed Graph Visualization
|
|
3
|
-
*
|
|
4
|
-
* Interactive canvas graph showing note connections.
|
|
5
|
-
* Physics: repulsion between all nodes, attraction along edges, gravity to center.
|
|
6
|
-
* Interactions: drag, zoom, pan, click-to-navigate, hover highlight.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const GraphEngine = (() => {
|
|
10
|
-
|
|
11
|
-
// ── Type colors ──
|
|
12
|
-
const TYPE_COLORS = {
|
|
13
|
-
fleeting: { fill: '#fbbf24', glow: 'rgba(251, 191, 36, 0.3)' },
|
|
14
|
-
permanent: { fill: '#a5b4fc', glow: 'rgba(165, 180, 252, 0.3)' },
|
|
15
|
-
reference: { fill: '#34d399', glow: 'rgba(52, 211, 153, 0.3)' },
|
|
16
|
-
person: { fill: '#f9a8d4', glow: 'rgba(249, 168, 212, 0.3)' },
|
|
17
|
-
meeting: { fill: '#93c5fd', glow: 'rgba(147, 197, 253, 0.3)' },
|
|
18
|
-
project: { fill: '#c4b5fd', glow: 'rgba(196, 181, 253, 0.3)' },
|
|
19
|
-
decision: { fill: '#fca5a5', glow: 'rgba(252, 165, 165, 0.3)' },
|
|
20
|
-
_default: { fill: '#6e6e7a', glow: 'rgba(110, 110, 122, 0.3)' },
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const BG_COLOR = '#08080a';
|
|
24
|
-
const EDGE_COLOR = 'rgba(70, 70, 85, 0.35)';
|
|
25
|
-
const EDGE_HIGHLIGHT = 'rgba(165, 180, 252, 0.6)';
|
|
26
|
-
const LABEL_COLOR = '#b0b0b8';
|
|
27
|
-
const LABEL_DIM = 'rgba(110, 110, 122, 0.3)';
|
|
28
|
-
const LABEL_FONT = '11px "Instrument Sans", sans-serif';
|
|
29
|
-
const NODE_ACTIVE_RING = '#6366f1';
|
|
30
|
-
|
|
31
|
-
// ── State ──
|
|
32
|
-
let canvas, ctx;
|
|
33
|
-
let nodes = [], edges = [];
|
|
34
|
-
let width, height;
|
|
35
|
-
let transform = { x: 0, y: 0, scale: 1 };
|
|
36
|
-
let animFrame = null;
|
|
37
|
-
let hoveredNode = null;
|
|
38
|
-
let dragNode = null;
|
|
39
|
-
let isPanning = false;
|
|
40
|
-
let panStart = { x: 0, y: 0 };
|
|
41
|
-
let activeSlug = null;
|
|
42
|
-
let onNavigate = null;
|
|
43
|
-
let isRunning = false;
|
|
44
|
-
let simulationAlpha = 1;
|
|
45
|
-
|
|
46
|
-
// ── Physics constants ──
|
|
47
|
-
const REPULSION = 800;
|
|
48
|
-
const ATTRACTION = 0.008;
|
|
49
|
-
const GRAVITY = 0.02;
|
|
50
|
-
const DAMPING = 0.88;
|
|
51
|
-
const MIN_ALPHA = 0.001;
|
|
52
|
-
const VELOCITY_LIMIT = 8;
|
|
53
|
-
|
|
54
|
-
function getTypeColor(type) {
|
|
55
|
-
return TYPE_COLORS[type] || TYPE_COLORS._default;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function init(canvasEl, graphData, options = {}) {
|
|
59
|
-
canvas = canvasEl;
|
|
60
|
-
ctx = canvas.getContext('2d');
|
|
61
|
-
onNavigate = options.onNavigate || null;
|
|
62
|
-
activeSlug = options.activeSlug || null;
|
|
63
|
-
|
|
64
|
-
// Build nodes
|
|
65
|
-
const connectionCount = {};
|
|
66
|
-
for (const e of graphData.edges) {
|
|
67
|
-
connectionCount[e.source] = (connectionCount[e.source] || 0) + 1;
|
|
68
|
-
connectionCount[e.target] = (connectionCount[e.target] || 0) + 1;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
nodes = graphData.nodes.map((n, i) => {
|
|
72
|
-
const connections = connectionCount[n.slug] || 0;
|
|
73
|
-
const angle = (i / graphData.nodes.length) * Math.PI * 2;
|
|
74
|
-
const spread = Math.min(300, graphData.nodes.length * 15);
|
|
75
|
-
return {
|
|
76
|
-
...n,
|
|
77
|
-
x: Math.cos(angle) * spread * (0.5 + Math.random() * 0.5),
|
|
78
|
-
y: Math.sin(angle) * spread * (0.5 + Math.random() * 0.5),
|
|
79
|
-
vx: 0, vy: 0,
|
|
80
|
-
radius: Math.max(4, Math.min(14, 4 + connections * 1.5)),
|
|
81
|
-
connections,
|
|
82
|
-
};
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// Build edge references
|
|
86
|
-
const nodeMap = {};
|
|
87
|
-
nodes.forEach(n => nodeMap[n.slug] = n);
|
|
88
|
-
edges = graphData.edges
|
|
89
|
-
.filter(e => nodeMap[e.source] && nodeMap[e.target])
|
|
90
|
-
.map(e => ({ source: nodeMap[e.source], target: nodeMap[e.target] }));
|
|
91
|
-
|
|
92
|
-
// Reset
|
|
93
|
-
transform = { x: 0, y: 0, scale: 1 };
|
|
94
|
-
simulationAlpha = 1;
|
|
95
|
-
|
|
96
|
-
resize();
|
|
97
|
-
setupEvents();
|
|
98
|
-
start();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function resize() {
|
|
102
|
-
const dpr = window.devicePixelRatio || 1;
|
|
103
|
-
const container = canvas.parentElement;
|
|
104
|
-
width = container.clientWidth;
|
|
105
|
-
height = container.clientHeight;
|
|
106
|
-
canvas.width = width * dpr;
|
|
107
|
-
canvas.height = height * dpr;
|
|
108
|
-
canvas.style.width = width + 'px';
|
|
109
|
-
canvas.style.height = height + 'px';
|
|
110
|
-
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// ── Physics simulation ──
|
|
114
|
-
function simulate() {
|
|
115
|
-
if (simulationAlpha < MIN_ALPHA) return;
|
|
116
|
-
|
|
117
|
-
// Repulsion (all pairs)
|
|
118
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
119
|
-
for (let j = i + 1; j < nodes.length; j++) {
|
|
120
|
-
const a = nodes[i], b = nodes[j];
|
|
121
|
-
let dx = b.x - a.x;
|
|
122
|
-
let dy = b.y - a.y;
|
|
123
|
-
let dist = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
124
|
-
const force = REPULSION / (dist * dist) * simulationAlpha;
|
|
125
|
-
const fx = (dx / dist) * force;
|
|
126
|
-
const fy = (dy / dist) * force;
|
|
127
|
-
a.vx -= fx; a.vy -= fy;
|
|
128
|
-
b.vx += fx; b.vy += fy;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Attraction (edges)
|
|
133
|
-
for (const edge of edges) {
|
|
134
|
-
const dx = edge.target.x - edge.source.x;
|
|
135
|
-
const dy = edge.target.y - edge.source.y;
|
|
136
|
-
const dist = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
137
|
-
const force = dist * ATTRACTION * simulationAlpha;
|
|
138
|
-
const fx = (dx / dist) * force;
|
|
139
|
-
const fy = (dy / dist) * force;
|
|
140
|
-
edge.source.vx += fx; edge.source.vy += fy;
|
|
141
|
-
edge.target.vx -= fx; edge.target.vy -= fy;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Gravity toward center
|
|
145
|
-
for (const node of nodes) {
|
|
146
|
-
node.vx -= node.x * GRAVITY * simulationAlpha;
|
|
147
|
-
node.vy -= node.y * GRAVITY * simulationAlpha;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Integrate
|
|
151
|
-
for (const node of nodes) {
|
|
152
|
-
if (node === dragNode) continue;
|
|
153
|
-
node.vx *= DAMPING;
|
|
154
|
-
node.vy *= DAMPING;
|
|
155
|
-
// Clamp velocity
|
|
156
|
-
const speed = Math.sqrt(node.vx * node.vx + node.vy * node.vy);
|
|
157
|
-
if (speed > VELOCITY_LIMIT) {
|
|
158
|
-
node.vx = (node.vx / speed) * VELOCITY_LIMIT;
|
|
159
|
-
node.vy = (node.vy / speed) * VELOCITY_LIMIT;
|
|
160
|
-
}
|
|
161
|
-
node.x += node.vx;
|
|
162
|
-
node.y += node.vy;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
simulationAlpha *= 0.995;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// ── Render ──
|
|
169
|
-
function draw() {
|
|
170
|
-
ctx.clearRect(0, 0, width, height);
|
|
171
|
-
ctx.fillStyle = BG_COLOR;
|
|
172
|
-
ctx.fillRect(0, 0, width, height);
|
|
173
|
-
|
|
174
|
-
ctx.save();
|
|
175
|
-
ctx.translate(width / 2 + transform.x, height / 2 + transform.y);
|
|
176
|
-
ctx.scale(transform.scale, transform.scale);
|
|
177
|
-
|
|
178
|
-
const hoveredNeighbors = new Set();
|
|
179
|
-
if (hoveredNode) {
|
|
180
|
-
for (const e of edges) {
|
|
181
|
-
if (e.source === hoveredNode) hoveredNeighbors.add(e.target);
|
|
182
|
-
if (e.target === hoveredNode) hoveredNeighbors.add(e.source);
|
|
183
|
-
}
|
|
184
|
-
hoveredNeighbors.add(hoveredNode);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const dimming = hoveredNode !== null;
|
|
188
|
-
|
|
189
|
-
// Draw edges
|
|
190
|
-
for (const edge of edges) {
|
|
191
|
-
const isHighlighted = hoveredNode && (edge.source === hoveredNode || edge.target === hoveredNode);
|
|
192
|
-
ctx.strokeStyle = isHighlighted ? EDGE_HIGHLIGHT : (dimming ? 'rgba(50, 50, 60, 0.15)' : EDGE_COLOR);
|
|
193
|
-
ctx.lineWidth = isHighlighted ? 1.5 : 0.75;
|
|
194
|
-
ctx.beginPath();
|
|
195
|
-
ctx.moveTo(edge.source.x, edge.source.y);
|
|
196
|
-
ctx.lineTo(edge.target.x, edge.target.y);
|
|
197
|
-
ctx.stroke();
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Draw nodes
|
|
201
|
-
for (const node of nodes) {
|
|
202
|
-
const colors = getTypeColor(node.type);
|
|
203
|
-
const isNeighbor = hoveredNeighbors.has(node);
|
|
204
|
-
const isDimmed = dimming && !isNeighbor;
|
|
205
|
-
const isActive = node.slug === activeSlug;
|
|
206
|
-
|
|
207
|
-
// Glow for hovered/active
|
|
208
|
-
if ((node === hoveredNode || isActive) && !isDimmed) {
|
|
209
|
-
ctx.fillStyle = colors.glow;
|
|
210
|
-
ctx.beginPath();
|
|
211
|
-
ctx.arc(node.x, node.y, node.radius + 6, 0, Math.PI * 2);
|
|
212
|
-
ctx.fill();
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Active ring
|
|
216
|
-
if (isActive) {
|
|
217
|
-
ctx.strokeStyle = NODE_ACTIVE_RING;
|
|
218
|
-
ctx.lineWidth = 2;
|
|
219
|
-
ctx.beginPath();
|
|
220
|
-
ctx.arc(node.x, node.y, node.radius + 3, 0, Math.PI * 2);
|
|
221
|
-
ctx.stroke();
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Node circle
|
|
225
|
-
ctx.globalAlpha = isDimmed ? 0.15 : 1;
|
|
226
|
-
ctx.fillStyle = colors.fill;
|
|
227
|
-
ctx.beginPath();
|
|
228
|
-
ctx.arc(node.x, node.y, node.radius, 0, Math.PI * 2);
|
|
229
|
-
ctx.fill();
|
|
230
|
-
|
|
231
|
-
// Label
|
|
232
|
-
ctx.font = LABEL_FONT;
|
|
233
|
-
ctx.fillStyle = isDimmed ? LABEL_DIM : LABEL_COLOR;
|
|
234
|
-
ctx.textAlign = 'center';
|
|
235
|
-
ctx.textBaseline = 'top';
|
|
236
|
-
const label = node.title.length > 24 ? node.title.slice(0, 22) + '…' : node.title;
|
|
237
|
-
ctx.fillText(label, node.x, node.y + node.radius + 5);
|
|
238
|
-
ctx.globalAlpha = 1;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
ctx.restore();
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function tick() {
|
|
245
|
-
simulate();
|
|
246
|
-
draw();
|
|
247
|
-
if (isRunning) animFrame = requestAnimationFrame(tick);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function start() {
|
|
251
|
-
if (isRunning) return;
|
|
252
|
-
isRunning = true;
|
|
253
|
-
tick();
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function stop() {
|
|
257
|
-
isRunning = false;
|
|
258
|
-
if (animFrame) cancelAnimationFrame(animFrame);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// ── Coordinate transforms ──
|
|
262
|
-
function screenToWorld(sx, sy) {
|
|
263
|
-
return {
|
|
264
|
-
x: (sx - width / 2 - transform.x) / transform.scale,
|
|
265
|
-
y: (sy - height / 2 - transform.y) / transform.scale,
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function findNodeAt(sx, sy) {
|
|
270
|
-
const { x, y } = screenToWorld(sx, sy);
|
|
271
|
-
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
272
|
-
const n = nodes[i];
|
|
273
|
-
const dx = n.x - x, dy = n.y - y;
|
|
274
|
-
if (dx * dx + dy * dy < (n.radius + 4) * (n.radius + 4)) return n;
|
|
275
|
-
}
|
|
276
|
-
return null;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// ── Events ──
|
|
280
|
-
function setupEvents() {
|
|
281
|
-
canvas.addEventListener('mousedown', (e) => {
|
|
282
|
-
const rect = canvas.getBoundingClientRect();
|
|
283
|
-
const sx = e.clientX - rect.left, sy = e.clientY - rect.top;
|
|
284
|
-
const node = findNodeAt(sx, sy);
|
|
285
|
-
if (node) {
|
|
286
|
-
dragNode = node;
|
|
287
|
-
dragNode.vx = 0;
|
|
288
|
-
dragNode.vy = 0;
|
|
289
|
-
simulationAlpha = Math.max(simulationAlpha, 0.3);
|
|
290
|
-
} else {
|
|
291
|
-
isPanning = true;
|
|
292
|
-
panStart = { x: e.clientX - transform.x, y: e.clientY - transform.y };
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
canvas.addEventListener('mousemove', (e) => {
|
|
297
|
-
const rect = canvas.getBoundingClientRect();
|
|
298
|
-
const sx = e.clientX - rect.left, sy = e.clientY - rect.top;
|
|
299
|
-
|
|
300
|
-
if (dragNode) {
|
|
301
|
-
const { x, y } = screenToWorld(sx, sy);
|
|
302
|
-
dragNode.x = x;
|
|
303
|
-
dragNode.y = y;
|
|
304
|
-
simulationAlpha = Math.max(simulationAlpha, 0.1);
|
|
305
|
-
} else if (isPanning) {
|
|
306
|
-
transform.x = e.clientX - panStart.x;
|
|
307
|
-
transform.y = e.clientY - panStart.y;
|
|
308
|
-
} else {
|
|
309
|
-
const node = findNodeAt(sx, sy);
|
|
310
|
-
if (node !== hoveredNode) {
|
|
311
|
-
hoveredNode = node;
|
|
312
|
-
canvas.style.cursor = node ? 'pointer' : 'grab';
|
|
313
|
-
// Show tooltip
|
|
314
|
-
const tooltip = document.getElementById('graph-tooltip');
|
|
315
|
-
if (node) {
|
|
316
|
-
tooltip.textContent = node.title;
|
|
317
|
-
tooltip.style.left = (e.clientX + 12) + 'px';
|
|
318
|
-
tooltip.style.top = (e.clientY - 8) + 'px';
|
|
319
|
-
tooltip.style.opacity = '1';
|
|
320
|
-
} else {
|
|
321
|
-
tooltip.style.opacity = '0';
|
|
322
|
-
}
|
|
323
|
-
} else if (node) {
|
|
324
|
-
const tooltip = document.getElementById('graph-tooltip');
|
|
325
|
-
tooltip.style.left = (e.clientX + 12) + 'px';
|
|
326
|
-
tooltip.style.top = (e.clientY - 8) + 'px';
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
canvas.addEventListener('mouseup', (e) => {
|
|
332
|
-
if (dragNode) {
|
|
333
|
-
// If barely moved, treat as click
|
|
334
|
-
const rect = canvas.getBoundingClientRect();
|
|
335
|
-
const node = findNodeAt(e.clientX - rect.left, e.clientY - rect.top);
|
|
336
|
-
if (node && node === dragNode && onNavigate) {
|
|
337
|
-
onNavigate(node.slug);
|
|
338
|
-
}
|
|
339
|
-
dragNode = null;
|
|
340
|
-
}
|
|
341
|
-
isPanning = false;
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
canvas.addEventListener('mouseleave', () => {
|
|
345
|
-
hoveredNode = null;
|
|
346
|
-
isPanning = false;
|
|
347
|
-
dragNode = null;
|
|
348
|
-
document.getElementById('graph-tooltip').style.opacity = '0';
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
canvas.addEventListener('wheel', (e) => {
|
|
352
|
-
e.preventDefault();
|
|
353
|
-
const scaleFactor = e.deltaY > 0 ? 0.92 : 1.08;
|
|
354
|
-
const newScale = Math.max(0.1, Math.min(5, transform.scale * scaleFactor));
|
|
355
|
-
|
|
356
|
-
// Zoom toward cursor
|
|
357
|
-
const rect = canvas.getBoundingClientRect();
|
|
358
|
-
const mx = e.clientX - rect.left - width / 2;
|
|
359
|
-
const my = e.clientY - rect.top - height / 2;
|
|
360
|
-
transform.x = mx - (mx - transform.x) * (newScale / transform.scale);
|
|
361
|
-
transform.y = my - (my - transform.y) * (newScale / transform.scale);
|
|
362
|
-
transform.scale = newScale;
|
|
363
|
-
}, { passive: false });
|
|
364
|
-
|
|
365
|
-
// Resize observer
|
|
366
|
-
const ro = new ResizeObserver(() => { resize(); });
|
|
367
|
-
ro.observe(canvas.parentElement);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
function setActiveSlug(slug) {
|
|
371
|
-
activeSlug = slug;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
function reheat() {
|
|
375
|
-
simulationAlpha = 0.5;
|
|
376
|
-
if (!isRunning) start();
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
return { init, start, stop, resize, setActiveSlug, reheat, draw };
|
|
380
|
-
})();
|