@powerhousedao/knowledge-note 1.0.1 → 1.0.3

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.
Files changed (60) hide show
  1. package/dist/editors/health-report-editor/editor.d.ts.map +1 -1
  2. package/dist/editors/health-report-editor/editor.js +100 -17
  3. package/dist/editors/knowledge-note-editor/editor.d.ts.map +1 -1
  4. package/dist/editors/knowledge-note-editor/editor.js +12 -3
  5. package/dist/editors/knowledge-vault/components/DriveExplorer.d.ts.map +1 -1
  6. package/dist/editors/knowledge-vault/components/GettingStarted.js +26 -7
  7. package/dist/editors/knowledge-vault/components/GraphView.d.ts +1 -1
  8. package/dist/editors/knowledge-vault/components/GraphView.d.ts.map +1 -1
  9. package/dist/editors/knowledge-vault/components/GraphView.js +211 -33
  10. package/dist/editors/knowledge-vault/components/HealthDashboard.d.ts.map +1 -1
  11. package/dist/editors/knowledge-vault/components/HealthDashboard.js +16 -4
  12. package/dist/editors/knowledge-vault/components/NoteList.d.ts.map +1 -1
  13. package/dist/editors/knowledge-vault/components/NoteList.js +13 -16
  14. package/dist/editors/knowledge-vault/hooks/use-drive-init.d.ts.map +1 -1
  15. package/dist/editors/knowledge-vault/hooks/use-drive-init.js +14 -6
  16. package/dist/editors/moc-editor/editor.d.ts.map +1 -1
  17. package/dist/editors/moc-editor/editor.js +13 -4
  18. package/dist/editors/tension-editor/editor.d.ts.map +1 -1
  19. package/dist/editors/tension-editor/editor.js +60 -9
  20. package/dist/package.json +2 -2
  21. package/dist/processors/factory.d.ts.map +1 -1
  22. package/dist/processors/factory.js +0 -3
  23. package/dist/processors/graph-indexer/index.d.ts.map +1 -1
  24. package/dist/processors/graph-indexer/index.js +5 -8
  25. package/dist/processors/graph-indexer/query.d.ts.map +1 -1
  26. package/dist/processors/graph-indexer/query.js +2 -1
  27. package/dist/style.css +82 -0
  28. package/dist/subgraphs/index.d.ts +0 -1
  29. package/dist/subgraphs/index.d.ts.map +1 -1
  30. package/dist/subgraphs/index.js +0 -1
  31. package/dist/subgraphs/knowledge-graph/subgraph.d.ts +28 -13
  32. package/dist/subgraphs/knowledge-graph/subgraph.d.ts.map +1 -1
  33. package/dist/subgraphs/knowledge-graph/subgraph.js +144 -22
  34. package/dist/tests/processor/graph-indexer.test.js +8 -2
  35. package/dist/tests/unit/knowledge-note-reducers.test.js +4 -1
  36. package/dist/tests/unit/lifecycle-state-machine.test.js +16 -4
  37. package/dist/tests/unit/moc-reducers.test.js +1 -1
  38. package/dist/tests/unit/pipeline-queue-reducers.test.js +10 -2
  39. package/package.json +3 -3
  40. package/dist/processors/methodology-indexer/factory.d.ts +0 -4
  41. package/dist/processors/methodology-indexer/factory.d.ts.map +0 -1
  42. package/dist/processors/methodology-indexer/factory.js +0 -23
  43. package/dist/processors/methodology-indexer/index.d.ts +0 -11
  44. package/dist/processors/methodology-indexer/index.d.ts.map +0 -1
  45. package/dist/processors/methodology-indexer/index.js +0 -118
  46. package/dist/processors/methodology-indexer/migrations.d.ts +0 -4
  47. package/dist/processors/methodology-indexer/migrations.d.ts.map +0 -1
  48. package/dist/processors/methodology-indexer/migrations.js +0 -39
  49. package/dist/processors/methodology-indexer/query.d.ts +0 -35
  50. package/dist/processors/methodology-indexer/query.d.ts.map +0 -1
  51. package/dist/processors/methodology-indexer/query.js +0 -102
  52. package/dist/processors/methodology-indexer/schema.d.ts +0 -22
  53. package/dist/processors/methodology-indexer/schema.d.ts.map +0 -1
  54. package/dist/processors/methodology-indexer/schema.js +0 -1
  55. package/dist/subgraphs/methodology/index.d.ts +0 -2
  56. package/dist/subgraphs/methodology/index.d.ts.map +0 -1
  57. package/dist/subgraphs/methodology/index.js +0 -1
  58. package/dist/subgraphs/methodology/subgraph.d.ts +0 -47
  59. package/dist/subgraphs/methodology/subgraph.d.ts.map +0 -1
  60. package/dist/subgraphs/methodology/subgraph.js +0 -87
@@ -1,11 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useRef, useState, useEffect, useCallback, useMemo } from "react";
3
3
  import cytoscape from "cytoscape";
4
- // @ts-expect-error - no types available for cytoscape-cose-bilkent
5
- import coseBilkent from "cytoscape-cose-bilkent";
4
+ // @ts-expect-error - no types available for cytoscape-fcose
5
+ import fcose from "cytoscape-fcose";
6
6
  import { setSelectedNode } from "@powerhousedao/reactor-browser";
7
- // Register the cose-bilkent layout once
8
- cytoscape.use(coseBilkent);
7
+ // Register the fcose layout once
8
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
9
+ cytoscape.use(fcose);
9
10
  /* ------------------------------------------------------------------ */
10
11
  /* Constants */
11
12
  /* ------------------------------------------------------------------ */
@@ -29,13 +30,79 @@ const TENSION_EDGE_COLOR = "#ef4444";
29
30
  const DEFAULT_NODE_COLOR = "#6b7280";
30
31
  const DEFAULT_EDGE_COLOR = "#64748b";
31
32
  /* ------------------------------------------------------------------ */
33
+ /* Position persistence */
34
+ /* ------------------------------------------------------------------ */
35
+ const POSITIONS_STORAGE_KEY = "bai-graph-positions";
36
+ function loadPositions() {
37
+ try {
38
+ const raw = localStorage.getItem(POSITIONS_STORAGE_KEY);
39
+ return raw ? JSON.parse(raw) : null;
40
+ }
41
+ catch {
42
+ return null;
43
+ }
44
+ }
45
+ function savePositions(cy) {
46
+ try {
47
+ const positions = {};
48
+ cy.nodes().forEach((node) => {
49
+ const pos = node.position();
50
+ positions[node.id()] = { x: pos.x, y: pos.y };
51
+ });
52
+ localStorage.setItem(POSITIONS_STORAGE_KEY, JSON.stringify(positions));
53
+ }
54
+ catch {
55
+ // localStorage unavailable or full — silently fail
56
+ }
57
+ }
58
+ function clearStoredPositions() {
59
+ try {
60
+ localStorage.removeItem(POSITIONS_STORAGE_KEY);
61
+ }
62
+ catch {
63
+ // ignore
64
+ }
65
+ }
66
+ /* ------------------------------------------------------------------ */
32
67
  /* Build cytoscape elements from data */
33
68
  /* ------------------------------------------------------------------ */
34
69
  function buildElements(notes, graphState, mocs, tensions) {
35
70
  const elements = [];
36
71
  const noteMap = new Map(notes.map((n) => [n.id, n]));
72
+ // Build MOC membership: note → primary MOC id
73
+ const noteToMoc = new Map();
74
+ if (mocs?.length) {
75
+ const refCounts = new Map();
76
+ for (const moc of mocs) {
77
+ for (const idea of moc.coreIdeas) {
78
+ if (!refCounts.has(idea.noteRef))
79
+ refCounts.set(idea.noteRef, new Map());
80
+ const counts = refCounts.get(idea.noteRef);
81
+ counts.set(moc.id, (counts.get(moc.id) ?? 0) + 1);
82
+ }
83
+ }
84
+ for (const [noteId, counts] of refCounts) {
85
+ let bestMoc = "";
86
+ let bestCount = 0;
87
+ for (const [mocId, count] of counts) {
88
+ if (count > bestCount) {
89
+ bestMoc = mocId;
90
+ bestCount = count;
91
+ }
92
+ }
93
+ if (bestMoc)
94
+ noteToMoc.set(noteId, bestMoc);
95
+ }
96
+ }
97
+ // Helper: check if an edge crosses MOC cluster boundaries
98
+ const isCrossCluster = (sourceId, targetId) => {
99
+ const sMoc = noteToMoc.get(sourceId);
100
+ const tMoc = noteToMoc.get(targetId);
101
+ if (!sMoc && !tMoc)
102
+ return false; // both orphans — not cross-cluster
103
+ return sMoc !== tMoc;
104
+ };
37
105
  if (graphState?.nodes.length) {
38
- // Use persisted graph state
39
106
  const linkCounts = new Map();
40
107
  for (const edge of graphState.edges) {
41
108
  linkCounts.set(edge.sourceDocumentId, (linkCounts.get(edge.sourceDocumentId) ?? 0) + 1);
@@ -63,13 +130,13 @@ function buildElements(notes, graphState, mocs, tensions) {
63
130
  source: edge.sourceDocumentId,
64
131
  target: edge.targetDocumentId,
65
132
  linkType: edge.linkType ?? null,
133
+ crossCluster: isCrossCluster(edge.sourceDocumentId, edge.targetDocumentId),
66
134
  color: LINK_TYPE_COLORS[edge.linkType ?? ""] ?? DEFAULT_EDGE_COLOR,
67
135
  },
68
136
  });
69
137
  }
70
138
  }
71
139
  else {
72
- // Compute from notes
73
140
  const nodeIds = new Set(notes.map((n) => n.id));
74
141
  const edgeList = [];
75
142
  for (const note of notes) {
@@ -109,6 +176,7 @@ function buildElements(notes, graphState, mocs, tensions) {
109
176
  source: edge.source,
110
177
  target: edge.target,
111
178
  linkType: edge.linkType ?? null,
179
+ crossCluster: isCrossCluster(edge.source, edge.target),
112
180
  color: LINK_TYPE_COLORS[edge.linkType ?? ""] ?? DEFAULT_EDGE_COLOR,
113
181
  },
114
182
  });
@@ -116,7 +184,7 @@ function buildElements(notes, graphState, mocs, tensions) {
116
184
  }
117
185
  // Track existing node IDs for MOC + tension edge targets
118
186
  const existingNodeIds = new Set(elements.filter((e) => !e.data.source).map((e) => e.data.id));
119
- // Add MOC nodes and their edges to core ideas
187
+ // Add MOC nodes as compound parents + edges for non-parented refs
120
188
  if (mocs?.length) {
121
189
  for (const moc of mocs) {
122
190
  elements.push({
@@ -220,18 +288,19 @@ const cyStylesheet = [
220
288
  "transition-duration": 150,
221
289
  },
222
290
  },
223
- // MOC nodes — diamond shape, larger
291
+ // MOC nodes — diamond, prominent cluster anchors
224
292
  {
225
293
  selector: "node[?isMoc]",
226
294
  style: {
227
295
  shape: "diamond",
228
- width: 35,
229
- height: 35,
230
- "font-size": "11px",
296
+ width: 45,
297
+ height: 45,
298
+ "font-size": "12px",
231
299
  "font-weight": "bold",
232
- "border-width": 2,
300
+ "border-width": 2.5,
233
301
  "border-color": "#cba6f7",
234
- "border-opacity": 0.5,
302
+ "border-opacity": 0.7,
303
+ "text-max-width": "140px",
235
304
  },
236
305
  },
237
306
  // Tension nodes — triangle shape, red
@@ -248,6 +317,16 @@ const cyStylesheet = [
248
317
  "border-opacity": 0.5,
249
318
  },
250
319
  },
320
+ // Cross-cluster edges — very faint, don't distract from clusters
321
+ {
322
+ selector: "edge[?crossCluster]",
323
+ style: {
324
+ opacity: 0.15,
325
+ width: 0.8,
326
+ "line-style": "dotted",
327
+ "line-dash-pattern": [2, 4],
328
+ },
329
+ },
251
330
  // MOC edges — dashed
252
331
  {
253
332
  selector: "edge[linkType = 'CORE_IDEA']",
@@ -310,32 +389,79 @@ const cyStylesheet = [
310
389
  /* ------------------------------------------------------------------ */
311
390
  /* Layout options */
312
391
  /* ------------------------------------------------------------------ */
313
- function getLayoutOptions() {
392
+ function getLayoutOptions(opts) {
393
+ const saved = opts?.savedPositions;
394
+ const hasPositions = saved && Object.keys(saved).length > 0;
395
+ // All nodes have saved positions — instant preset layout
396
+ if (hasPositions && !opts?.newNodeIds?.size) {
397
+ return {
398
+ name: "preset",
399
+ positions: (node) => {
400
+ const id = node.id();
401
+ return saved[id] ?? { x: 0, y: 0 };
402
+ },
403
+ fit: false,
404
+ padding: 60,
405
+ };
406
+ }
407
+ // Some or all nodes need computation.
408
+ // Per-edge idealEdgeLength pulls MOC clusters tight and keeps
409
+ // derivation chains compact while general notes spread out.
314
410
  return {
315
- name: "cose-bilkent",
411
+ name: "fcose",
316
412
  animate: false,
317
- nodeRepulsion: 6000,
318
- idealEdgeLength: 120,
319
- edgeElasticity: 0.1,
413
+ quality: "default",
414
+ randomize: !hasPositions,
415
+ nodeRepulsion: (node) => node.data("isMoc") ? 30000 : 8000,
416
+ idealEdgeLength: (edge) => {
417
+ const lt = edge.data("linkType");
418
+ const cross = edge.data("crossCluster");
419
+ if (lt === "CORE_IDEA")
420
+ return 70;
421
+ if (cross)
422
+ return 350;
423
+ if (lt === "BUILDS_ON" || lt === "DERIVED_FROM")
424
+ return 90;
425
+ return 150;
426
+ },
427
+ edgeElasticity: (edge) => {
428
+ const lt = edge.data("linkType");
429
+ const cross = edge.data("crossCluster");
430
+ if (lt === "CORE_IDEA")
431
+ return 0.45;
432
+ if (cross)
433
+ return 0.01;
434
+ if (lt === "BUILDS_ON" || lt === "DERIVED_FROM")
435
+ return 0.3;
436
+ return 0.1;
437
+ },
320
438
  nestingFactor: 0.1,
321
- gravity: 0.25,
322
- numIter: 2500,
439
+ gravity: 0.08,
440
+ numIter: 500,
323
441
  tile: true,
324
- fit: true,
325
- padding: 40,
442
+ fit: false,
443
+ padding: 60,
444
+ // Pin existing nodes, only layout new ones
445
+ ...(hasPositions
446
+ ? {
447
+ fixedNodeConstraint: Object.entries(saved)
448
+ .filter(([id]) => !opts?.newNodeIds?.has(id))
449
+ .map(([id, pos]) => ({ nodeId: id, position: pos })),
450
+ }
451
+ : {}),
326
452
  };
327
453
  }
328
454
  /* ------------------------------------------------------------------ */
329
455
  /* Component */
330
456
  /* ------------------------------------------------------------------ */
331
- export function GraphView({ notes, graphState, mocs, tensions }) {
457
+ export function GraphView({ notes, graphState, mocs, tensions, }) {
332
458
  const containerRef = useRef(null);
333
459
  const cyRef = useRef(null);
334
460
  const [selectedDetail, setSelectedDetail] = useState(null);
335
461
  const [zoomLevel, setZoomLevel] = useState(1);
336
462
  const [hoverInfo, setHoverInfo] = useState(null);
337
463
  // Build elements from data
338
- const elements = useMemo(() => buildElements(notes, graphState ?? null, mocs, tensions), [notes, graphState]);
464
+ const elements = useMemo(() => buildElements(notes, graphState ?? null, mocs, tensions), [notes, graphState, mocs, tensions]);
339
465
  // Gather neighbor info for the detail panel
340
466
  const getNodeDetail = useCallback((nodeId) => {
341
467
  const cy = cyRef.current;
@@ -394,11 +520,21 @@ export function GraphView({ notes, graphState, mocs, tensions }) {
394
520
  useEffect(() => {
395
521
  if (!containerRef.current)
396
522
  return;
523
+ // Detect which nodes are new vs have saved positions
524
+ const savedPositions = loadPositions();
525
+ const savedNodeIds = savedPositions
526
+ ? new Set(Object.keys(savedPositions))
527
+ : new Set();
528
+ const currentNodeIds = new Set(elements.filter((e) => !e.data.source).map((e) => e.data.id));
529
+ const newNodeIds = new Set([...currentNodeIds].filter((id) => !savedNodeIds.has(id)));
397
530
  const cy = cytoscape({
398
531
  container: containerRef.current,
399
532
  elements,
400
533
  style: cyStylesheet,
401
- layout: getLayoutOptions(),
534
+ layout: getLayoutOptions({
535
+ savedPositions,
536
+ newNodeIds: newNodeIds.size > 0 ? newNodeIds : undefined,
537
+ }),
402
538
  zoom: 1,
403
539
  minZoom: 0.1,
404
540
  maxZoom: 5,
@@ -406,14 +542,51 @@ export function GraphView({ notes, graphState, mocs, tensions }) {
406
542
  boxSelectionEnabled: false,
407
543
  });
408
544
  cyRef.current = cy;
409
- // After layout settles, ensure zoom is 100% with nodes centered.
410
- // fit:true already centered the graph; now pin zoom to 1 around
411
- // the midpoint of the bounding box so pan stays correct.
412
- cy.one("layoutstop", () => {
413
- const bb = cy.elements().boundingBox();
414
- const modelCenter = { x: (bb.x1 + bb.x2) / 2, y: (bb.y1 + bb.y2) / 2 };
415
- cy.zoom({ level: 1, position: modelCenter });
545
+ // After layout settles, save positions and center view.
546
+ const centerGraph = () => {
547
+ cy.fit(cy.elements(), 60);
548
+ if (cy.zoom() > 0.8)
549
+ cy.zoom(0.8);
416
550
  cy.center();
551
+ };
552
+ cy.one("layoutstop", () => {
553
+ savePositions(cy);
554
+ centerGraph();
555
+ });
556
+ // For preset layout, also center immediately since layoutstop
557
+ // fires synchronously before the viewport may have adjusted.
558
+ requestAnimationFrame(() => centerGraph());
559
+ // Drag MOC nodes with their cluster children
560
+ const mocDragState = new Map();
561
+ cy.on("grab", "node[?isMoc]", (evt) => {
562
+ const node = evt.target;
563
+ const pos = node.position();
564
+ mocDragState.set(node.id(), { x: pos.x, y: pos.y });
565
+ });
566
+ cy.on("drag", "node[?isMoc]", (evt) => {
567
+ const moc = evt.target;
568
+ const prev = mocDragState.get(moc.id());
569
+ if (!prev)
570
+ return;
571
+ const curr = moc.position();
572
+ const dx = curr.x - prev.x;
573
+ const dy = curr.y - prev.y;
574
+ mocDragState.set(moc.id(), { x: curr.x, y: curr.y });
575
+ // Move all CORE_IDEA-connected notes along with the MOC
576
+ moc.connectedEdges().forEach((edge) => {
577
+ if (edge.data("linkType") !== "CORE_IDEA")
578
+ return;
579
+ const child = edge.source().id() === moc.id()
580
+ ? edge.target()
581
+ : edge.source();
582
+ if (child.grabbed())
583
+ return; // don't fight if user is dragging it
584
+ child.shift({ x: dx, y: dy });
585
+ });
586
+ });
587
+ // Persist position after manual drag
588
+ cy.on("dragfree", "node", () => {
589
+ savePositions(cy);
417
590
  });
418
591
  // Track zoom level for UI
419
592
  cy.on("zoom", () => {
@@ -510,7 +683,12 @@ export function GraphView({ notes, graphState, mocs, tensions }) {
510
683
  const cy = cyRef.current;
511
684
  if (!cy)
512
685
  return;
513
- cy.layout(getLayoutOptions()).run();
686
+ clearStoredPositions();
687
+ const layout = cy.layout(getLayoutOptions());
688
+ layout.run();
689
+ cy.one("layoutstop", () => {
690
+ savePositions(cy);
691
+ });
514
692
  }, []);
515
693
  if (notes.length === 0) {
516
694
  return (_jsx("div", { className: "flex h-full items-center justify-center", children: _jsx("p", { style: { color: "var(--bai-text-muted)" }, children: "No notes to display in graph" }) }));
@@ -1 +1 @@
1
- {"version":3,"file":"HealthDashboard.d.ts","sourceRoot":"","sources":["../../../../editors/knowledge-vault/components/HealthDashboard.tsx"],"names":[],"mappings":"AAqBA,wBAAgB,eAAe,4CA+iB9B"}
1
+ {"version":3,"file":"HealthDashboard.d.ts","sourceRoot":"","sources":["../../../../editors/knowledge-vault/components/HealthDashboard.tsx"],"names":[],"mappings":"AAqBA,wBAAgB,eAAe,4CA8kB9B"}
@@ -191,7 +191,14 @@ export function HealthDashboard() {
191
191
  border: "1px solid var(--bai-border)",
192
192
  }, children: [_jsx("span", { className: `rounded-full border px-4 py-1.5 text-sm font-bold ${STATUS_BADGE[agentReport?.overallStatus ?? health.overallStatus]}`, children: agentReport?.overallStatus ?? health.overallStatus }), _jsxs("div", { className: "flex-1", children: [_jsx("h2", { className: "text-lg font-bold", style: { color: "var(--bai-text)" }, children: "Vault Health" }), _jsx("p", { className: "text-xs", style: { color: "var(--bai-text-muted)" }, children: agentReport
193
193
  ? `Agent report from ${new Date(agentReport.generatedAt).toLocaleString()} by ${agentReport.generatedBy}`
194
- : "Live metrics \u2014 run /health in Claude Code for a full agent report" })] }), _jsx("span", { className: "rounded-full border px-3 py-1 text-[10px] font-medium", style: { borderColor: "var(--bai-border)", color: "var(--bai-text-muted)" }, children: agentReport ? "Agent" : "Live" })] }), agentReport?.recommendations && agentReport.recommendations.length > 0 && (_jsxs("div", { className: "rounded-xl p-4", style: { backgroundColor: "var(--bai-accent-soft)", border: "1px solid var(--bai-border)" }, children: [_jsx("h3", { className: "mb-2 text-xs font-semibold", style: { color: "var(--bai-accent)" }, children: "Agent Recommendations" }), _jsx("ul", { className: "space-y-1", children: agentReport.recommendations.map((rec, i) => (_jsxs("li", { className: "flex items-start gap-2 text-xs", style: { color: "var(--bai-text-secondary)" }, children: [_jsx("span", { className: "mt-0.5 h-1.5 w-1.5 shrink-0 rounded-full", style: { backgroundColor: "var(--bai-accent)" } }), rec] }, i))) })] })), _jsx("div", { className: "grid grid-cols-4 gap-3", children: [
194
+ : "Live metrics \u2014 run /health in Claude Code for a full agent report" })] }), _jsx("span", { className: "rounded-full border px-3 py-1 text-[10px] font-medium", style: {
195
+ borderColor: "var(--bai-border)",
196
+ color: "var(--bai-text-muted)",
197
+ }, children: agentReport ? "Agent" : "Live" })] }), agentReport?.recommendations &&
198
+ agentReport.recommendations.length > 0 && (_jsxs("div", { className: "rounded-xl p-4", style: {
199
+ backgroundColor: "var(--bai-accent-soft)",
200
+ border: "1px solid var(--bai-border)",
201
+ }, children: [_jsx("h3", { className: "mb-2 text-xs font-semibold", style: { color: "var(--bai-accent)" }, children: "Agent Recommendations" }), _jsx("ul", { className: "space-y-1", children: agentReport.recommendations.map((rec, i) => (_jsxs("li", { className: "flex items-start gap-2 text-xs", style: { color: "var(--bai-text-secondary)" }, children: [_jsx("span", { className: "mt-0.5 h-1.5 w-1.5 shrink-0 rounded-full", style: { backgroundColor: "var(--bai-accent)" } }), rec] }, i))) })] })), _jsx("div", { className: "grid grid-cols-4 gap-3", children: [
195
202
  {
196
203
  label: "Notes",
197
204
  value: agentReport?.graphMetrics?.noteCount ?? health.noteCount,
@@ -225,13 +232,18 @@ export function HealthDashboard() {
225
232
  {
226
233
  label: "Orphans",
227
234
  value: agentReport?.graphMetrics?.orphanCount ?? health.orphanCount,
228
- colorClass: (agentReport?.graphMetrics?.orphanCount ?? health.orphanCount) > 0 ? "text-amber-400" : "text-emerald-400",
235
+ colorClass: (agentReport?.graphMetrics?.orphanCount ?? health.orphanCount) >
236
+ 0
237
+ ? "text-amber-400"
238
+ : "text-emerald-400",
229
239
  colorStyle: undefined,
230
240
  },
231
241
  {
232
242
  label: "Avg Links",
233
- value: (agentReport?.graphMetrics?.averageLinksPerNote ?? health.avgLinksPerNote).toFixed(1),
234
- colorClass: (agentReport?.graphMetrics?.averageLinksPerNote ?? health.avgLinksPerNote) < 2
243
+ value: (agentReport?.graphMetrics?.averageLinksPerNote ??
244
+ health.avgLinksPerNote).toFixed(1),
245
+ colorClass: (agentReport?.graphMetrics?.averageLinksPerNote ??
246
+ health.avgLinksPerNote) < 2
235
247
  ? "text-amber-400"
236
248
  : "text-emerald-400",
237
249
  colorStyle: undefined,
@@ -1 +1 @@
1
- {"version":3,"file":"NoteList.d.ts","sourceRoot":"","sources":["../../../../editors/knowledge-vault/components/NoteList.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAEzE,KAAK,aAAa,GAAG;IACnB,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC5B,CAAC;AAyBF,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,EAAE,aAAa,2CA0BhD"}
1
+ {"version":3,"file":"NoteList.d.ts","sourceRoot":"","sources":["../../../../editors/knowledge-vault/components/NoteList.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAEzE,KAAK,aAAa,GAAG;IACnB,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC5B,CAAC;AA2BF,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,EAAE,aAAa,2CAgDhD"}
@@ -1,5 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { setSelectedNode } from "@powerhousedao/reactor-browser";
3
+ import { memo, useState } from "react";
4
+ const PAGE_SIZE = 30;
3
5
  const STATUS_BADGE_STYLES = {
4
6
  DRAFT: {
5
7
  background: "rgba(245, 158, 11, 0.2)",
@@ -23,31 +25,26 @@ const STATUS_BADGE_STYLES = {
23
25
  },
24
26
  };
25
27
  export function NoteList({ notes }) {
28
+ const [visibleCount, setVisibleCount] = useState(PAGE_SIZE);
26
29
  if (notes.length === 0) {
27
30
  return (_jsx("div", { className: "flex h-full items-center justify-center", children: _jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-lg", style: { color: "var(--bai-text-muted)" }, children: "No knowledge notes yet" }), _jsx("p", { className: "mt-1 text-sm", style: { color: "var(--bai-text-faint)" }, children: "Create your first note to get started" })] }) }));
28
31
  }
29
- return (_jsx("div", { className: "grid gap-3 p-4 sm:grid-cols-2 lg:grid-cols-3", children: notes.map((note) => (_jsx(NoteCard, { note: note }, note.id))) }));
32
+ const hasMore = visibleCount < notes.length;
33
+ return (_jsxs("div", { className: "p-4", children: [_jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-3", children: notes.slice(0, visibleCount).map((note) => (_jsx(NoteCard, { note: note }, note.id))) }), hasMore && (_jsx("div", { className: "mt-4 flex justify-center", children: _jsxs("button", { type: "button", onClick: () => setVisibleCount((c) => c + PAGE_SIZE), className: "rounded-lg border px-4 py-2 text-sm transition-colors", style: {
34
+ borderColor: "var(--bai-border)",
35
+ color: "var(--bai-text-muted)",
36
+ background: "var(--bai-surface)",
37
+ }, children: ["Show more (", notes.length - visibleCount, " remaining)"] }) }))] }));
30
38
  }
31
- function NoteCard({ note }) {
39
+ const NoteCard = memo(function NoteCard({ note }) {
32
40
  const title = note.title ?? note.name;
33
41
  const status = note.status ?? "DRAFT";
34
42
  const badgeStyle = STATUS_BADGE_STYLES[status] ?? STATUS_BADGE_STYLES.DRAFT;
35
- return (_jsxs("button", { type: "button", onClick: () => setSelectedNode(note.id), className: "note-card group flex flex-col rounded-xl border p-4 text-left transition-all", style: {
36
- background: "var(--bai-surface)",
37
- borderColor: "var(--bai-border)",
38
- }, children: [_jsxs("div", { className: "mb-2 flex items-start justify-between gap-2", children: [_jsx("h3", { className: "note-card-title flex-1 truncate text-sm font-semibold", style: { color: "var(--bai-text)" }, children: title }), _jsx("span", { className: "shrink-0 rounded-full border px-2 py-0.5 text-[10px] font-medium", style: badgeStyle, children: status.replace("_", " ") })] }), note.description && (_jsx("p", { className: "mb-3 line-clamp-2 text-xs leading-relaxed", style: { color: "var(--bai-text-muted)" }, children: note.description })), _jsxs("div", { className: "mt-auto flex flex-wrap items-center gap-1.5 pt-2", children: [note.noteType && (_jsx("span", { className: "rounded px-1.5 py-0.5 text-[10px] font-medium", style: {
43
+ return (_jsxs("button", { type: "button", onClick: () => setSelectedNode(note.id), className: "group flex flex-col rounded-xl border border-[var(--bai-border)] bg-[var(--bai-surface)] p-4 text-left transition-all hover:border-[var(--bai-accent)] hover:bg-[var(--bai-hover)]", children: [_jsxs("div", { className: "mb-2 flex items-start justify-between gap-2", children: [_jsx("h3", { className: "flex-1 truncate text-sm font-semibold text-[var(--bai-text)] group-hover:text-[var(--bai-accent)]", children: title }), _jsx("span", { className: "shrink-0 rounded-full border px-2 py-0.5 text-[10px] font-medium", style: badgeStyle, children: status.replace("_", " ") })] }), note.description && (_jsx("p", { className: "mb-3 line-clamp-2 text-xs leading-relaxed", style: { color: "var(--bai-text-muted)" }, children: note.description })), _jsxs("div", { className: "mt-auto flex flex-wrap items-center gap-1.5 pt-2", children: [note.noteType && (_jsx("span", { className: "rounded px-1.5 py-0.5 text-[10px] font-medium", style: {
39
44
  background: "var(--bai-hover)",
40
45
  color: "var(--bai-text-muted)",
41
46
  }, children: note.noteType })), note.topics.slice(0, 3).map((t) => (_jsxs("span", { className: "text-[10px]", style: { color: "var(--bai-accent)", opacity: 0.6 }, children: ["#", t.name] }, t.id))), note.links.length > 0 && (_jsxs("span", { className: "ml-auto text-[10px]", style: { color: "var(--bai-text-faint)" }, children: [note.links.length, " link", note.links.length !== 1 ? "s" : ""] }))] }), note.provenance?.author && (_jsxs("div", { className: "mt-2 border-t pt-2 text-[10px]", style: {
42
47
  borderColor: "var(--bai-border)",
43
48
  color: "var(--bai-text-faint)",
44
- }, children: ["by ", note.provenance.author, note.provenance.updatedAt && (_jsxs("span", { children: [" \u00b7 ", new Date(note.provenance.updatedAt).toLocaleDateString()] }))] })), _jsx("style", { children: `
45
- .note-card:hover {
46
- background: var(--bai-hover) !important;
47
- border-color: var(--bai-accent) !important;
48
- }
49
- .note-card:hover .note-card-title {
50
- color: var(--bai-accent) !important;
51
- }
52
- ` })] }));
53
- }
49
+ }, children: ["by ", note.provenance.author, note.provenance.updatedAt && (_jsxs("span", { children: [" \u00b7 ", new Date(note.provenance.updatedAt).toLocaleDateString()] }))] }))] }));
50
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"use-drive-init.d.ts","sourceRoot":"","sources":["../../../../editors/knowledge-vault/hooks/use-drive-init.ts"],"names":[],"mappings":"AA2DA,wBAAgB,YAAY,SAqB3B;AAsFD;;;GAGG;AACH,wBAAgB,YAAY,wBAI3B"}
1
+ {"version":3,"file":"use-drive-init.d.ts","sourceRoot":"","sources":["../../../../editors/knowledge-vault/hooks/use-drive-init.ts"],"names":[],"mappings":"AA6DA,wBAAgB,YAAY,SAsB3B;AAsGD;;;GAGG;AACH,wBAAgB,YAAY,wBAI3B"}
@@ -14,7 +14,6 @@ import { useSelectedDrive, useNodesInSelectedDrive, addDocument, addFolder, } fr
14
14
  * /ops/queue/ <- pipeline queue singleton
15
15
  * /self/ <- system identity & config
16
16
  * /self/methodology/ <- methodology notes
17
- * /research/ <- bundled research claims
18
17
  *
19
18
  * Pattern: follows contributor-billing's proven approach —
20
19
  * module-level tracking Set per drive prevents duplicates,
@@ -35,10 +34,13 @@ const FOLDERS = [
35
34
  { name: "queue", parentPath: "ops" },
36
35
  { name: "self" },
37
36
  { name: "methodology", parentPath: "self" },
38
- { name: "research" },
39
37
  ];
40
38
  const SINGLETONS = [
41
- { name: "PipelineQueue", type: "bai/pipeline-queue", folderPath: "ops/queue" },
39
+ {
40
+ name: "PipelineQueue",
41
+ type: "bai/pipeline-queue",
42
+ folderPath: "ops/queue",
43
+ },
42
44
  { name: "HealthReport", type: "bai/health-report", folderPath: "ops/health" },
43
45
  { name: "KnowledgeGraph", type: "bai/knowledge-graph", folderPath: "self" },
44
46
  { name: "VaultConfig", type: "bai/vault-config", folderPath: "self" },
@@ -70,11 +72,15 @@ async function initDrive(driveId, existingNodes) {
70
72
  const existingFolderMap = buildExistingFolderMap(existingNodes);
71
73
  const folderIds = new Map(existingFolderMap);
72
74
  for (const folder of FOLDERS) {
73
- const path = folder.parentPath ? `${folder.parentPath}/${folder.name}` : folder.name;
75
+ const path = folder.parentPath
76
+ ? `${folder.parentPath}/${folder.name}`
77
+ : folder.name;
74
78
  if (folderIds.has(path)) {
75
79
  continue;
76
80
  }
77
- const parentId = folder.parentPath ? folderIds.get(folder.parentPath) : undefined;
81
+ const parentId = folder.parentPath
82
+ ? folderIds.get(folder.parentPath)
83
+ : undefined;
78
84
  try {
79
85
  const result = await addFolder(driveId, folder.name, parentId);
80
86
  folderIds.set(path, result.id);
@@ -124,7 +130,9 @@ function buildExistingFolderMap(nodes) {
124
130
  let current = folder;
125
131
  while (current) {
126
132
  pathParts.unshift(current.name);
127
- current = current.parentFolder ? folderById.get(current.parentFolder) : undefined;
133
+ current = current.parentFolder
134
+ ? folderById.get(current.parentFolder)
135
+ : undefined;
128
136
  }
129
137
  map.set(pathParts.join("/"), folder.id);
130
138
  }
@@ -1 +1 @@
1
- {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../../editors/moc-editor/editor.tsx"],"names":[],"mappings":"AAmBA,MAAM,CAAC,OAAO,UAAU,MAAM,4CAmV7B"}
1
+ {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../../editors/moc-editor/editor.tsx"],"names":[],"mappings":"AAuBA,MAAM,CAAC,OAAO,UAAU,MAAM,4CA6X7B"}
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState, useCallback } from "react";
3
3
  import { generateId } from "document-model/core";
4
4
  import { DocumentToolbar } from "@powerhousedao/design-system/connect";
5
+ import { setSelectedNode, useDocumentsInSelectedDrive, } from "@powerhousedao/reactor-browser";
5
6
  import { useSelectedMocDocument, actions, } from "@powerhousedao/knowledge-note/document-models/moc";
6
7
  import { TOOLBAR_CLASS } from "../shared/theme-context.js";
7
8
  const TIERS = ["HUB", "DOMAIN", "TOPIC"];
@@ -16,6 +17,7 @@ function ts() {
16
17
  export default function Editor() {
17
18
  const [document, dispatch] = useSelectedMocDocument();
18
19
  const state = document.state.global;
20
+ const allDocs = useDocumentsInSelectedDrive();
19
21
  const [newQuestion, setNewQuestion] = useState("");
20
22
  const [newIdeaRef, setNewIdeaRef] = useState("");
21
23
  const [newIdeaPhrase, setNewIdeaPhrase] = useState("");
@@ -47,10 +49,17 @@ export default function Editor() {
47
49
  } })] }), _jsxs("div", { className: "grid grid-cols-2 gap-6", children: [_jsxs("div", { className: "rounded-xl p-6", style: {
48
50
  backgroundColor: "var(--bai-surface)",
49
51
  border: "1px solid var(--bai-border)",
50
- }, children: [_jsxs("h3", { className: "mb-3 text-xs font-semibold uppercase tracking-wider", style: { color: "var(--bai-text-muted)" }, children: ["Core Ideas (", (state.coreIdeas ?? []).length, ")"] }), _jsxs("div", { className: "space-y-2", children: [(state.coreIdeas ?? []).map((idea) => (_jsxs("div", { className: "group flex items-start gap-2 rounded-lg px-3 py-2", style: {
51
- backgroundColor: "var(--bai-bg)",
52
- boxShadow: "0 0 0 1px var(--bai-ring)",
53
- }, children: [_jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("p", { className: "text-xs", style: { color: "var(--bai-text-secondary)" }, children: idea.contextPhrase }), _jsx("p", { className: "text-[10px] font-mono mt-0.5", style: { color: "var(--bai-text-faint)" }, children: idea.noteRef?.slice(0, 12) })] }), _jsx("button", { type: "button", onClick: () => dispatch(actions.removeCoreIdea({ id: idea.id })), className: "opacity-0 hover:text-red-400 group-hover:opacity-100", style: { color: "var(--bai-text-faint)" }, children: "\u00D7" })] }, idea.id))), _jsxs("form", { className: "flex gap-2", onSubmit: (e) => {
52
+ }, children: [_jsxs("h3", { className: "mb-3 text-xs font-semibold uppercase tracking-wider", style: { color: "var(--bai-text-muted)" }, children: ["Core Ideas (", (state.coreIdeas ?? []).length, ")"] }), _jsxs("div", { className: "space-y-2", children: [(state.coreIdeas ?? []).map((idea) => {
53
+ const linkedDoc = (allDocs ?? []).find((d) => d.header.id === idea.noteRef);
54
+ const noteTitle = linkedDoc
55
+ ? (linkedDoc.state.global.title ?? linkedDoc.header.name)
56
+ : null;
57
+ return (_jsxs("div", { className: "group flex items-start gap-2 rounded-lg px-3 py-2", style: {
58
+ backgroundColor: "var(--bai-bg)",
59
+ boxShadow: "0 0 0 1px var(--bai-ring)",
60
+ }, children: [_jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("p", { className: "text-xs", style: { color: "var(--bai-text-secondary)" }, children: idea.contextPhrase }), idea.noteRef &&
61
+ (noteTitle ? (_jsxs("button", { type: "button", onClick: () => setSelectedNode(idea.noteRef), className: "mt-0.5 text-[10px] text-left truncate max-w-full transition-colors hover:underline", style: { color: "var(--bai-accent)" }, title: `Open note: ${noteTitle}`, children: [noteTitle, _jsxs("svg", { className: "ml-1 inline h-2.5 w-2.5", style: { color: "var(--bai-text-faint)" }, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("path", { d: "M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" }), _jsx("path", { d: "M15 3h6v6" }), _jsx("path", { d: "M10 14L21 3" })] })] })) : (_jsx("p", { className: "text-[10px] font-mono mt-0.5", style: { color: "var(--bai-text-faint)" }, children: idea.noteRef.slice(0, 12) })))] }), _jsx("button", { type: "button", onClick: () => dispatch(actions.removeCoreIdea({ id: idea.id })), className: "opacity-0 hover:text-red-400 group-hover:opacity-100", style: { color: "var(--bai-text-faint)" }, children: "\u00D7" })] }, idea.id));
62
+ }), _jsxs("form", { className: "flex gap-2", onSubmit: (e) => {
54
63
  e.preventDefault();
55
64
  if (!newIdeaRef.trim() || !newIdeaPhrase.trim())
56
65
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../../editors/tension-editor/editor.tsx"],"names":[],"mappings":"AAsBA,MAAM,CAAC,OAAO,UAAU,MAAM,4CA+H7B"}
1
+ {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../../editors/tension-editor/editor.tsx"],"names":[],"mappings":"AAsBA,MAAM,CAAC,OAAO,UAAU,MAAM,4CAyO7B"}