Skip to content

feat(intelligence): add Graph Completion Hints (Adamic-Adar triad closure)#3002

Open
aashir-athar wants to merge 1 commit into
tinyhumansai:mainfrom
aashir-athar:feat/triad-closure
Open

feat(intelligence): add Graph Completion Hints (Adamic-Adar triad closure)#3002
aashir-athar wants to merge 1 commit into
tinyhumansai:mainfrom
aashir-athar:feat/triad-closure

Conversation

@aashir-athar
Copy link
Copy Markdown
Contributor

@aashir-athar aashir-athar commented May 30, 2026

Summary

Adds a new read-only "Completion" tab — the first lens in the suite to surface what's MISSING from the graph instead of measuring what's present. For every ordered pair (A, C) with no direct edge but several shared intermediaries A→B→C, propose creating A→C, ranked by the Adamic–Adar score Σ_B 1 / log(1 + deg(B)). Low-degree intermediaries weigh more — formalising the intuition that a B who only knows A and C is much stronger structural evidence than a mega-hub who knows everyone.

Provenance & design discipline

Picked by the loop-22 judge-panel design workflow (4 fresh angles — cadence / chunks / inference / quadrants — × 3 independent judges + synthesis). Weighted score 7.87 / 10, beating Knowledge Cadence (7.78), Attention Quadrants (lowest user-value), and Chunk Corroboration Atlas (overlaps too much with #19 Document Provenance). All 5 synthesis-stage refinements applied:

  • JSON.stringify([A, C]) pair key (not NUL separator — dodges the control-char scan flag from MEMORY.md).
  • Default limit: 500 to cap output payload.
  • Per-source wedge ceiling MAX_WEDGES_PER_A = 200_000 with truncated:true flag.
  • 5th hand fixture covering empty input + self-loops.
  • Docstring documents the 1 + deg(B) shift (vs textbook deg(B)) — keeps log finite & positive at the boundary.

Engine design (pure, deterministic)

  • Predicate-AGNOSTIC: a direct A→C edge under ANY predicate suppresses the hint (cleanest "no link exists" semantics — surfacing (A, C) when an A→C edge exists under a different predicate would mislead).
  • Self-loops dropped, parallel edges collapsed.
  • Intermediary list is sorted ASC before the score sum, so the float result is byte-identical across input permutations.
  • Output sort: score DESC, support DESC, subject ASC, object ASC — a total order.
  • candidatePairCount exposes the pre-filter count so the UI can explain "1 candidate filtered by support floor" instead of just showing an empty list.

Zero new core surface

Composes the already-shipped memoryGraphQuery / memoryListNamespaces wrappers. Container/presentational split with a monotonic request-token race guard for load-on-mount; i18n across all 13 locales; aria-hidden on the decorative glyph paired with an sr-only "suggest edge to" so screen readers announce the relationship semantically.

Test plan

  • vitest — 27 tests (engine: empty/EMPTY_RESULT shape, default-filtered single-wedge, minSupport=1 exposes it, two-intermediary Adamic-Adar exact, hub-degree vs low-degree weighting, direct-edge suppression (any predicate), reverse-direction does NOT suppress, self-loop drop, parallel-edge collapse, malformed-row drop, no case-folding, byte-identical across permutations, total-order sort with tied scores, limit cap — plus api facade, panel metric tiles + worklist + intermediary chips + no-candidates / all-filtered captions, container load/namespace-requery/error)
  • tsc --noEmit — clean
  • eslint — 0 errors (1 accepted load-on-mount warning)
  • prettier --check — clean
  • i18n coverage gate — EXIT 0, no missing/extra/drifted keys across 13 locales

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features
    • Added "Graph Completion Hints" feature that suggests potential missing edges in graphs using intelligent scoring algorithms.
    • Introduced "Completion" memory tab with namespace filtering, score-based candidate ranking, and comprehensive metrics.
    • Implemented full internationalization support for 15 languages.

Review Change Stack

@aashir-athar aashir-athar requested a review from a team May 30, 2026 02:36
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 30, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c41bf316-474c-4f1e-918d-513e890a4116

📥 Commits

Reviewing files that changed from the base of the PR and between 7d22ca3 and 578cedf.

📒 Files selected for processing (22)
  • app/src/components/intelligence/TriadClosurePanel.test.tsx
  • app/src/components/intelligence/TriadClosurePanel.tsx
  • app/src/components/intelligence/TriadClosureTab.test.tsx
  • app/src/components/intelligence/TriadClosureTab.tsx
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.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/lib/memory/triadClosure.test.ts
  • app/src/lib/memory/triadClosure.ts
  • app/src/services/api/triadClosureApi.test.ts
  • app/src/services/api/triadClosureApi.ts
✅ Files skipped from review due to trivial changes (12)
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • app/src/lib/i18n/pt.ts
  • app/src/components/intelligence/TriadClosureTab.test.tsx
  • app/src/services/api/triadClosureApi.ts
  • app/src/lib/i18n/en.ts
  • app/src/services/api/triadClosureApi.test.ts
  • app/src/lib/memory/triadClosure.test.ts
  • app/src/components/intelligence/TriadClosurePanel.tsx
  • app/src/lib/memory/triadClosure.ts
  • app/src/components/intelligence/TriadClosurePanel.test.tsx
  • app/src/components/intelligence/TriadClosureTab.tsx

📝 Walkthrough

Walkthrough

This PR implements the Triad Closure feature—a graph-completion algorithm that identifies suggested edges using Adamic–Adar scoring over open wedges. It adds the deterministic computeTriadClosure function, RPC facade, UI components (panel and tab), comprehensive tests, and translations across 14 languages.

Changes

Triad Closure Feature

Layer / File(s) Summary
Core triad-closure algorithm
app/src/lib/memory/triadClosure.ts
Implements computeTriadClosure(relations, options) with deterministic Adamic–Adar scoring over open wedges, minSupport filtering, predicate-agnostic edge suppression, per-source wedge enumeration limits, and exports TriadHint, TriadClosureResult, and TriadClosureOptions types.
Algorithm unit tests
app/src/lib/memory/triadClosure.test.ts
Tests validate output shapes, minSupport/limit behavior, Adamic–Adar weighting, edge suppression, self-loop exclusion, case sensitivity, determinism across input permutations, multi-key sort order, and limit option preservation of candidatePairCount.
RPC facade and tests
app/src/services/api/triadClosureApi.ts, app/src/services/api/triadClosureApi.test.ts
loadTriadClosure(namespace?) queries relations and computes hints; loadNamespaces() returns available namespaces. Tests verify namespace passthrough, empty behavior, error propagation, and API surface.
Presentation component
app/src/components/intelligence/TriadClosurePanel.tsx
Pure component rendering loading skeleton, error+retry, empty state, and populated results with metric tiles, truncated badge, ranked worklist with score progress bars, intermediary chips capped to MAX_INTERMEDIARIES_SHOWN and alphabetized, and extra-intermediary label. Supports i18n.
Panel component tests
app/src/components/intelligence/TriadClosurePanel.test.tsx
Tests cover all states: loading, error with retry, empty (no candidates/all filtered), and populated with UI labels, suggested edges ("A" and "C"), alphabetized intermediaries ("B", "D"), score display (three decimals), and caption variations.
Container component and tests
app/src/components/intelligence/TriadClosureTab.tsx, app/src/components/intelligence/TriadClosureTab.test.tsx
Container manages result/loading/error state, namespace selection, and monotonic request IDs. Best-effort loads namespaces on mount, triggers initial and namespace-change loads, delegating UI to TriadClosurePanel with onRetry. Tests verify initial load, namespace requery, and error handling.
i18n translations
app/src/lib/i18n/*.ts
Adds memory.tab.completion and triadClosure.* translation keys across English base and 13 locales (Arabic, Bengali, German, Spanish, French, Hindi, Indonesian, Italian, Korean, Polish, Portuguese, Russian, Chinese).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • oxoxDev
  • graycyrus

🐰 Graph hints now shine so bright,
With Adamic–Adar scoring in sight!
Wedges close with deterministic care,
Suggestions bloom in every where.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% 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 clearly and specifically describes the main feature addition: a Graph Completion Hints system using Adamic-Adar triad closure algorithm, which aligns with the comprehensive changeset across components, tests, and i18n.
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.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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 May 30, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

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

43-45: ⚡ Quick win

Use async/await for the namespace bootstrap.

This .then()/.catch() chain conflicts with the repo’s TS/TSX guideline. Fold it into an async helper inside the effect so the bootstrap path matches the rest of the file.

♻️ Guideline-aligned rewrite
-    loadNamespaces()
-      .then(setNamespaces)
-      .catch(() => setNamespaces([]));
+    void (async () => {
+      try {
+        setNamespaces(await loadNamespaces());
+      } catch {
+        setNamespaces([]);
+      }
+    })();

As per coding guidelines: "Always use async/await for promises instead of .then() chains 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/TriadClosureTab.tsx` around lines 43 - 45,
The promise chain using loadNamespaces().then(setNamespaces).catch(() =>
setNamespaces([])) should be replaced with an async helper inside the same
effect: create an async function (e.g., async function bootstrapNamespaces())
that awaits loadNamespaces(), calls setNamespaces with the result, and uses
try/catch to call setNamespaces([]) on error; then invoke that helper from the
effect. Refer to loadNamespaces and setNamespaces when implementing the
await/try/catch flow so the bootstrap matches the file’s async/await style.
🤖 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/components/intelligence/TriadClosureTab.tsx`:
- Around line 24-38: The load function currently always calls setLoading(true)
and setError(null) synchronously, which causes an unwanted mount-time state
reset; modify load (the useCallback named load that uses
latestRequestId.current, setLoading, setError, setResult) to accept an optional
flag (e.g., initialLoad or skipReset) and when that flag is true skip the
initial synchronous setLoading(true)/setError(null) calls and also avoid
toggling loading in finally for that invocation; update the useEffect mount call
to invoke load('', true) (or equivalent) so the initial mount reuses existing
defaults while all other calls keep the existing behaviour and requestId
cancellation checks.

In `@app/src/lib/memory/triadClosure.ts`:
- Around line 76-79: The limit handling in TriadClosureOptions is inconsistent:
negative limits are documented as clamped to 0 but the code uses rawLimit <= 0
to treat negatives as "unlimited". In the TriadClosure logic (look for rawLimit
usage where you normalize options and any code near the previous change around
lines 103-105), change the normalization so that rawLimit === 0 is the only
sentinel for "unlimited", and if rawLimit < 0 clamp it to 0 (or otherwise return
an empty slice/fail closed) before using it; replace checks like rawLimit <= 0
with a two-way branch: if rawLimit === 0 => unlimited, else if rawLimit < 0 =>
set limit = 0, else limit = rawLimit.

In `@app/src/services/api/triadClosureApi.ts`:
- Around line 14-19: The log call in loadTriadClosure currently prints the raw
namespace (PII) — change it to use a stable bracketed prefix and a redacted
correlation field instead: do not log namespace verbatim; compute a redacted
scope/correlation (e.g., a fixed prefix like "[triad-closure]" plus a masked or
hashed namespace ID or an opaque request/correlation id) and pass that into the
log call along with relations.length; update the log invocation in
loadTriadClosure (which calls memoryGraphQuery) to emit something like a
bracketed prefix and the redacted correlation field instead of namespace to
avoid leaking PII.

---

Nitpick comments:
In `@app/src/components/intelligence/TriadClosureTab.tsx`:
- Around line 43-45: The promise chain using
loadNamespaces().then(setNamespaces).catch(() => setNamespaces([])) should be
replaced with an async helper inside the same effect: create an async function
(e.g., async function bootstrapNamespaces()) that awaits loadNamespaces(), calls
setNamespaces with the result, and uses try/catch to call setNamespaces([]) on
error; then invoke that helper from the effect. Refer to loadNamespaces and
setNamespaces when implementing the await/try/catch flow so the bootstrap
matches the file’s async/await style.
🪄 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: dc119219-83d7-4de3-aa7b-540e2af79dc8

📥 Commits

Reviewing files that changed from the base of the PR and between ba498db and b032c37.

📒 Files selected for processing (23)
  • app/src/components/intelligence/TriadClosurePanel.test.tsx
  • app/src/components/intelligence/TriadClosurePanel.tsx
  • app/src/components/intelligence/TriadClosureTab.test.tsx
  • app/src/components/intelligence/TriadClosureTab.tsx
  • app/src/lib/i18n/chunks/ar-1.ts
  • app/src/lib/i18n/chunks/bn-1.ts
  • app/src/lib/i18n/chunks/de-1.ts
  • app/src/lib/i18n/chunks/en-1.ts
  • app/src/lib/i18n/chunks/es-1.ts
  • app/src/lib/i18n/chunks/fr-1.ts
  • app/src/lib/i18n/chunks/hi-1.ts
  • app/src/lib/i18n/chunks/id-1.ts
  • app/src/lib/i18n/chunks/it-1.ts
  • app/src/lib/i18n/chunks/ko-1.ts
  • app/src/lib/i18n/chunks/pt-1.ts
  • app/src/lib/i18n/chunks/ru-1.ts
  • app/src/lib/i18n/chunks/zh-CN-1.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/memory/triadClosure.test.ts
  • app/src/lib/memory/triadClosure.ts
  • app/src/pages/Intelligence.tsx
  • app/src/services/api/triadClosureApi.test.ts
  • app/src/services/api/triadClosureApi.ts

Comment on lines +24 to +38
const load = useCallback(async (ns: string) => {
const requestId = (latestRequestId.current += 1);
setLoading(true);
setError(null);
try {
const next = await loadTriadClosure(ns || undefined);
if (requestId !== latestRequestId.current) return;
setResult(next);
} catch (err) {
if (requestId !== latestRequestId.current) return;
setError(err instanceof Error ? err.message : String(err));
} finally {
if (requestId === latestRequestId.current) setLoading(false);
}
}, []);
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.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Keep the mount effect from synchronously resetting local state.

void load('') still executes setLoading(true) and setError(null) before the first await, so the initial useEffect is indirectly doing the synchronous state reset pattern this repo has been avoiding. Please special-case the mount path so the effect can reuse the existing loading/error defaults without calling those setters first.

♻️ One minimal way to avoid the mount-time reset
-  const load = useCallback(async (ns: string) => {
+  const load = useCallback(async (ns: string, resetState = true) => {
     const requestId = (latestRequestId.current += 1);
-    setLoading(true);
-    setError(null);
+    if (resetState) {
+      setLoading(true);
+      setError(null);
+    }
     try {
       const next = await loadTriadClosure(ns || undefined);
       if (requestId !== latestRequestId.current) return;
       setResult(next);
@@
-    void load('');
+    void load('', false);
   }, [load]);

Based on learnings: "In React components, do not perform synchronous setState (or other state-updating calls) directly inside useEffect bodies."

Also applies to: 40-47

🤖 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/TriadClosureTab.tsx` around lines 24 - 38,
The load function currently always calls setLoading(true) and setError(null)
synchronously, which causes an unwanted mount-time state reset; modify load (the
useCallback named load that uses latestRequestId.current, setLoading, setError,
setResult) to accept an optional flag (e.g., initialLoad or skipReset) and when
that flag is true skip the initial synchronous setLoading(true)/setError(null)
calls and also avoid toggling loading in finally for that invocation; update the
useEffect mount call to invoke load('', true) (or equivalent) so the initial
mount reuses existing defaults while all other calls keep the existing behaviour
and requestId cancellation checks.

Comment on lines +76 to +79
export interface TriadClosureOptions {
minSupport?: number; // default 2
limit?: number; // default 500 (pass 0 for unlimited; negative is clamped to 0)
}
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 | 🟡 Minor | ⚡ Quick win

Make negative limit handling match the public contract.

TriadClosureOptions documents negative limits as clamped, but rawLimit <= 0 currently turns -1 into an unlimited result set. That makes a bad caller value expand the payload instead of failing closed or returning an empty slice.

♻️ One way to normalize negatives separately from the `0 = unlimited` sentinel
-  const rawLimit = options?.limit ?? DEFAULT_LIMIT;
-  const limit = rawLimit <= 0 ? Number.POSITIVE_INFINITY : Math.floor(rawLimit);
+  const rawLimit = Math.floor(options?.limit ?? DEFAULT_LIMIT);
+  const limit = rawLimit === 0 ? Number.POSITIVE_INFINITY : Math.max(0, rawLimit);

Also applies to: 103-105

🤖 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/triadClosure.ts` around lines 76 - 79, The limit handling
in TriadClosureOptions is inconsistent: negative limits are documented as
clamped to 0 but the code uses rawLimit <= 0 to treat negatives as "unlimited".
In the TriadClosure logic (look for rawLimit usage where you normalize options
and any code near the previous change around lines 103-105), change the
normalization so that rawLimit === 0 is the only sentinel for "unlimited", and
if rawLimit < 0 clamp it to 0 (or otherwise return an empty slice/fail closed)
before using it; replace checks like rawLimit <= 0 with a two-way branch: if
rawLimit === 0 => unlimited, else if rawLimit < 0 => set limit = 0, else limit =
rawLimit.

Comment on lines +14 to +19
const log = debug('triad-closure:api');

/** Fetch graph relations for a namespace (or all) and compute closure hints. */
export async function loadTriadClosure(namespace?: string): Promise<TriadClosureResult> {
const relations = await memoryGraphQuery(namespace);
log('loadTriadClosure namespace=%s relations=%d', namespace ?? '(all)', relations.length);
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

Redact namespace before logging.

namespace values here can carry user identifiers, so logging them verbatim leaks PII into debug output. Please switch to a stable bracketed prefix and log only a redacted scope/correlation field instead.

🛡️ Safer logging shape
-const log = debug('triad-closure:api');
+const log = debug('[rpc] triadClosureApi');
...
-  log('loadTriadClosure namespace=%s relations=%d', namespace ?? '(all)', relations.length);
+  log(
+    'method=loadTriadClosure namespaceScope=%s relations=%d',
+    namespace ? 'single' : 'all',
+    relations.length
+  );

As per coding guidelines **/*.{rs,ts,tsx}: Use stable grep-friendly log prefixes ([domain], [rpc], [ui-flow]) and correlation fields (request IDs, method names, entity IDs). Never log secrets or full PII in debug logs; redact sensitive information.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const log = debug('triad-closure:api');
/** Fetch graph relations for a namespace (or all) and compute closure hints. */
export async function loadTriadClosure(namespace?: string): Promise<TriadClosureResult> {
const relations = await memoryGraphQuery(namespace);
log('loadTriadClosure namespace=%s relations=%d', namespace ?? '(all)', relations.length);
const log = debug('[rpc] triadClosureApi');
/** Fetch graph relations for a namespace (or all) and compute closure hints. */
export async function loadTriadClosure(namespace?: string): Promise<TriadClosureResult> {
const relations = await memoryGraphQuery(namespace);
log(
'method=loadTriadClosure namespaceScope=%s relations=%d',
namespace ? 'single' : 'all',
relations.length
);
🤖 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/triadClosureApi.ts` around lines 14 - 19, The log call
in loadTriadClosure currently prints the raw namespace (PII) — change it to use
a stable bracketed prefix and a redacted correlation field instead: do not log
namespace verbatim; compute a redacted scope/correlation (e.g., a fixed prefix
like "[triad-closure]" plus a masked or hashed namespace ID or an opaque
request/correlation id) and pass that into the log call along with
relations.length; update the log invocation in loadTriadClosure (which calls
memoryGraphQuery) to emit something like a bracketed prefix and the redacted
correlation field instead of namespace to avoid leaking PII.

@oxoxDev
Copy link
Copy Markdown
Contributor

oxoxDev commented May 30, 2026

@aashir-athar Strong, consistent work across the intelligence series — each module's logic is clean (e.g. #2967's export serializers are RFC-4180 correct, with malformed-row drops and finite-number guards).

One structural issue spanning the whole series, raising it here as the anchor rather than on all ~22 PRs:

Every PR in the wave edits the same three shared points:

  • the IntelligenceTab union type in app/src/pages/Intelligence.tsx
  • the tab-registration array in the same file
  • app/src/lib/i18n/en.ts + the 14 locale chunks

Because they all append to the same regions, the PRs mutually conflict — only one can merge before the rest go DIRTY (e.g. #2967 is already conflicting). A 22-deep rebase chain isn't workable.

Suggested restructure:

  1. Land one small base PR that makes the tab registry data-driven — a registry array in its own module that each feature imports into — plus the shared i18n scaffolding.
  2. Rebase each feature PR to add only its own module files + a single registry entry (no edits to the shared union/array/i18n).

That removes the cross-PR conflicts and lets them merge in any order. Happy to fast-track reviews once they're restructured.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🤖 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/lib/i18n/es.ts`:
- Around line 4215-4236: The Spanish locale currently contains English strings
for the new triad-closure keys; replace the English values with proper Spanish
translations for each key added (e.g., 'triadClosure.title',
'triadClosure.intro', 'triadClosure.loading', 'triadClosure.errorPrefix',
'triadClosure.retry', 'triadClosure.empty', 'triadClosure.emptyHint',
'triadClosure.namespaceLabel', 'triadClosure.namespaceAll',
'triadClosure.metricHints', 'triadClosure.metricCandidates',
'triadClosure.metricSupport', 'triadClosure.summaryCaption',
'triadClosure.truncatedBadge', 'triadClosure.truncatedTitle',
'triadClosure.noCandidates', 'triadClosure.allFiltered',
'triadClosure.rankedHeading', 'triadClosure.suggestEdgeTo',
'triadClosure.viaPrefix', 'triadClosure.extraIntermediaries', and
'memory.tab.completion') with accurate, natural Spanish copy (including
accents/punctuation) that preserves meaning and placeholders like {nodes},
{edges}, {count}, and {n}. Ensure translations fit UI context and
pluralization/placeholder usage matches the original intent.

In `@app/src/lib/i18n/fr.ts`:
- Around line 4232-4253: Replace the English strings for the newly added i18n
keys (all keys beginning with 'triadClosure.' shown and 'memory.tab.completion')
with proper French translations, preserving interpolation tokens like {nodes},
{edges}, {count}, and {n} and keeping punctuation/typography (e.g., em dashes,
ellipses) intact; update values for 'triadClosure.title', 'triadClosure.intro',
'triadClosure.loading', 'triadClosure.errorPrefix', 'triadClosure.retry',
'triadClosure.empty', 'triadClosure.emptyHint', 'triadClosure.namespaceLabel',
'triadClosure.namespaceAll', 'triadClosure.metricHints',
'triadClosure.metricCandidates', 'triadClosure.metricSupport',
'triadClosure.summaryCaption', 'triadClosure.truncatedBadge',
'triadClosure.truncatedTitle', 'triadClosure.noCandidates',
'triadClosure.allFiltered', 'triadClosure.rankedHeading',
'triadClosure.suggestEdgeTo', 'triadClosure.viaPrefix',
'triadClosure.extraIntermediaries' and 'memory.tab.completion' with accurate,
idiomatic French strings. Ensure translations match the original meaning
(technical terms like "Adamíc–Adar score" may be left or explained in French)
and run a quick lint/format check after editing.

In `@app/src/lib/i18n/id.ts`:
- Around line 4155-4176: The new triadClosure.* keys are still in English;
replace each English string for 'triadClosure.title', 'triadClosure.intro',
'triadClosure.loading', 'triadClosure.errorPrefix', 'triadClosure.retry',
'triadClosure.empty', 'triadClosure.emptyHint', 'triadClosure.namespaceLabel',
'triadClosure.namespaceAll', 'triadClosure.metricHints',
'triadClosure.metricCandidates', 'triadClosure.metricSupport',
'triadClosure.summaryCaption', 'triadClosure.truncatedBadge',
'triadClosure.truncatedTitle', 'triadClosure.noCandidates',
'triadClosure.allFiltered', 'triadClosure.rankedHeading',
'triadClosure.suggestEdgeTo', 'triadClosure.viaPrefix', and
'triadClosure.extraIntermediaries' with correct Indonesian translations
(preserve interpolation placeholders like {nodes}, {edges}, {count}, {n} and any
punctuation or technical terms such as "Adamic–Adar" if you decide to keep
them), so the id.ts locale file is a real Indonesian localization rather than
English literals.

In `@app/src/lib/i18n/it.ts`:
- Around line 4210-4231: The Italian locale contains untranslated English
strings for the triad-closure keys and memory.tab.completion; update the values
for the following keys in it.ts: 'triadClosure.title', 'triadClosure.intro',
'triadClosure.loading', 'triadClosure.errorPrefix', 'triadClosure.retry',
'triadClosure.empty', 'triadClosure.emptyHint', 'triadClosure.namespaceLabel',
'triadClosure.namespaceAll', 'triadClosure.metricHints',
'triadClosure.metricCandidates', 'triadClosure.metricSupport',
'triadClosure.summaryCaption', 'triadClosure.truncatedBadge',
'triadClosure.truncatedTitle', 'triadClosure.noCandidates',
'triadClosure.allFiltered', 'triadClosure.rankedHeading',
'triadClosure.suggestEdgeTo', 'triadClosure.viaPrefix',
'triadClosure.extraIntermediaries' and 'memory.tab.completion' with proper
Italian translations; preserve placeholders like {nodes}, {edges}, {count}, {n}
and punctuation/typographic characters (e.g., em dashes, ellipses), and ensure
translations are idiomatic and grammatically correct for UI display.

In `@app/src/lib/i18n/ko.ts`:
- Around line 4108-4129: The new triadClosure.* i18n entries in ko.ts are still
in English; replace each English string for keys triadClosure.title,
triadClosure.intro, triadClosure.loading, triadClosure.errorPrefix,
triadClosure.retry, triadClosure.empty, triadClosure.emptyHint,
triadClosure.namespaceLabel, triadClosure.namespaceAll,
triadClosure.metricHints, triadClosure.metricCandidates,
triadClosure.metricSupport, triadClosure.summaryCaption,
triadClosure.truncatedBadge, triadClosure.truncatedTitle,
triadClosure.noCandidates, triadClosure.allFiltered, triadClosure.rankedHeading,
triadClosure.suggestEdgeTo, triadClosure.viaPrefix,
triadClosure.extraIntermediaries (and memory.tab.completion if applicable) with
correct Korean translations consistent with the meanings in the English en.ts
additions; ensure phrasing/placeholders (e.g., {nodes},{edges},{count},{n}) are
preserved and verify punctuation/ellipsis matches Korean conventions so the
locale displays proper Korean text for the Graph Completion Hints feature.

In `@app/src/lib/i18n/pl.ts`:
- Around line 4211-4232: Replace the English placeholder values in pl.ts for all
newly added i18n keys (e.g. 'triadClosure.title', 'triadClosure.intro',
'triadClosure.loading', 'triadClosure.errorPrefix', 'triadClosure.retry',
'triadClosure.empty', 'triadClosure.emptyHint', 'triadClosure.namespaceLabel',
'triadClosure.namespaceAll', 'triadClosure.metricHints',
'triadClosure.metricCandidates', 'triadClosure.metricSupport',
'triadClosure.summaryCaption', 'triadClosure.truncatedBadge',
'triadClosure.truncatedTitle', 'triadClosure.noCandidates',
'triadClosure.allFiltered', 'triadClosure.rankedHeading',
'triadClosure.suggestEdgeTo', 'triadClosure.viaPrefix',
'triadClosure.extraIntermediaries', and 'memory.tab.completion') with correct
Polish translations; ensure the translations preserve placeholders like {nodes},
{edges}, {count}, and {n} and maintain punctuation and capitalization consistent
with other Polish locale entries, then run a quick lint/i18n parity check to
confirm no English strings remain.

In `@app/src/lib/i18n/pt.ts`:
- Around line 4210-4231: The new i18n entries (triadClosure.title,
triadClosure.intro, triadClosure.loading, triadClosure.errorPrefix,
triadClosure.retry, triadClosure.empty, triadClosure.emptyHint,
triadClosure.namespaceLabel, triadClosure.namespaceAll,
triadClosure.metricHints, triadClosure.metricCandidates,
triadClosure.metricSupport, triadClosure.summaryCaption,
triadClosure.truncatedBadge, triadClosure.truncatedTitle,
triadClosure.noCandidates, triadClosure.allFiltered, triadClosure.rankedHeading,
triadClosure.suggestEdgeTo, triadClosure.viaPrefix,
triadClosure.extraIntermediaries, and memory.tab.completion) are left in
English; replace each English string in app/src/lib/i18n/pt.ts with accurate
Portuguese translations (not English) matching the meaning and tone of the
original UI text, preserving placeholders like {nodes}, {edges}, {count}, and
{n} exactly and keeping punctuation/ellipsis where appropriate so the keys
(e.g., triadClosure.intro, triadClosure.summaryCaption) render correctly for
Portuguese users.

In `@app/src/lib/i18n/ru.ts`:
- Around line 4179-4200: The listed i18n entries (triadClosure.title,
triadClosure.intro, triadClosure.loading, triadClosure.errorPrefix,
triadClosure.retry, triadClosure.empty, triadClosure.emptyHint,
triadClosure.namespaceLabel, triadClosure.namespaceAll,
triadClosure.metricHints, triadClosure.metricCandidates,
triadClosure.metricSupport, triadClosure.summaryCaption,
triadClosure.truncatedBadge, triadClosure.truncatedTitle,
triadClosure.noCandidates, triadClosure.allFiltered, triadClosure.rankedHeading,
triadClosure.suggestEdgeTo, triadClosure.viaPrefix,
triadClosure.extraIntermediaries and memory.tab.completion) are still in
English; replace each string value with a correct Russian translation
(preserving placeholders like {nodes}, {edges}, {count}, {n} and punctuation) so
Russian locale users see localized UI text. Ensure translations are natural,
concise, and respect existing formatting and placeholders when updating the
corresponding keys (e.g., triadClosure.intro, triadClosure.summaryCaption,
triadClosure.allFiltered).

In `@app/src/lib/i18n/zh-CN.ts`:
- Around line 3941-3962: The triadClosure i18n entries in zh-CN.ts currently
contain English placeholders; update all triadClosure.* keys (e.g.,
'triadClosure.title', 'triadClosure.intro', 'triadClosure.loading',
'triadClosure.errorPrefix', 'triadClosure.retry', 'triadClosure.empty',
'triadClosure.emptyHint', 'triadClosure.namespaceLabel',
'triadClosure.namespaceAll', 'triadClosure.metricHints',
'triadClosure.metricCandidates', 'triadClosure.metricSupport',
'triadClosure.summaryCaption', 'triadClosure.truncatedBadge',
'triadClosure.truncatedTitle', 'triadClosure.noCandidates',
'triadClosure.allFiltered', 'triadClosure.rankedHeading',
'triadClosure.suggestEdgeTo', 'triadClosure.viaPrefix',
'triadClosure.extraIntermediaries', plus 'memory.tab.completion') with proper
Simplified Chinese translations (replace English text with Chinese), preserving
any placeholders like {nodes}, {edges}, {count}, {n} and punctuation/ellipsis
semantics; ensure translations are accurate, concise, and idiomatic for zh-CN
users.
🪄 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: 017bb3c3-3273-42e8-967a-9fc65d5525c2

📥 Commits

Reviewing files that changed from the base of the PR and between a50b4cf and 8393441.

📒 Files selected for processing (23)
  • app/src/components/intelligence/TriadClosurePanel.test.tsx
  • app/src/components/intelligence/TriadClosurePanel.tsx
  • app/src/components/intelligence/TriadClosureTab.test.tsx
  • app/src/components/intelligence/TriadClosureTab.tsx
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.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/lib/memory/triadClosure.test.ts
  • app/src/lib/memory/triadClosure.ts
  • app/src/pages/Intelligence.tsx
  • app/src/services/api/triadClosureApi.test.ts
  • app/src/services/api/triadClosureApi.ts
✅ Files skipped from review due to trivial changes (5)
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • app/src/services/api/triadClosureApi.test.ts
  • app/src/services/api/triadClosureApi.ts
  • app/src/pages/Intelligence.tsx
  • app/src/components/intelligence/TriadClosureTab.tsx
  • app/src/components/intelligence/TriadClosureTab.test.tsx
  • app/src/lib/memory/triadClosure.ts
  • app/src/lib/memory/triadClosure.test.ts
  • app/src/components/intelligence/TriadClosurePanel.tsx
  • app/src/components/intelligence/TriadClosurePanel.test.tsx

Comment thread app/src/lib/i18n/es.ts Outdated
Comment thread app/src/lib/i18n/fr.ts Outdated
Comment thread app/src/lib/i18n/id.ts Outdated
Comment thread app/src/lib/i18n/it.ts Outdated
Comment on lines +4210 to +4231
'triadClosure.title': 'Graph Completion Hints',
'triadClosure.intro': "Every other lens measures relations that ALREADY exist. This one surfaces what's MISSING: for every ordered pair (A, C) with no direct edge but several shared intermediaries A→B→C, propose creating A→C. Hints are ranked by the Adamic–Adar score — low-degree intermediaries weigh more, because a B that only knows A and C is much stronger evidence than a mega-hub that knows everyone.",
'triadClosure.loading': 'Computing closure hints…',
'triadClosure.errorPrefix': 'Could not load the graph:',
'triadClosure.retry': 'Retry',
'triadClosure.empty': 'No knowledge graph yet.',
'triadClosure.emptyHint': 'As the assistant records relations about you, suggested closing edges will surface here.',
'triadClosure.namespaceLabel': 'Namespace',
'triadClosure.namespaceAll': 'All namespaces',
'triadClosure.metricHints': 'Suggested edges',
'triadClosure.metricCandidates': 'Candidate pairs',
'triadClosure.metricSupport': 'Minimum support',
'triadClosure.summaryCaption': '{nodes} entities · {edges} directed edges',
'triadClosure.truncatedBadge': 'truncated',
'triadClosure.truncatedTitle': 'A hub-heavy source node hit the per-source wedge cap — some hints may be missing for that source.',
'triadClosure.noCandidates': 'No open triads — the graph has no wedges to close.',
'triadClosure.allFiltered': '{count} candidate pairs filtered out by support floor — every closing wedge was single-intermediary. Lower minSupport to see them.',
'triadClosure.rankedHeading': 'Suggested edges to consider',
'triadClosure.suggestEdgeTo': 'suggest edge to',
'triadClosure.viaPrefix': 'via',
'triadClosure.extraIntermediaries': '+{n} more',
'memory.tab.completion': 'Completion',
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

Italian locale includes English strings for new triad-closure keys

Lines 4210-4231 are user-visible Italian locale entries but remain in English (including memory.tab.completion). Please replace them with proper Italian translations to meet locale parity and avoid mixed-language UI.

🌐 Suggested translation patch
-  'triadClosure.title': 'Graph Completion Hints',
+  'triadClosure.title': 'Suggerimenti di completamento del grafo',
-  'triadClosure.intro': "Every other lens measures relations that ALREADY exist. This one surfaces what's MISSING: for every ordered pair (A, C) with no direct edge but several shared intermediaries A→B→C, propose creating A→C. Hints are ranked by the Adamic–Adar score — low-degree intermediaries weigh more, because a B that only knows A and C is much stronger evidence than a mega-hub that knows everyone.",
+  'triadClosure.intro': "Le altre viste misurano relazioni che ESISTONO GIÀ. Questa evidenzia ciò che MANCA: per ogni coppia ordinata (A, C) senza arco diretto ma con intermediari condivisi A→B→C, propone la creazione di A→C. I suggerimenti sono ordinati con il punteggio Adamic–Adar — gli intermediari con basso grado pesano di più, perché un B che conosce solo A e C è un indizio molto più forte di un mega-hub collegato a tutti.",
-  'triadClosure.loading': 'Computing closure hints…',
+  'triadClosure.loading': 'Calcolo dei suggerimenti di chiusura…',
-  'triadClosure.errorPrefix': 'Could not load the graph:',
+  'triadClosure.errorPrefix': 'Impossibile caricare il grafo:',
-  'triadClosure.retry': 'Retry',
+  'triadClosure.retry': 'Riprova',
-  'triadClosure.empty': 'No knowledge graph yet.',
+  'triadClosure.empty': 'Nessun grafo della conoscenza al momento.',
-  'triadClosure.emptyHint': 'As the assistant records relations about you, suggested closing edges will surface here.',
+  'triadClosure.emptyHint': 'Man mano che l’assistente registra relazioni su di te, qui compariranno i possibili archi di chiusura.',
-  'triadClosure.namespaceLabel': 'Namespace',
+  'triadClosure.namespaceLabel': 'Spazio dei nomi',
-  'triadClosure.namespaceAll': 'All namespaces',
+  'triadClosure.namespaceAll': 'Tutti gli spazi dei nomi',
-  'triadClosure.metricHints': 'Suggested edges',
+  'triadClosure.metricHints': 'Archi suggeriti',
-  'triadClosure.metricCandidates': 'Candidate pairs',
+  'triadClosure.metricCandidates': 'Coppie candidate',
-  'triadClosure.metricSupport': 'Minimum support',
+  'triadClosure.metricSupport': 'Supporto minimo',
-  'triadClosure.summaryCaption': '{nodes} entities · {edges} directed edges',
+  'triadClosure.summaryCaption': '{nodes} entità · {edges} archi diretti',
-  'triadClosure.truncatedBadge': 'truncated',
+  'triadClosure.truncatedBadge': 'troncato',
-  'triadClosure.truncatedTitle': 'A hub-heavy source node hit the per-source wedge cap — some hints may be missing for that source.',
+  'triadClosure.truncatedTitle': 'Un nodo sorgente con molti hub ha raggiunto il limite di wedge per sorgente: alcuni suggerimenti potrebbero mancare per quella sorgente.',
-  'triadClosure.noCandidates': 'No open triads — the graph has no wedges to close.',
+  'triadClosure.noCandidates': 'Nessuna triade aperta: il grafo non ha wedge da chiudere.',
-  'triadClosure.allFiltered': '{count} candidate pairs filtered out by support floor — every closing wedge was single-intermediary. Lower minSupport to see them.',
+  'triadClosure.allFiltered': '{count} coppie candidate filtrate dalla soglia minima di supporto: ogni wedge di chiusura aveva un solo intermediario. Riduci minSupport per visualizzarle.',
-  'triadClosure.rankedHeading': 'Suggested edges to consider',
+  'triadClosure.rankedHeading': 'Archi suggeriti da valutare',
-  'triadClosure.suggestEdgeTo': 'suggest edge to',
+  'triadClosure.suggestEdgeTo': 'suggerisci arco verso',
-  'triadClosure.viaPrefix': 'via',
+  'triadClosure.viaPrefix': 'tramite',
-  'triadClosure.extraIntermediaries': '+{n} more',
+  'triadClosure.extraIntermediaries': '+{n} altri',
-  'memory.tab.completion': 'Completion',
+  'memory.tab.completion': 'Completamento',

As per coding guidelines: “When adding or changing i18n keys in app/src/lib/i18n/en.ts, add the same key with a real, correct translation (not English) to every non-English locale file…”.

🤖 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/i18n/it.ts` around lines 4210 - 4231, The Italian locale contains
untranslated English strings for the triad-closure keys and
memory.tab.completion; update the values for the following keys in it.ts:
'triadClosure.title', 'triadClosure.intro', 'triadClosure.loading',
'triadClosure.errorPrefix', 'triadClosure.retry', 'triadClosure.empty',
'triadClosure.emptyHint', 'triadClosure.namespaceLabel',
'triadClosure.namespaceAll', 'triadClosure.metricHints',
'triadClosure.metricCandidates', 'triadClosure.metricSupport',
'triadClosure.summaryCaption', 'triadClosure.truncatedBadge',
'triadClosure.truncatedTitle', 'triadClosure.noCandidates',
'triadClosure.allFiltered', 'triadClosure.rankedHeading',
'triadClosure.suggestEdgeTo', 'triadClosure.viaPrefix',
'triadClosure.extraIntermediaries' and 'memory.tab.completion' with proper
Italian translations; preserve placeholders like {nodes}, {edges}, {count}, {n}
and punctuation/typographic characters (e.g., em dashes, ellipses), and ensure
translations are idiomatic and grammatically correct for UI display.

Comment thread app/src/lib/i18n/ko.ts Outdated
Comment thread app/src/lib/i18n/pl.ts Outdated
Comment thread app/src/lib/i18n/pt.ts Outdated
Comment thread app/src/lib/i18n/ru.ts Outdated
Comment on lines +4179 to +4200
'triadClosure.title': 'Graph Completion Hints',
'triadClosure.intro': "Every other lens measures relations that ALREADY exist. This one surfaces what's MISSING: for every ordered pair (A, C) with no direct edge but several shared intermediaries A→B→C, propose creating A→C. Hints are ranked by the Adamic–Adar score — low-degree intermediaries weigh more, because a B that only knows A and C is much stronger evidence than a mega-hub that knows everyone.",
'triadClosure.loading': 'Computing closure hints…',
'triadClosure.errorPrefix': 'Could not load the graph:',
'triadClosure.retry': 'Retry',
'triadClosure.empty': 'No knowledge graph yet.',
'triadClosure.emptyHint': 'As the assistant records relations about you, suggested closing edges will surface here.',
'triadClosure.namespaceLabel': 'Namespace',
'triadClosure.namespaceAll': 'All namespaces',
'triadClosure.metricHints': 'Suggested edges',
'triadClosure.metricCandidates': 'Candidate pairs',
'triadClosure.metricSupport': 'Minimum support',
'triadClosure.summaryCaption': '{nodes} entities · {edges} directed edges',
'triadClosure.truncatedBadge': 'truncated',
'triadClosure.truncatedTitle': 'A hub-heavy source node hit the per-source wedge cap — some hints may be missing for that source.',
'triadClosure.noCandidates': 'No open triads — the graph has no wedges to close.',
'triadClosure.allFiltered': '{count} candidate pairs filtered out by support floor — every closing wedge was single-intermediary. Lower minSupport to see them.',
'triadClosure.rankedHeading': 'Suggested edges to consider',
'triadClosure.suggestEdgeTo': 'suggest edge to',
'triadClosure.viaPrefix': 'via',
'triadClosure.extraIntermediaries': '+{n} more',
'memory.tab.completion': 'Completion',
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

Translate new triadClosure.* and memory.tab.completion strings into Russian.

These newly added Russian-locale entries are still English, so Russian users will see untranslated UI text in the Completion tab.

💡 Suggested direction
-  'triadClosure.title': 'Graph Completion Hints',
+  'triadClosure.title': 'Подсказки дополнения графа',
...
-  'triadClosure.retry': 'Retry',
+  'triadClosure.retry': 'Повторить',
...
-  'memory.tab.completion': 'Completion',
+  'memory.tab.completion': 'Дополнение',

Please apply this to all added triadClosure.* keys in this hunk.

As per coding guidelines, "When adding or changing i18n keys in app/src/lib/i18n/en.ts, add the same key with a real, correct translation (not English) to every non-English locale file."

🤖 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/i18n/ru.ts` around lines 4179 - 4200, The listed i18n entries
(triadClosure.title, triadClosure.intro, triadClosure.loading,
triadClosure.errorPrefix, triadClosure.retry, triadClosure.empty,
triadClosure.emptyHint, triadClosure.namespaceLabel, triadClosure.namespaceAll,
triadClosure.metricHints, triadClosure.metricCandidates,
triadClosure.metricSupport, triadClosure.summaryCaption,
triadClosure.truncatedBadge, triadClosure.truncatedTitle,
triadClosure.noCandidates, triadClosure.allFiltered, triadClosure.rankedHeading,
triadClosure.suggestEdgeTo, triadClosure.viaPrefix,
triadClosure.extraIntermediaries and memory.tab.completion) are still in
English; replace each string value with a correct Russian translation
(preserving placeholders like {nodes}, {edges}, {count}, {n} and punctuation) so
Russian locale users see localized UI text. Ensure translations are natural,
concise, and respect existing formatting and placeholders when updating the
corresponding keys (e.g., triadClosure.intro, triadClosure.summaryCaption,
triadClosure.allFiltered).

Comment thread app/src/lib/i18n/zh-CN.ts Outdated
…sure)

A new read-only "Completion" tab. Every other lens measures relations
that ALREADY exist; this is the first to surface what's MISSING. For
every ordered (A, C) pair with no direct edge but several shared
intermediaries A→B→C, propose creating A→C — ranked by the Adamic–Adar
score:

  score(A, C) = Σ_B 1 / log(1 + deg(B))

Low-degree intermediaries weigh more, formalising the intuition that a
B who only knows A and C is much stronger evidence than a mega-hub.

Picked by the loop-22 judge-panel design workflow (4 fresh angles ×
3 judges + synthesis): weighted score 7.87, beating Knowledge Cadence
(7.78), Attention Quadrants, and Chunk Corroboration Atlas.

Engine (pure, deterministic — no React/RPC/clock/RNG):
- Predicate-AGNOSTIC: a direct A→C edge under ANY predicate suppresses
  the hint (cleanest "no link exists" semantics).
- Self-loops dropped; parallel edges collapsed.
- Per-pair score is summed over intermediaries in canonical sorted
  order so the float is byte-identical across input permutations.
- Pair key uses JSON.stringify([s, o]) (no NUL separator) so the
  control-char scan stays at zero.
- Default minSupport=2, limit=500, per-source wedge ceiling 200k with
  `truncated:true` flag surfaced in the UI.
- candidatePairCount exposes the pre-filter count so users understand
  when minSupport ate every wedge.

Adds ZERO new core surface: composes the already-shipped
memoryGraphQuery / memoryListNamespaces wrappers. Container/
presentational split with a monotonic request-token race guard for
load-on-mount; i18n across all 13 locales; aria-hidden on the
decorative → glyph + sr-only "suggest edge to" for screen readers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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