Skip to content

fix(graph): address high-severity findings from PR #88 review#189

Closed
damienriehl wants to merge 47 commits into
entity-graph-prfrom
fix/pr-88-blockers
Closed

fix(graph): address high-severity findings from PR #88 review#189
damienriehl wants to merge 47 commits into
entity-graph-prfrom
fix/pr-88-blockers

Conversation

@damienriehl
Copy link
Copy Markdown
Contributor

Summary

Follow-up to #88 addressing the 3 high-severity findings from peer review. Targets entity-graph-pr so fixes land before #88 merges to dev.

Recommended merge order: this PR (#XX) → #88dev.

Findings addressed

Sev ID Issue Commit
High H1 OntologyNode advertised itself as a focusable button (role="button" tabIndex={0}) with onKeyDown Enter/Space handlers, but useELKLayout never populated onNavigate / onExpandNode props — so screen readers announced an interactive element that did nothing. ad95639
High H2 graphEdgeStyle Zustand store field was added, persisted to localStorage, defaulted to 'smoothstep', AND exposed via a toolbar — but OntologyEdge always called getBezierPath. The setting silently lied to the user. df83589
High H3 OntologyGraphNode / OntologyGraphEdge / GraphData were leftover from the deleted client-side graph builder. After the server-side BFS port, EntityGraphResponse is canonical and grep confirms no consumers reference the leftovers. 724ea45

Detailed rationale per fix

H1 — Wire keyboard handlers through

The challenge: ReactFlow already owns mouse interactions via onNodeClick / onNodeDoubleClick at the OntologyGraph level. If OntologyNode adds its own onClick, mouse events fire BOTH handlers (expand AND navigate). Solution:

  1. Store handlers in a ref inside useELKLayout 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.
  2. useELKLayout exports setNodeHandlers({ onNavigate, onExpandNode }) and the consumer calls it from a useEffect.
  3. Drop competing onClick / onDoubleClick from OntologyNode — let ReactFlow own all mouse events.
  4. Keep onKeyDown for keyboard parity with the new mapping:
    • Enter / Space → expand (matches single-click)
    • Shift + Enter → navigate (matches double-click)
  5. aria-label describes the keyboard shortcuts so screen readers announce both actions.
// useELKLayout.ts — handlers in ref, stable lambdas in node data
const handlersRef = useRef<NodeHandlers>({});
const stableHandlers = useMemo(
  () => ({
    onNavigate: (iri: string) => handlersRef.current.onNavigate?.(iri),
    onExpandNode: (iri: string) => handlersRef.current.onExpandNode?.(iri),
  }),
  [],
);
const setNodeHandlers = useCallback((h: NodeHandlers) => {
  handlersRef.current = h;
}, []);

// OntologyGraph.tsx — feed handlers without re-laying out
useEffect(() => {
  setNodeHandlers({ onNavigate: onNavigateToClass, onExpandNode: expandNode });
}, [setNodeHandlers, onNavigateToClass, expandNode]);

H2 — Honor graphEdgeStyle setting

// OntologyEdge.tsx
const edgeStyle = useEditorModeStore((s) => s.graphEdgeStyle);
const pathBuilder = edgeStyle === "smoothstep" ? getSmoothStepPath : getBezierPath;
const [edgePath, labelX, labelY] = pathBuilder({ ... });

OntologyEdge.test.tsx mocks the store (default 'smoothstep') and the new getSmoothStepPath import to keep tests fast and matchMedia-free.

H3 — Drop redundant types

OntologyGraphNode / OntologyGraphEdge / GraphData were render-side mirrors of the API types but no consumer imported them after the BFS port. Removed; only the GraphNodeType / GraphEdgeType literal unions remain (rendering-side mirrors of the Python Literal[...] in ontokit-api ontokit/schemas/graph.py from the companion fix in #37 follow-up).

Verification

  • All 2,516 existing tests pass — none regressed
  • npm run type-check clean
  • npm run lint — 21 warnings (all pre-existing in unrelated files; no new ones)
npm test -- --run             # 2516 passed (was 2507; +9 retests after API change)
npm run type-check            # clean

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)
  • In browser: focus a node with Tab, press Enter → graph expands; press Shift+Enter → navigates to class detail
  • Settings → toggle Graph edge style → bezier vs smoothstep visibly differs

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

damienriehl and others added 30 commits April 12, 2026 18:52
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>
JohnRDOrazio and others added 17 commits April 12, 2026 18:53
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>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 25, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ac8df814-c337-4921-a228-d0c5163c0a31

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/pr-88-blockers

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 25, 2026

@damienriehl damienriehl force-pushed the entity-graph-pr branch 2 times, most recently from 742255c to 1fadb47 Compare May 2, 2026 14:19
damienriehl added a commit that referenced this pull request May 2, 2026
…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>
@damienriehl
Copy link
Copy Markdown
Contributor Author

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 b42496e edge style toggle UI in the graph toolbar — has been ported directly onto #88 in commit 02cec20 (with positive test coverage). Co-author credit preserved on the commit.

#88 is now the single authoritative branch for this work. Safe to close.

@damienriehl damienriehl closed this May 2, 2026
@damienriehl damienriehl deleted the fix/pr-88-blockers branch May 2, 2026 14:28
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