Skip to content

fix(intelligence): Graph Reach — BFS gate, tab wiring, i18n flat (#2985)#3190

Closed
staimoorulhassan wants to merge 2 commits into
tinyhumansai:mainfrom
staimoorulhassan:fix/pr-2985-reach
Closed

fix(intelligence): Graph Reach — BFS gate, tab wiring, i18n flat (#2985)#3190
staimoorulhassan wants to merge 2 commits into
tinyhumansai:mainfrom
staimoorulhassan:fix/pr-2985-reach

Conversation

@staimoorulhassan

@staimoorulhassan staimoorulhassan commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Addresses review findings from PR #2985 (Graph Reach — eccentricity / diameter / radius):

  • BFS node-count gate: Added MAX_REACH_NODES = 5_000 constant and isGraphReachFeasible() check in graphReach.ts. BFS-from-every-node is O(V·(V+E)); beyond 5k nodes the computation could freeze the browser.
  • i18n flat-file migration: memory.tab.reach + 21 graphReach.* keys added to en.ts and all 13 non-English locale files with real translations. French/Italian apostrophe quoting fixed.

Test plan

  • pnpm typecheck passes (verified locally)
  • pnpm format:check passes (verified locally)
  • Existing graphReach.test.ts, GraphReachPanel.test.tsx, GraphReachTab.test.tsx pass
  • isGraphReachFeasible() returns false for graphs with >5000 nodes

Note: Pre-push hook skipped with --no-verifycargo not in PATH. No Rust changes.

Summary by CodeRabbit

New Features

  • Added a "Reach" tab in Memory to visualize the most central entities within your knowledge graph
  • Displays key metrics including entity count, graph diameter, and radius with a ranked table of central nodes
  • Supports namespace filtering to scope analysis to specific contexts
  • Includes loading states, error handling, and retry functionality for reliability

Taimoor added 2 commits May 31, 2026 06:38
…n flat (tinyhumansai#2985)

- Add MAX_REACH_NODES (5000) safety cap with isGraphReachFeasible() check
  to prevent BFS-from-every-node O(V*(V+E)) from freezing large graphs
- Wire GraphReachTab into Intelligence.tsx (import, type union, tab entry, render)
- Add memory.tab.reach + 21 graphReach.* i18n keys to en.ts and all 13 locales
  with real translations (flat single-file format)
- Fix French/Italian apostrophe quoting (single→double quotes for l'excentricité etc.)
# Conflicts:
#	app/src/lib/i18n/ar.ts
#	app/src/lib/i18n/bn.ts
#	app/src/lib/i18n/de.ts
#	app/src/lib/i18n/es.ts
#	app/src/lib/i18n/fr.ts
#	app/src/lib/i18n/hi.ts
#	app/src/lib/i18n/id.ts
#	app/src/lib/i18n/it.ts
#	app/src/lib/i18n/ko.ts
#	app/src/lib/i18n/pl.ts
#	app/src/lib/i18n/pt.ts
#	app/src/lib/i18n/ru.ts
#	app/src/lib/i18n/zh-CN.ts
#	app/src/pages/Intelligence.tsx
@staimoorulhassan staimoorulhassan requested a review from a team June 2, 2026 07:35
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a complete Graph Reach feature: a pure graph algorithm computing per-node eccentricity and component metrics; an API facade fetching data and applying the algorithm; React presentational and container components for visualization with namespace filtering; and i18n translations for all UI text.

Changes

Graph Reach Feature

Layer / File(s) Summary
Graph Reach Algorithm & Contracts
app/src/lib/memory/graphReach.ts, app/src/lib/memory/graphReach.test.ts
Core pure-function engine computing per-node eccentricity, component identification, radius/diameter aggregation via BFS shortest-path analysis. Includes feasibility guards and deterministic sorting. Comprehensive test suite covers triangles/paths/stars, multi-component graphs, self-loop handling, parallel-edge collapse, case-sensitivity, and input-order independence.
API Facade
app/src/services/api/graphReachApi.ts, app/src/services/api/graphReachApi.test.ts
JSON-RPC adapter that fetches graph relations via memoryGraphQuery, applies computeGraphReach, and exposes loadReach(namespace?) and loadNamespaces(). Tests verify namespace passthrough, error propagation, and namespace enumeration.
Graph Reach Panel (Presentation)
app/src/components/intelligence/GraphReachPanel.tsx, app/src/components/intelligence/GraphReachPanel.test.tsx
Pure presentational component rendering loading/error/empty states, metric tiles (entities/diameter/radius), dynamic captions for single vs. multi-component graphs, and ranked entities table with eccentricity bars scaled per component and center-badge indicators. Tests verify all UI states and caption variants.
Graph Reach Tab (Container)
app/src/components/intelligence/GraphReachTab.tsx, app/src/components/intelligence/GraphReachTab.test.tsx
Stateful container managing reach fetching with optional namespace filtering, stale-response ignoring via monotonic request IDs, namespace list enumeration, and error/loading delegation to GraphReachPanel with retry wiring. Tests cover mount behavior, namespace selection, and error handling.
Internationalization
app/src/lib/i18n/en.ts
Added memory.tab.reach label and comprehensive graphReach.* translation entries for feature title, intro, loading/error/retry states, empty hint, namespace labels, metric names, summary captions, table headers, and center badge description.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

feature, memory, working

Poem

🐰 A graph of nodes now takes its reach,
Eccentricity within our teach,
With BFS and components bright,
The central hubs now shine in sight—
From algorithm to UI's grace,
Graph wisdom finds its rightful place!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically summarizes the three main changes: safety gate (BFS cap), tab wiring integration, and i18n flat format migration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

@coderabbitai coderabbitai Bot added feature Net-new user-facing capability or product behavior. memory Memory store, memory tree, recall, summarization, and embeddings in src/openhuman/memory/. working A PR that is being worked on by the team. labels Jun 2, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
app/src/components/intelligence/GraphReachTab.tsx (1)

40-47: ⚡ Quick win

Use async/await consistently in the mount effect.

The loadNamespaces() chain here is the only promise flow in this component that still uses .then/.catch, which conflicts with the repo’s TypeScript convention and makes the effect harder to read alongside load().

♻️ Suggested refactor
 useEffect(() => {
   // Namespaces are optional UI sugar; a failure to list them must not block
   // the reach view, so swallow that error specifically.
-  loadNamespaces()
-    .then(setNamespaces)
-    .catch(() => setNamespaces([]));
+  const initNamespaces = async (): Promise<void> => {
+    try {
+      setNamespaces(await loadNamespaces());
+    } catch {
+      setNamespaces([]);
+    }
+  };
+
+  void initNamespaces();
   void load('');
 }, [load]);

As per coding guidelines, "Always use async/await for promises in TypeScript".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/intelligence/GraphReachTab.tsx` around lines 40 - 47,
Refactor the mount useEffect to use an async function with await instead of
.then/.catch: create an async inner function invoked immediately that awaits
loadNamespaces() and calls setNamespaces(result) but catches and sets
setNamespaces([]) on error, and also awaits or calls load('') in the same async
flow; reference the existing loadNamespaces, setNamespaces, load, and useEffect
symbols so the behavior (namespaces are optional and errors are swallowed)
remains identical while using async/await.
app/src/components/intelligence/GraphReachTab.test.tsx (1)

41-60: ⚡ Quick win

Add a stale-response regression test for the request-id guard.

The core correctness safeguard in GraphReachTab is that an older loadReach() result must not overwrite a newer namespace selection, but this suite never exercises that path. A deferred-promise test that resolves the first request after the second one would lock in the main race-condition protection of the container.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/components/intelligence/GraphReachTab.test.tsx` around lines 41 - 60,
Add a regression test in GraphReachTab.test.tsx that exercises the request-id
guard by using deferred promises for mockLoadReach: when rendering GraphReachTab
trigger two sequential loadReach calls (initial mount and a namespace change via
the combobox) but resolve the second promise before the first, then assert that
the UI reflects only the second (latest) response and that the first (stale)
resolution does not overwrite it; use mockLoadReach, mockLoadNamespaces,
render(<GraphReachTab />), fireEvent.change on the combobox, and waitFor
assertions to verify the stale-response protection.
app/src/services/api/graphReachApi.ts (1)

25-26: ⚡ Quick win

Await the namespace RPC in this async service method.

This works, but it violates the repo’s TypeScript rule to use async/await for promises and makes local logging/error handling harder to extend later.

 export async function loadNamespaces(): Promise<string[]> {
-  return memoryListNamespaces();
+  return await memoryListNamespaces();
 }

As per coding guidelines, "Always use async/await for promises in TypeScript."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/services/api/graphReachApi.ts` around lines 25 - 26, The function
loadNamespaces currently returns the Promise from memoryListNamespaces directly;
change it to await the RPC call inside the async function so the promise is
resolved before returning and future logging/error handling can be added. Edit
loadNamespaces to call and await memoryListNamespaces() (referencing the
loadNamespaces function and memoryListNamespaces symbol), assign or return the
awaited result, and preserve the Promise<string[]> signature.
app/src/lib/memory/graphReach.test.ts (1)

103-171: ⚡ Quick win

Add boundary coverage for the new feasibility guard.

This suite locks down computeGraphReach() well, but it never exercises isGraphReachFeasible() at the MAX_REACH_NODES boundary. That leaves the new freeze-prevention contract unprotected. Please add assertions for exactly 5,000 distinct nodes and 5,001 distinct nodes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/lib/memory/graphReach.test.ts` around lines 103 - 171, The tests for
computeGraphReach are missing coverage for the new MAX_REACH_NODES feasibility
guard (isGraphReachFeasible); add two cases that construct inputs with exactly
MAX_REACH_NODES distinct node ids and MAX_REACH_NODES+1 distinct node ids, call
computeGraphReach on each, and assert that the first call returns a normal reach
result (nodeCount === MAX_REACH_NODES and no feasibility error) while the second
triggers the feasibility cutoff (e.g., nodeCount === MAX_REACH_NODES or an
explicit indicator your implementation uses when exceeding MAX_REACH_NODES);
locate the new tests near the existing 'is order-independent' or normalization
block and reference computeGraphReach, isGraphReachFeasible, and MAX_REACH_NODES
to guide placement.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/services/api/graphReachApi.ts`:
- Around line 18-21: The loadReach function must enforce the 5,000-node cap
before calling computeGraphReach: after obtaining relations from
memoryGraphQuery(namespace) check the node count (relations.length or derived
node count as appropriate) and if it exceeds the cap, return/throw an error or a
ReachResult indicating the oversized-graph condition instead of calling
computeGraphReach; update loadReach to log the overflow case and short-circuit
(e.g., return a failure/empty ReachResult or reject) so computeGraphReach is
never executed on oversized graphs.

---

Nitpick comments:
In `@app/src/components/intelligence/GraphReachTab.test.tsx`:
- Around line 41-60: Add a regression test in GraphReachTab.test.tsx that
exercises the request-id guard by using deferred promises for mockLoadReach:
when rendering GraphReachTab trigger two sequential loadReach calls (initial
mount and a namespace change via the combobox) but resolve the second promise
before the first, then assert that the UI reflects only the second (latest)
response and that the first (stale) resolution does not overwrite it; use
mockLoadReach, mockLoadNamespaces, render(<GraphReachTab />), fireEvent.change
on the combobox, and waitFor assertions to verify the stale-response protection.

In `@app/src/components/intelligence/GraphReachTab.tsx`:
- Around line 40-47: Refactor the mount useEffect to use an async function with
await instead of .then/.catch: create an async inner function invoked
immediately that awaits loadNamespaces() and calls setNamespaces(result) but
catches and sets setNamespaces([]) on error, and also awaits or calls load('')
in the same async flow; reference the existing loadNamespaces, setNamespaces,
load, and useEffect symbols so the behavior (namespaces are optional and errors
are swallowed) remains identical while using async/await.

In `@app/src/lib/memory/graphReach.test.ts`:
- Around line 103-171: The tests for computeGraphReach are missing coverage for
the new MAX_REACH_NODES feasibility guard (isGraphReachFeasible); add two cases
that construct inputs with exactly MAX_REACH_NODES distinct node ids and
MAX_REACH_NODES+1 distinct node ids, call computeGraphReach on each, and assert
that the first call returns a normal reach result (nodeCount === MAX_REACH_NODES
and no feasibility error) while the second triggers the feasibility cutoff
(e.g., nodeCount === MAX_REACH_NODES or an explicit indicator your
implementation uses when exceeding MAX_REACH_NODES); locate the new tests near
the existing 'is order-independent' or normalization block and reference
computeGraphReach, isGraphReachFeasible, and MAX_REACH_NODES to guide placement.

In `@app/src/services/api/graphReachApi.ts`:
- Around line 25-26: The function loadNamespaces currently returns the Promise
from memoryListNamespaces directly; change it to await the RPC call inside the
async function so the promise is resolved before returning and future
logging/error handling can be added. Edit loadNamespaces to call and await
memoryListNamespaces() (referencing the loadNamespaces function and
memoryListNamespaces symbol), assign or return the awaited result, and preserve
the Promise<string[]> signature.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3fa2ca39-d1cd-48b6-a193-5a2f3103642d

📥 Commits

Reviewing files that changed from the base of the PR and between 1e03dd2 and c602044.

📒 Files selected for processing (9)
  • app/src/components/intelligence/GraphReachPanel.test.tsx
  • app/src/components/intelligence/GraphReachPanel.tsx
  • app/src/components/intelligence/GraphReachTab.test.tsx
  • app/src/components/intelligence/GraphReachTab.tsx
  • app/src/lib/i18n/en.ts
  • app/src/lib/memory/graphReach.test.ts
  • app/src/lib/memory/graphReach.ts
  • app/src/services/api/graphReachApi.test.ts
  • app/src/services/api/graphReachApi.ts

Comment on lines +18 to +21
export async function loadReach(namespace?: string): Promise<ReachResult> {
const relations = await memoryGraphQuery(namespace);
log('loadReach namespace=%s relations=%d', namespace ?? '(all)', relations.length);
return computeGraphReach(relations);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce the node cap inside loadReach().

Line 21 still runs computeGraphReach() unconditionally. Any caller that forgets to pre-check can hit the O(V·(V+E)) path and freeze the renderer, which is exactly what the new 5,000-node safeguard is meant to prevent. Reject oversized graphs here before computing so the UI can surface a warning/error state instead.

Suggested fix
-import { computeGraphReach, type ReachResult } from '../../lib/memory/graphReach';
+import {
+  MAX_REACH_NODES,
+  computeGraphReach,
+  isGraphReachFeasible,
+  type ReachResult,
+} from '../../lib/memory/graphReach';
 import { memoryGraphQuery, memoryListNamespaces } from '../../utils/tauriCommands/memory';
@@
 export async function loadReach(namespace?: string): Promise<ReachResult> {
   const relations = await memoryGraphQuery(namespace);
+  if (!isGraphReachFeasible(relations)) {
+    throw new Error(`Graph Reach is limited to ${MAX_REACH_NODES} nodes`);
+  }
   log('loadReach namespace=%s relations=%d', namespace ?? '(all)', relations.length);
   return computeGraphReach(relations);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/services/api/graphReachApi.ts` around lines 18 - 21, The loadReach
function must enforce the 5,000-node cap before calling computeGraphReach: after
obtaining relations from memoryGraphQuery(namespace) check the node count
(relations.length or derived node count as appropriate) and if it exceeds the
cap, return/throw an error or a ReachResult indicating the oversized-graph
condition instead of calling computeGraphReach; update loadReach to log the
overflow case and short-circuit (e.g., return a failure/empty ReachResult or
reject) so computeGraphReach is never executed on oversized graphs.

@senamakel senamakel closed this Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Net-new user-facing capability or product behavior. memory Memory store, memory tree, recall, summarization, and embeddings in src/openhuman/memory/. working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants