Skip to content

ANG-004: Structural note graph with Cytoscape.js + fCoSE webview rendering#6

Open
yugalkaushik wants to merge 3 commits into
devfrom
ANG-004
Open

ANG-004: Structural note graph with Cytoscape.js + fCoSE webview rendering#6
yugalkaushik wants to merge 3 commits into
devfrom
ANG-004

Conversation

@yugalkaushik

@yugalkaushik yugalkaushik commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

Renders an interactive graph inside a Joplin panel showing explicit note-to-note links as solid arrowed edges and shared tag relationships as dotted edges.

  • Bundled Cytoscape.js and fCoSE layout for the browser webview via a separate webpack config targeting web
  • Built graph builder that assembles nodes and edges into Cytoscape-compatible format, computes degree for each node
  • Added click-to-navigate: tapping a node opens the corresponding note in Joplin
  • Styled connected nodes as blue circles with borders, link edges as solid with arrowheads, tag edges as dotted gray
  • Added ResizeObserver so the graph fills the panel and adapts to resize
  • Wrote unit tests for edge factory (9 tests) and graph builder (6 tests)

Preview

image image

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a Cytoscape.js-based interactive note graph rendered in a Joplin panel webview, with a new build pipeline for bundling webview-only dependencies and server-side graph construction (nodes/edges) with unit tests.

Changes:

  • Introduces a separate web-targeted webpack build for the webview graph renderer and wires it into npm run dist.
  • Adds graph domain types plus EdgeFactory + GraphBuilder (with unit tests) to assemble Cytoscape-compatible nodes/edges and compute node degree.
  • Extends the panel webview messaging to support requesting graph data and click-to-navigate from graph nodes.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
webview.webpack.config.js New webpack config to bundle Cytoscape + fCoSE for the panel webview.
src/ui/webview.ts Adds graph-data request handling and node-click navigation; stores/publishes graph data.
src/ui/styles/panel.css Updates panel layout and adds graph container/status styling.
src/ui/graph-view.js New Cytoscape renderer + layout + resize/theme handling + webview messaging.
src/ui/App.ts Adds graph container + status element to panel HTML.
src/services/similarity/EdgeFactory.ts Builds link/tag edges between notes.
src/services/similarity/EdgeFactory.test.ts Unit tests for edge creation rules.
src/services/graph/types.ts New graph data model used by builder and webview.
src/services/graph/GraphBuilder.ts Builds Cytoscape-ready nodes/edges and computes degree.
src/services/graph/GraphBuilder.test.ts Unit tests for graph node/edge building behavior.
src/index.ts Generates graph data before showing the panel.
package.json Adds Cytoscape deps and a webview build step/script.
package-lock.json Locks new Cytoscape/fCoSE dependencies.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +10 to +19
const addEdge = (source: string, target: string, type: EdgeType) => {
const key =
source < target
? `${source}::${target}::${type}`
: `${target}::${source}::${type}`;
if (!edgeSet.has(key)) {
edgeSet.add(key);
edges.push({ source, target, type });
}
};
Comment on lines +57 to +66
it('creates bidirectional link when notes reference each other', () => {
const notes = [
note('a', 'A', ['b']),
note('b', 'B', ['a']),
];

const edges = factory.createEdges(notes);
expect(edges).toHaveLength(1);
expect(edges).toContainEqual({ source: 'a', target: 'b', type: 'link' });
});
Comment thread src/ui/webview.ts
Comment on lines +67 to +69
export const postGraphData = async (graphData: GraphData): Promise<void> => {
currentGraphData = graphData;
};
Comment thread src/ui/graph-view.js
Comment on lines +229 to +235
if (typeof webviewApi !== 'undefined') {
webviewApi.onMessage(function (message) {
if (message && message.type === 'fit-to-screen') {
cy.fit(undefined, 30);
}
});
}
Comment thread src/ui/graph-view.js
Comment on lines +131 to +152
function renderGraph(message) {
if (!message || !message.nodes || !message.nodes.length) {
showStatus('No graph data received');
return;
}

var nodeCount = message.nodes.length;
var edgeCount = (message.edges || []).length;

if (edgeCount === 0) {
showStatus(nodeCount + ' notes loaded, 0 connections found');
return;
}

hideStatus();

cy.elements().remove();
cy.add(message.nodes);
cy.add(message.edges);

cy.layout(FCOSE_OPTIONS).run();
}
Comment on lines +39 to +44
for (const [, noteIds] of tagToNotes) {
for (let i = 0; i < noteIds.length; i++) {
for (let j = i + 1; j < noteIds.length; j++) {
addEdge(noteIds[i], noteIds[j], 'tag');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants