feat: port graph page from playground #78

Merged
forgejo_admin merged 1 commit from 74-port-graph-page into main 2026-03-27 21:55:06 +00:00

Summary

  • Ports the graph visualization page from the playground to a new /graph route
  • Implements a force-directed SVG layout that fetches notes from the API, derives parent-child edges from parent_slug, and renders interactive nodes colored by note_type with size proportional to recency
  • Adds Graph link to top nav bar and sidebar navigation

Changes

  • src/routes/graph/+page.svelte -- NEW: SVG graph visualization with force-directed layout, filter pills by note type, clickable nodes navigating to /notes/{slug}, legend matching playground design
  • src/routes/graph/+page.ts -- NEW: client-side only route (SSR disabled)
  • src/routes/+layout.svelte -- Added Graph link to top nav bar and sidebar navigation

Test Plan

  • npm run build passes with zero errors
  • npx svelte-check passes with zero errors (1 pre-existing warning in search page)
  • Graph route renders at /graph with nodes and edges from live API data
  • Filter pills toggle between note types
  • Clicking a node navigates to /notes/{slug}

Review Checklist

  • No secrets committed
  • No unnecessary file changes
  • Commit messages are descriptive
  • Closes #74
  • forgejo_admin/pal-e-app#74 -- Port graph page from playground
  • Depends on #68 (CSS + sidebar foundation, already merged as #75)
## Summary - Ports the graph visualization page from the playground to a new `/graph` route - Implements a force-directed SVG layout that fetches notes from the API, derives parent-child edges from `parent_slug`, and renders interactive nodes colored by `note_type` with size proportional to recency - Adds Graph link to top nav bar and sidebar navigation ## Changes - `src/routes/graph/+page.svelte` -- NEW: SVG graph visualization with force-directed layout, filter pills by note type, clickable nodes navigating to `/notes/{slug}`, legend matching playground design - `src/routes/graph/+page.ts` -- NEW: client-side only route (SSR disabled) - `src/routes/+layout.svelte` -- Added Graph link to top nav bar and sidebar navigation ## Test Plan - [x] `npm run build` passes with zero errors - [x] `npx svelte-check` passes with zero errors (1 pre-existing warning in search page) - [ ] Graph route renders at `/graph` with nodes and edges from live API data - [ ] Filter pills toggle between note types - [ ] Clicking a node navigates to `/notes/{slug}` ## Review Checklist - [x] No secrets committed - [x] No unnecessary file changes - [x] Commit messages are descriptive ## Related Notes - Closes #74 - `forgejo_admin/pal-e-app#74` -- Port graph page from playground - Depends on #68 (CSS + sidebar foundation, already merged as #75)
feat: port graph page from playground with force-directed SVG layout
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
3d483598f7
Closes #74

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

QA Review -- PR #78

Structure & Patterns

  • Clean Svelte 5 runes usage ($state, $derived, onMount)
  • TypeScript interfaces for GraphNode/GraphEdge are well-defined
  • SSR disabled in +page.ts, consistent with app-wide SPA pattern
  • Nav + sidebar additions match existing link patterns exactly

Accessibility

  • Nodes have role="link", tabindex="0", keyboard handler for Enter/Space -- good
  • <title> element on each node provides tooltip -- good
  • a11y_click_events_have_key_events suppression is appropriate since keydown is handled on the same element

Nits (non-blocking)

  1. O(n^2) repulsion loop -- The force simulation runs n*(n-1)/2 repulsion calculations for 120 iterations synchronously in onMount. With 500 notes this is ~15M iterations. On fast hardware this should complete in <100ms, but if the note count grows significantly, consider a Barnes-Hut approximation or requestAnimationFrame chunking. Not blocking since the current dataset is well within bounds.

  2. nodeOpacity operator precedence -- 0.5 + (r - 8) / (24 - 8) * 0.4 relies on left-to-right evaluation of / and * which is correct, but adding parentheses 0.5 + ((r - 8) / (24 - 8)) * 0.4 would improve readability.

  3. Non-deterministic layout -- Math.random() for initial positions means the graph looks different on every page load. This is fine for exploration but worth noting. A seeded PRNG or slug-based hash could provide stable layouts.

  4. GraphEdge.type includes 'reference' but no reference edges are created -- The buildGraph function only creates parent type edges from parent_slug. The reference edge type exists in the interface and is handled in the template (dashed lines, accent color), but no code path produces reference edges. This is a placeholder for future note-links API integration, which is fine, but the legend shows "reference" lines that won't appear in practice yet.

  5. Unused project field on GraphNode -- project is populated but never read in the template. Minor dead code.

Verdict

VERDICT: APPROVE

Clean port from playground. Build passes, type-check clean. The force-directed layout is a solid approach for the current dataset size. Nits are all non-blocking improvements for future iterations.

## QA Review -- PR #78 ### Structure & Patterns - Clean Svelte 5 runes usage ($state, $derived, onMount) - TypeScript interfaces for GraphNode/GraphEdge are well-defined - SSR disabled in +page.ts, consistent with app-wide SPA pattern - Nav + sidebar additions match existing link patterns exactly ### Accessibility - Nodes have `role="link"`, `tabindex="0"`, keyboard handler for Enter/Space -- good - `<title>` element on each node provides tooltip -- good - `a11y_click_events_have_key_events` suppression is appropriate since keydown is handled on the same element ### Nits (non-blocking) 1. **O(n^2) repulsion loop** -- The force simulation runs `n*(n-1)/2` repulsion calculations for 120 iterations synchronously in `onMount`. With 500 notes this is ~15M iterations. On fast hardware this should complete in <100ms, but if the note count grows significantly, consider a Barnes-Hut approximation or requestAnimationFrame chunking. Not blocking since the current dataset is well within bounds. 2. **`nodeOpacity` operator precedence** -- `0.5 + (r - 8) / (24 - 8) * 0.4` relies on left-to-right evaluation of `/` and `*` which is correct, but adding parentheses `0.5 + ((r - 8) / (24 - 8)) * 0.4` would improve readability. 3. **Non-deterministic layout** -- `Math.random()` for initial positions means the graph looks different on every page load. This is fine for exploration but worth noting. A seeded PRNG or slug-based hash could provide stable layouts. 4. **`GraphEdge.type` includes 'reference' but no reference edges are created** -- The `buildGraph` function only creates `parent` type edges from `parent_slug`. The `reference` edge type exists in the interface and is handled in the template (dashed lines, accent color), but no code path produces reference edges. This is a placeholder for future note-links API integration, which is fine, but the legend shows "reference" lines that won't appear in practice yet. 5. **Unused `project` field on GraphNode** -- `project` is populated but never read in the template. Minor dead code. ### Verdict **VERDICT: APPROVE** Clean port from playground. Build passes, type-check clean. The force-directed layout is a solid approach for the current dataset size. Nits are all non-blocking improvements for future iterations.
forgejo_admin deleted branch 74-port-graph-page 2026-03-27 21:55:06 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo_admin/pal-e-docs-app!78
No description provided.