fix(graph): address high-severity findings from PR #88 review#189
fix(graph): address high-severity findings from PR #88 review#189damienriehl wants to merge 47 commits into
Conversation
Port entity graph from folio-mapper to use server-driven BFS endpoint. Replace buildGraphData.ts and elkLayout.ts with useELKLayout hook and lib/graph/utils.ts. Add EntityGraphModal for full-screen graph view. Update OntologyGraph, OntologyNode, OntologyEdge for server data model. Remove accessToken and labelHints props from layout components. - New: lib/api/graph.ts (API client for graph endpoint) - New: lib/graph/useELKLayout.ts (ELK layout hook from folio-mapper) - New: lib/graph/utils.ts (extractTreeLabelMap utility) - New: components/graph/EntityGraphModal.tsx (full-screen overlay) - Rewritten: useGraphData.ts (single API call + progressive expansion) - Updated: OntologyGraph, OntologyNode, OntologyEdge - Updated: DeveloperEditorLayout, StandardEditorLayout Ref: #81 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Update CONTEXT.md D-05 to match actual project-scoped endpoint - Add language tags to bare fenced code blocks in PLAN.md - Add dialog semantics and focus management to EntityGraphModal - Preserve truncated flag when merging expansion results - Make resetGraph re-fetch baseline via resetKey state - Defer expandedNodes.add until fetch succeeds (retryable on failure) - Single-click only expands nodes; navigation is double-click only - Add layout race condition guard in useELKLayout - Prefer tree label over raw IRI fragment in graph modal title - Fix OntologyGraph expandedNodes mutation lint warning (useMemo → useRef) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…data - Replace ontologyId with projectId in PLAN.md API examples - Update success criteria endpoint to project-scoped path - Remove layoutNodes.length guard so React Flow clears when graph is empty Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Delete elkLayout.test.ts (tests deleted elkLayout.ts module) - Update OntologyEdge test: seeAlso dash pattern "6 3" not "2 4" - Rewrite OntologyGraph tests for new API: - Mock useELKLayout instead of deleted elkLayout - Use EntityGraphResponse shape for mock data - Remove accessToken from props (handled centrally) - Add showDescendants/setShowDescendants to mock return - Update legend labels (Root ancestor, rdfs:seeAlso) - Remove stale "resolved count" and "Fit view" assertions - Add truncation badge test Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…LK layout - Add EntityGraphModal tests: dialog semantics, aria attributes, focus management, focus trapping, Escape key, close button, loading fallback - Add graph API client tests: URL encoding, parameter passing, error propagation, optional params handling - Add useELKLayout tests: LR direction, race condition guard, node/edge type assignment, isLayouting state Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cast mockGet.mock.calls[0][1] to typed config object to satisfy strict TypeScript checks on possibly-undefined array access. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix OntologyGraph mock path to match lazy import resolution - Add focusable button to mock for focus trapping test coverage - Add test for handleNavigate callback (onNavigateToClass + onClose) - Add test for Tab without trapping (middle element) - Add tests for expandNode skip (already expanded, null graphData) - EntityGraphModal: 60%→97% statements, 28%→89% branches Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Capture ReactFlow props to test onNodeClick (expand), onNodeDoubleClick (navigate), and MiniMap nodeColor function. Add descendants toggle and expand node tests. Graph components coverage: 85%→99% statements. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The backend route is /ontology/classes/{class_iri}/graph, not
/ontology/graph/{class_iri}. Update the API client and test.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace incorrect additive tally with Math.max of previous and new total_concept_count when merging expanded node data, since the backend returns the total discovered count per subgraph, not an incremental value. Add clarifying comments for focus trap edge case (single focusable element) and expandNode skip assertion timing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The backend changed the graph route from
/classes/{class_iri:path}/graph to /classes/graph?class_iri=...
because FastAPI's :path converter greedily captured /graph as part
of the IRI. Update the API client and tests to match.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add optional token parameter to graphApi.getEntityGraph and thread accessToken through useGraphData, OntologyGraph, EntityGraphModal, and both editor layouts so private projects can fetch graph data. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The target handle (incoming edge) should be at the top of the node and the source handle (outgoing edge) at the bottom, so parent-to-child wires flow downward in top-down layout mode. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace custom SVG marker with React Flow's MarkerType.ArrowClosed for correct arrowhead orientation on curved paths. Switch from getBezierPath to getSmoothStepPath (borderRadius: 16) as default — smooth step paths use orthogonal segments so arrowheads align with the wire direction instead of always pointing perpendicular. Add graphEdgeStyle user preference (bezier/smoothstep) to editorModeStore with localStorage persistence. Add toggle button in the graph toolbar to switch between Bezier and Smooth Step edge styles. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… depth The expandNode helper was always requesting descendantsDepth: 1 regardless of the showDescendants toggle state. Now it passes descendantsDepth based on the toggle (1 when enabled, 0 when disabled) and includes showDescendants in the useCallback dependency array. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The effect that fetches the entity graph used accessToken but omitted it from the dependency array, so the fetch would not retry when the token becomes available or changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the unsafe `as GraphNodeType` cast with a normalizeNodeType helper that checks against known valid types and maps backend-specific values like "secondary_root" to the correct frontend type. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…licate expect Replace weak toBeDefined-only assertions with checks for actual subClassOf style properties (stroke color, strokeWidth, no dash array). Remove the duplicate expect(edge).toBeDefined() call. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add getBezierPath mock and a test that sets graphEdgeStyle to "bezier" to verify the bezier path branch is exercised. Use a mutable variable for the mock store to allow per-test style overrides. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a graphEpoch ref that increments on each initial fetch and an expandingNodes set to track in-flight expansions. The expansion .then() now verifies the epoch matches and the IRI is still in expandingNodes before merging, preventing stale data from corrupting the current graph. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…Data Use useQuery for the initial entity graph fetch, consistent with other hooks in the project (useCrossReferences, useProject, etc.). React Query handles cancellation, retries, deduplication, and caching. Local graphData state is seeded from the query result and expanded progressively via expandNode. Tests updated to use QueryClientProvider. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix react/display-name error in test QueryClient wrapper by assigning displayName. Refactor useGraphData to store expansions separately and merge via useMemo, avoiding setState-in-render from ref access during render phase. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The useEffect that resets expansion tracking depended on query.data, causing expansions to be cleared on background refetches (staleTime expiry, window refocus). Now depends on the actual graph input props (projectId, focusIri, branch, showDescendants, resetKey) so expansions are only cleared when the graph context truly changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Always fetch descendantsDepth: 1 so direct children appear on initial graph load. Rename showDescendants to showAllDescendants — the toggle now controls whether to fetch deeper descendants (depth 2) rather than toggling descendants on/off entirely. Update button labels and tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…erges useEffect runs after paint, so a promise .then() microtask from expandNode could execute between render and the effect, passing the stale epoch guard. useLayoutEffect runs synchronously in the commit phase, closing this race window. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When graphData becomes null or has no nodes, the layout effect returned early but left the previous React Flow state intact, showing a stale graph. Now explicitly calls setNodes([]) and setEdges([]) to clear the canvas. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OntologyGraph maintained its own expandedNodes ref and reset effect that duplicated the deduplication already handled inside useGraphData's expandNode. Simplified handleNodeClick to always delegate to expandNode and removed the unused useRef import. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The catch handler for runLayout was empty, making layout failures invisible. Now logs the error with console.error for debugging. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Single click on a node now waits 200ms before calling expandNode. If a double click follows within that window, the expand is cancelled and onNavigateToClass fires instead. This prevents unintended expansion when the user intends to navigate. Timer is cleaned up on unmount. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add explicit tests for the "Computing layout..." indicator when isLayouting is true, and verify that accessToken is forwarded to useGraphData. Made useELKLayout mock's isLayouting value overridable per test. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The useQuery enabled condition only checked focusIri, allowing unauthorized requests when the session token wasn't yet available. Now requires both focusIri and accessToken. Added test for missing accessToken case. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Handles were hardcoded to Position.Top/Bottom which breaks edge anchoring in left-right layouts. Now reads layoutDirection from node data and uses Position.Left/Right for LR layout. The direction is passed through from useELKLayout when building node data. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add normalizeEdgeType helper matching the existing normalizeNodeType pattern. Unknown edge types fall back to subClassOf instead of passing through an invalid enum value to OntologyEdge. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the static import of EntityGraphModal with next/dynamic so the modal code is only loaded when the user opens the full-screen graph view, matching the existing pattern used for OntologyGraph. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous test awaited runLayout to completion so it never saw isLayouting=true. Now uses a deferred promise in the ELK mock to assert isLayouting=true while layout is pending, then resolves and verifies it returns to false. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After clicking a node to expand, the ELK relayout could move the clicked node off-screen. Now tracks the last expanded node ID and smoothly centers the viewport on it (300ms transition) once layout finishes. Wraps OntologyGraph in ReactFlowProvider to enable useReactFlow's setCenter. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ELKLayout Add tests for LR layout handle positions, edge style toggle, and normalization fallback branches (secondary_root, unknown node_type, unknown edge_type). Brings useELKLayout to 100% statement coverage. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Optimize mergeExpansions with cumulative Sets instead of recreating per iteration - Replace magic numbers in viewport centering with NODE_WIDTH/NODE_HEIGHT constants exported from useELKLayout - Assert dynamic node width via captured ELK mock call in test - Reset deferredLayout in beforeEach to prevent stale test state Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Focus the first focusable element on open instead of the dialog container. Also handle Shift+Tab when activeElement is the dialog container itself (tabIndex=-1), wrapping to the last focusable element. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
If graphData was cleared while a previous runLayout was still resolving, the stale layoutNodes/layoutEdges would be reapplied by the sync effect. Now checks graphData is non-null before syncing layout results. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Assert Authorization header is sent/omitted based on token presence - Verify stale expandNode responses are discarded after resetGraph - Update EntityGraphModal focus test for first-focusable-element behavior Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Reset capturedOnNavigateToClass in beforeEach to prevent cross-test leaks - Focus a middle element in the focus trap test instead of the first - Assert reset payload (focus_label) instead of just non-null graphData - Use objectContaining for options assertion to avoid pinning undefined keys Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ow markers Merge visual enhancements with colleague's entity-graph-pr work: - Add secondary_root node type with distinct slate styling and legend entry - Pin root and secondary_root nodes to first ELK layer - Remove smoothstep/bezier toggle, use bezier-only edges - Add per-edge-type markerEnd config (subClassOf gets arrow-slate SVG marker) - Add secondary_root to MiniMap color mapping - Update tests: remove smoothstep mocks, add secondary_root + layer constraint tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The marker condition checked the raw backend edge_type, so edges normalized to "subClassOf" via normalizeEdgeType could miss the arrow. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously OntologyNode declared `onNavigate` / `onExpandNode` props plus
`role="button" tabIndex={0}` and an `onKeyDown` Enter/Space handler — but
useELKLayout never populated the props, so the node advertised itself as
a focusable button to screen readers and did nothing on Enter/Space. The
inner `onClick` / `onDoubleClick` also competed with ReactFlow's parent
event handling.
Changes:
- useELKLayout exposes `setNodeHandlers({ onNavigate, onExpandNode })`.
Handlers live in a ref so swapping them does NOT re-run layout or bust
React.memo on OntologyNode. Stable lambdas in node data read from the
ref at event time.
- OntologyGraph effect calls setNodeHandlers with `expandNode` (single-
click semantics) and `onNavigateToClass` (double-click semantics).
- OntologyNode drops competing `onClick` / `onDoubleClick` (ReactFlow's
parent handlers own mouse events). `onKeyDown` now provides keyboard
parity:
- Enter / Space → expand (matches single-click)
- Shift + Enter → navigate (matches double-click)
- aria-label now describes the keyboard shortcuts so screen readers
announce the actions.
- Tests updated to verify keyboard-only contract.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The `graphEdgeStyle` Zustand setting (bezier vs smoothstep) was added, persisted to localStorage, defaulted to 'smoothstep', AND exposed via a toolbar — but OntologyEdge always called getBezierPath, so the setting silently lied to the user. OntologyEdge now subscribes to the store and dispatches between getBezierPath / getSmoothStepPath based on the user's choice. OntologyEdge.test.tsx mocks the store (default: 'smoothstep') and the new getSmoothStepPath import to keep the test fast and matchMedia-free. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
These types were leftover from the deleted client-side graph builder (`lib/graph/buildGraphData.ts`). After the server-side BFS port, the canonical graph payload is `EntityGraphResponse` from `lib/api/graph.ts`, and grep confirms no consumers import OntologyGraphNode, OntologyGraphEdge, or GraphData. Keeping them invited future contributors to use the wrong vocabulary. The remaining `GraphNodeType` / `GraphEdgeType` literal unions stay — they are the rendering-side mirrors of the API enum, also referenced in Python (ontokit-api ontokit/schemas/graph.py). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
742255c to
1fadb47
Compare
…olbar Restore the in-toolbar toggle button from John D'Orazio's b42496e (originally on PR #189) so users can switch between Bezier and Smooth Step edge paths without leaving the graph. The graphEdgeStyle store field, persistence, and OntologyEdge wiring were already in entity-graph-pr; only the toolbar affordance was missing after the H1/H2/H3 work landed. Inverts the prior \"does not render an edge style toggle button\" assertion into positive coverage: the toggle renders labelled for the alternate style (default smoothstep → offers \"Switch to bezier edges\"), and clicking flips the labelling to the next alternate. Co-Authored-By: John R. D'Orazio <priest@johnromanodorazio.com> Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Closing as superseded by #88 (entity-graph-pr). After PR #228 (back-to-project) merged to dev, I rebased entity-graph-pr against the new dev tip. All H1/H2/H3 fixes that were originally the point of this PR are already on the rebased entity-graph-pr (commits 1fadb47, 059ac12, e2a0c70 = original H3, H2, H1 in renamed form), along with the rest of the entity-graph evolution. The one piece of this PR that wasn't already in #88 — Fr. John's #88 is now the single authoritative branch for this work. Safe to close. |
Summary
Follow-up to #88 addressing the 3 high-severity findings from peer review. Targets
entity-graph-prso fixes land before #88 merges todev.Recommended merge order: this PR (#XX) → #88 →
dev.Findings addressed
OntologyNodeadvertised itself as a focusable button (role="button" tabIndex={0}) withonKeyDownEnter/Space handlers, butuseELKLayoutnever populatedonNavigate/onExpandNodeprops — so screen readers announced an interactive element that did nothing.ad95639graphEdgeStyleZustand store field was added, persisted to localStorage, defaulted to 'smoothstep', AND exposed via a toolbar — butOntologyEdgealways calledgetBezierPath. The setting silently lied to the user.df83589OntologyGraphNode/OntologyGraphEdge/GraphDatawere leftover from the deleted client-side graph builder. After the server-side BFS port,EntityGraphResponseis canonical and grep confirms no consumers reference the leftovers.724ea45Detailed rationale per fix
H1 — Wire keyboard handlers through
The challenge: ReactFlow already owns mouse interactions via
onNodeClick/onNodeDoubleClickat theOntologyGraphlevel. IfOntologyNodeadds its ownonClick, mouse events fire BOTH handlers (expand AND navigate). Solution:useELKLayoutso swapping them does not re-run layout or bustReact.memoonOntologyNode. Stable lambdas in node data read from the ref at event time.useELKLayoutexportssetNodeHandlers({ onNavigate, onExpandNode })and the consumer calls it from auseEffect.onClick/onDoubleClickfromOntologyNode— let ReactFlow own all mouse events.onKeyDownfor keyboard parity with the new mapping:Enter/Space→ expand (matches single-click)Shift + Enter→ navigate (matches double-click)aria-labeldescribes the keyboard shortcuts so screen readers announce both actions.H2 — Honor
graphEdgeStylesettingOntologyEdge.test.tsxmocks the store (default 'smoothstep') and the newgetSmoothStepPathimport to keep tests fast and matchMedia-free.H3 — Drop redundant types
OntologyGraphNode/OntologyGraphEdge/GraphDatawere render-side mirrors of the API types but no consumer imported them after the BFS port. Removed; only theGraphNodeType/GraphEdgeTypeliteral unions remain (rendering-side mirrors of the PythonLiteral[...]inontokit-api ontokit/schemas/graph.pyfrom the companion fix in #37 follow-up).Verification
npm run type-checkcleannpm run lint— 21 warnings (all pre-existing in unrelated files; no new ones)Test plan
npm test -- --run __tests__/components/graph/OntologyNode.test.tsx— keyboard contract verified (Enter expands, Shift+Enter navigates, external nodes don't dispatch)npm test -- --run __tests__/components/graph/OntologyEdge.test.tsx— smoothstep path used (per default store mock)Findings deferred to Discussion threads
The original review also flagged 5 medium and 5 low items. They will be filed as a tracked Discussion on this repo for follow-up.
🤖 Generated with Claude Code