feat(memory): redesign memory workspace with graph, insights, heatmap#185
feat(memory): redesign memory workspace with graph, insights, heatmap#185senamakel merged 6 commits intotinyhumansai:mainfrom
Conversation
… performance - Updated the MemoryHeatmap component to track document/relation timestamps over the last 8 months instead of 52 weeks. - Improved date handling by aligning the start date to the nearest Sunday and counting timestamps within the display range. - Enhanced grid generation logic to dynamically calculate the number of weeks displayed based on the available data. - Adjusted SVG dimensions to ensure proper scaling and responsiveness. - Updated UI text to reflect the new time frame for event tracking. These changes improve the accuracy and usability of the MemoryHeatmap, providing a clearer view of user activity over time.
Break MemoryWorkspace into focused sub-components (MemoryStatsBar, MemoryGraphMap, MemoryInsights, MemoryHeatmap). Fix lint errors in MemoryGraphMap (window.rAF, unused ref). Change heatmap to fixed 8-month range with responsive SVG width. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughFour new React components— Changes
Sequence DiagramsequenceDiagram
participant W as MemoryWorkspace
participant G as MemoryGraphMap
participant H as MemoryHeatmap
participant I as MemoryInsights
participant S as MemoryStatsBar
W->>W: Extract relations, cap/sort edges & nodes
W->>W: Extract timestamps from docs & relations
W->>W: Compute storage, oldest/newest timestamps
W->>G: send relations, loading
activate G
G->>G: compute force layout (repel/attract/center)
G->>G: render SVG edges, nodes, legend
deactivate G
W->>H: send heatmapTimestamps, loading
activate H
H->>H: aggregate daily counts (6-month window)
H->>H: render heatmap grid + tooltip
deactivate H
W->>I: send relations, loading
activate I
I->>I: categorize relations by predicate
I->>I: render expandable category cards
deactivate I
W->>S: send metrics, loading
activate S
S->>S: format bytes/dates/numbers
S->>S: render stats cards grid
deactivate S
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (7)
app/src/components/intelligence/MemoryGraphMap.tsx (4)
96-150: Force simulation runs synchronously and may cause UI jank.The O(n²) simulation with up to 100 nodes and 150 iterations (≈1.5M operations) runs in
useMemoon the main thread. While the caps keep it bounded, consider logging a warning or adding performance monitoring for debugging large graphs.As per coding guidelines, add a namespaced debug log for tracing performance in new flows:
const startTime = performance.now(); const simulated = runSimulation(rawNodes, rawEdges); console.debug('[MemoryGraphMap] simulation completed', { nodes: rawNodes.length, edges: rawEdges.length, durationMs: performance.now() - startTime, });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/components/intelligence/MemoryGraphMap.tsx` around lines 96 - 150, The force simulation runSimulation is expensive and runs synchronously (called from useMemo) which can cause UI jank; wrap the call to runSimulation in a performance measurement: record start = performance.now(), call runSimulation(rawNodes, rawEdges), then console.debug('[MemoryGraphMap] simulation completed', { nodes: rawNodes.length, edges: rawEdges.length, durationMs: performance.now() - start }); also emit a namespaced console.warn when rawNodes.length exceeds a threshold (e.g., 50) to aid debugging and monitoring; place these changes where useMemo invokes runSimulation inside MemoryGraphMap so the log context and counts are accurate.
55-67: Inconsistent namespace assignment for subjects vs objects.For subjects (line 60), the namespace is always overwritten with
r.namespace, while for objects (line 64), the existing namespace is preserved (existingObj?.namespace ?? r.namespace). This inconsistency means the subject's namespace depends on the last relation processed, which may not be intentional.♻️ Suggested consistency fix
for (const r of cappedRelations) { const subKey = r.subject.toLowerCase(); const objKey = r.object.toLowerCase(); const existing = entitySet.get(subKey); - entitySet.set(subKey, { namespace: r.namespace, count: (existing?.count ?? 0) + 1 }); + entitySet.set(subKey, { + namespace: existing?.namespace ?? r.namespace, + count: (existing?.count ?? 0) + 1, + }); const existingObj = entitySet.get(objKey); entitySet.set(objKey, { namespace: existingObj?.namespace ?? r.namespace, count: (existingObj?.count ?? 0) + 1, }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/components/intelligence/MemoryGraphMap.tsx` around lines 55 - 67, The subject namespace is being overwritten on every iteration while the object preserves an existing namespace; update the subject handling in the loop over cappedRelations so entitySet.set(subKey, ...) uses the same preservation logic as objects (use existing?.namespace ?? r.namespace) instead of always assigning r.namespace, ensuring both subKey and objKey namespace resolution is consistent; reference symbols: cappedRelations, r.namespace, subKey, objKey, entitySet, existing and existingObj.
192-206: Consider memoizingnodeMapandconnectedIdscomputations.
nodeMap(line 192) andconnectedIds(lines 199-205) are recomputed on every render, including when onlyhoveredEdgechanges. Since they depend onnodes/edges/selectedNode, consider wrapping them inuseMemofor better performance on interaction-heavy renders.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/components/intelligence/MemoryGraphMap.tsx` around lines 192 - 206, The nodeMap and connectedIds calculations are recomputed on every render; wrap them (and centerNodeId which also derives from nodes) in React's useMemo to avoid unnecessary work: compute nodeMap with useMemo(() => new Map(nodes.map(...)), [nodes]), compute centerNodeId with useMemo(() => nodes.find(...)?.id ?? (nodes.length ? nodes[0].id : null), [nodes]), and compute connectedIds with useMemo(() => selectedNode ? new Set(edges.filter(...).flatMap(...)) : null, [edges, selectedNode]); update references to nodeMap, centerNodeId, and connectedIds in MemoryGraphMap so the rest of the component uses the memoized values.
177-182: Consider using memo values directly instead of syncing to state.The
useEffectsynchronizes memo results to state, butnodes,edges, andnamespacePalettedon't change interactively—onlyselectedNodeandhoveredEdgedo. You could useinitialNodes/initialEdges/palettedirectly from the memo, avoiding the extra render cycle caused by the state sync.Based on learnings: "In React components, do not perform synchronous setState directly inside useEffect bodies."
♻️ Proposed simplification
- const [nodes, setNodes] = useState<GraphNode[]>([]); - const [edges, setEdges] = useState<GraphEdge[]>([]); const [hoveredEdge, setHoveredEdge] = useState<number | null>(null); const [selectedNode, setSelectedNode] = useState<string | null>(null); - const [namespacePalette, setNamespacePalette] = useState<Map<string, string>>(new Map()); // Build graph data from relations (synchronous, deterministic) const { initialNodes, initialEdges, palette } = useMemo(() => { // ... existing logic }, [relations]); - // Sync memo results into state (needed for interactive selection/hover) - useEffect(() => { - setNodes(initialNodes); - setEdges(initialEdges); - setNamespacePalette(palette); - }, [initialNodes, initialEdges, palette]); + + // Use memo results directly + const nodes = initialNodes; + const edges = initialEdges; + const namespacePalette = palette;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/components/intelligence/MemoryGraphMap.tsx` around lines 177 - 182, The useEffect is needlessly syncing memoized values into state (setNodes, setEdges, setNamespacePalette) causing an extra render; instead remove that effect and use the memo outputs initialNodes, initialEdges and palette directly where nodes, edges and namespacePalette are used (keeping state only for interactive values like selectedNode and hoveredEdge). Update references in the component to read from initialNodes/initialEdges/palette (or rename the memo outputs to nodes/edges/palette) and delete the useEffect and the corresponding state setters so only interactive state remains.app/src/components/intelligence/MemoryWorkspace.tsx (1)
155-198: Graph relations loaded sequentially instead of in parallel.The
memoryGraphQuery()call at lines 167-175 runs afterPromise.allcompletes. Consider including it in thePromise.allfor parallel loading to improve initial load performance:♻️ Proposed parallel loading
try { - const [documentsPayload, namespacesPayload, memoryDirFiles] = await Promise.all([ + const [documentsPayload, namespacesPayload, memoryDirFiles, relationsResult] = await Promise.all([ memoryListDocuments(), memoryListNamespaces(), aiListMemoryFiles('memory'), + memoryGraphQuery().catch(() => [] as GraphRelation[]), ]); - setGraphRelationsLoading(true); - try { - const relations = await memoryGraphQuery(); - setGraphRelations(relations); - } catch { - setGraphRelations([]); - } finally { - setGraphRelationsLoading(false); - } + setGraphRelations(relationsResult);Note: This removes the separate
graphRelationsLoadingstate. If you need distinct loading states, keep the current approach but move the loading state update beforePromise.all.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/components/intelligence/MemoryWorkspace.tsx` around lines 155 - 198, The graph query is being awaited after Promise.all, causing sequential loads; update loadWorkspace to include memoryGraphQuery in the initial Promise.all (alongside memoryListDocuments, memoryListNamespaces, aiListMemoryFiles) so documents, namespaces, files and relations load in parallel, then call setGraphRelations with the returned relations; remove or adapt the separate try/catch around memoryGraphQuery and ensure graphRelationsLoading is handled (either removed or set before Promise.all) and errors for the graph result are handled the same way as other payloads in the unified response.app/src/components/intelligence/MemoryHeatmap.tsx (1)
227-236: Tooltip may overflow viewport edges.The tooltip is positioned at
left: hoveredCell.xwithtranslateX(-50%), which could cause it to overflow the viewport when hovering cells near the left or right edges. Consider adding viewport boundary checks or using a tooltip library.app/src/components/intelligence/MemoryInsights.tsx (1)
77-87: Fuzzy matching logic may produce false positives.The bidirectional check
normalized.includes(key) || key.includes(normalized)can misclassify predicates. For example, a predicate like"dislike"would match the key"is"(since"dislike".includes("is")is true), incorrectly categorizing it asfactsinstead ofpreferences.Consider removing the
key.includes(normalized)part or using word-boundary matching:♻️ Proposed fix
function categorize(predicate: string): InsightCategory { const normalized = predicate.toLowerCase().replace(/[_-]/g, ' ').trim(); if (PREDICATE_CATEGORIES[normalized]) return PREDICATE_CATEGORIES[normalized]; // Fuzzy match: check if predicate contains known keywords for (const [key, category] of Object.entries(PREDICATE_CATEGORIES)) { - if (normalized.includes(key) || key.includes(normalized)) return category; + // Only match if the key appears as a complete word/phrase in the predicate + const keyPattern = new RegExp(`\\b${key}\\b`); + if (keyPattern.test(normalized)) return category; } return 'other'; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/components/intelligence/MemoryInsights.tsx` around lines 77 - 87, The fuzzy matching in categorize(predicate) is producing false positives because it checks both normalized.includes(key) and key.includes(normalized); remove the bidirectional check and implement a safer match: keep only the predicate-containing-key check but require whole-word matching (use a word-boundary match or escaped regex) against each key from PREDICATE_CATEGORIES, so "dislike" won't match "is"; update the loop in categorize and, if needed, add/use an escapeRegExp helper to safely build the word-boundary test for keys.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/src/components/intelligence/MemoryHeatmap.tsx`:
- Around line 51-58: The inline comment in MemoryHeatmap.tsx inside the useMemo
block incorrectly says "6 months ago" while the MONTHS constant equals 8; update
that comment to accurately describe the window (e.g., "8 months ago through
today" or "MONTHS months ago through today") so it matches the MONTHS constant
used when computing rangeStart in useMemo.
---
Nitpick comments:
In `@app/src/components/intelligence/MemoryGraphMap.tsx`:
- Around line 96-150: The force simulation runSimulation is expensive and runs
synchronously (called from useMemo) which can cause UI jank; wrap the call to
runSimulation in a performance measurement: record start = performance.now(),
call runSimulation(rawNodes, rawEdges), then console.debug('[MemoryGraphMap]
simulation completed', { nodes: rawNodes.length, edges: rawEdges.length,
durationMs: performance.now() - start }); also emit a namespaced console.warn
when rawNodes.length exceeds a threshold (e.g., 50) to aid debugging and
monitoring; place these changes where useMemo invokes runSimulation inside
MemoryGraphMap so the log context and counts are accurate.
- Around line 55-67: The subject namespace is being overwritten on every
iteration while the object preserves an existing namespace; update the subject
handling in the loop over cappedRelations so entitySet.set(subKey, ...) uses the
same preservation logic as objects (use existing?.namespace ?? r.namespace)
instead of always assigning r.namespace, ensuring both subKey and objKey
namespace resolution is consistent; reference symbols: cappedRelations,
r.namespace, subKey, objKey, entitySet, existing and existingObj.
- Around line 192-206: The nodeMap and connectedIds calculations are recomputed
on every render; wrap them (and centerNodeId which also derives from nodes) in
React's useMemo to avoid unnecessary work: compute nodeMap with useMemo(() =>
new Map(nodes.map(...)), [nodes]), compute centerNodeId with useMemo(() =>
nodes.find(...)?.id ?? (nodes.length ? nodes[0].id : null), [nodes]), and
compute connectedIds with useMemo(() => selectedNode ? new
Set(edges.filter(...).flatMap(...)) : null, [edges, selectedNode]); update
references to nodeMap, centerNodeId, and connectedIds in MemoryGraphMap so the
rest of the component uses the memoized values.
- Around line 177-182: The useEffect is needlessly syncing memoized values into
state (setNodes, setEdges, setNamespacePalette) causing an extra render; instead
remove that effect and use the memo outputs initialNodes, initialEdges and
palette directly where nodes, edges and namespacePalette are used (keeping state
only for interactive values like selectedNode and hoveredEdge). Update
references in the component to read from initialNodes/initialEdges/palette (or
rename the memo outputs to nodes/edges/palette) and delete the useEffect and the
corresponding state setters so only interactive state remains.
In `@app/src/components/intelligence/MemoryInsights.tsx`:
- Around line 77-87: The fuzzy matching in categorize(predicate) is producing
false positives because it checks both normalized.includes(key) and
key.includes(normalized); remove the bidirectional check and implement a safer
match: keep only the predicate-containing-key check but require whole-word
matching (use a word-boundary match or escaped regex) against each key from
PREDICATE_CATEGORIES, so "dislike" won't match "is"; update the loop in
categorize and, if needed, add/use an escapeRegExp helper to safely build the
word-boundary test for keys.
In `@app/src/components/intelligence/MemoryWorkspace.tsx`:
- Around line 155-198: The graph query is being awaited after Promise.all,
causing sequential loads; update loadWorkspace to include memoryGraphQuery in
the initial Promise.all (alongside memoryListDocuments, memoryListNamespaces,
aiListMemoryFiles) so documents, namespaces, files and relations load in
parallel, then call setGraphRelations with the returned relations; remove or
adapt the separate try/catch around memoryGraphQuery and ensure
graphRelationsLoading is handled (either removed or set before Promise.all) and
errors for the graph result are handled the same way as other payloads in the
unified response.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: b32b2415-7665-4fa3-9033-59088430f198
📒 Files selected for processing (5)
app/src/components/intelligence/MemoryGraphMap.tsxapp/src/components/intelligence/MemoryHeatmap.tsxapp/src/components/intelligence/MemoryInsights.tsxapp/src/components/intelligence/MemoryStatsBar.tsxapp/src/components/intelligence/MemoryWorkspace.tsx
| const { grid, monthLabels, totalEvents, maxDailyCount, totalWeeks } = useMemo(() => { | ||
| // The window: 6 months ago through today | ||
| const today = new Date(); | ||
| today.setHours(0, 0, 0, 0); | ||
|
|
||
| const rangeStart = new Date(today); | ||
| rangeStart.setMonth(rangeStart.getMonth() - MONTHS); | ||
| rangeStart.setDate(1); // start of that month |
There was a problem hiding this comment.
Stale comment: references "6 months" but MONTHS constant is 8.
The comment at line 52 says "6 months ago" but the MONTHS constant at line 9 is set to 8. Update the comment to match the actual behavior.
📝 Fix comment
- // The window: 6 months ago through today
+ // The window: 8 months ago through today📝 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.
| const { grid, monthLabels, totalEvents, maxDailyCount, totalWeeks } = useMemo(() => { | |
| // The window: 6 months ago through today | |
| const today = new Date(); | |
| today.setHours(0, 0, 0, 0); | |
| const rangeStart = new Date(today); | |
| rangeStart.setMonth(rangeStart.getMonth() - MONTHS); | |
| rangeStart.setDate(1); // start of that month | |
| const { grid, monthLabels, totalEvents, maxDailyCount, totalWeeks } = useMemo(() => { | |
| // The window: 8 months ago through today | |
| const today = new Date(); | |
| today.setHours(0, 0, 0, 0); | |
| const rangeStart = new Date(today); | |
| rangeStart.setMonth(rangeStart.getMonth() - MONTHS); | |
| rangeStart.setDate(1); // start of that month |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/src/components/intelligence/MemoryHeatmap.tsx` around lines 51 - 58, The
inline comment in MemoryHeatmap.tsx inside the useMemo block incorrectly says "6
months ago" while the MONTHS constant equals 8; update that comment to
accurately describe the window (e.g., "8 months ago through today" or "MONTHS
months ago through today") so it matches the MONTHS constant used when computing
rangeStart in useMemo.
Align test assertions with the new sub-component structure (MemoryStatsBar, MemoryGraphMap, MemoryInsights, MemoryHeatmap). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
app/src/components/intelligence/__tests__/MemoryWorkspace.test.tsx (1)
96-101: Strengthen the Relations stat test to validate the rendered value, not just the label.Label-only assertion can pass even when the actual relations count regresses. Consider asserting the displayed count derived from mocked graph data as part of this scenario.
As per coding guidelines "Prefer testing behavior over implementation details in Vitest unit tests."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/components/intelligence/__tests__/MemoryWorkspace.test.tsx` around lines 96 - 101, The test 'shows Relations stat in the stats bar' currently only asserts the "Relations" label; update it to also assert the rendered relations count derived from the mocked graph data so regressions in the displayed value are caught. In the MemoryWorkspace test, after renderWithProviders(<MemoryWorkspace onToast={onToast} />) and waiting for the label, query for the element that shows the numeric value (e.g., via screen.getByText or a role/aria label used by the stats UI) and assert it equals the expected count from your mock graph fixture; reference the MemoryWorkspace component and the existing onToast/renderWithProviders helpers to locate the test and the mock data source. Ensure the assertion waits (e.g., inside waitFor) if the count is rendered asynchronously.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/src/components/intelligence/__tests__/MemoryWorkspace.test.tsx`:
- Around line 65-67: Tests in MemoryWorkspace.test.tsx assert text values that
no longer match the current output of the MemoryWorkspace component; update the
assertions to match the component's actual rendered strings. Open the
MemoryWorkspace.test.tsx file and revise the getByText/getByRole expectations
used in the tests referencing MemoryWorkspace (e.g., the 'renders the Memory
heading' test and the other failing blocks) so they match the exact text
produced by MemoryWorkspace (use the component's actual heading and button/label
strings from MemoryWorkspace.tsx), or switch to text-insensitive queries (e.g.,
getByRole with accessible name) if the text can change. Ensure you update all
occurrences indicated by the reviewer (the tests around the heading and the
other failing assertions) so they reflect the component's current render output.
---
Nitpick comments:
In `@app/src/components/intelligence/__tests__/MemoryWorkspace.test.tsx`:
- Around line 96-101: The test 'shows Relations stat in the stats bar' currently
only asserts the "Relations" label; update it to also assert the rendered
relations count derived from the mocked graph data so regressions in the
displayed value are caught. In the MemoryWorkspace test, after
renderWithProviders(<MemoryWorkspace onToast={onToast} />) and waiting for the
label, query for the element that shows the numeric value (e.g., via
screen.getByText or a role/aria label used by the stats UI) and assert it equals
the expected count from your mock graph fixture; reference the MemoryWorkspace
component and the existing onToast/renderWithProviders helpers to locate the
test and the mock data source. Ensure the assertion waits (e.g., inside waitFor)
if the count is rendered asynchronously.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 423859f5-c488-4fa2-8e8c-a281682bcea3
📒 Files selected for processing (1)
app/src/components/intelligence/__tests__/MemoryWorkspace.test.tsx
| it('renders the Memory heading', async () => { | ||
| renderWithProviders(<MemoryWorkspace onToast={onToast} />); | ||
| expect(screen.getByText('Memory Workspace')).toBeInTheDocument(); | ||
| expect(screen.getByText('Memory')).toBeInTheDocument(); |
There was a problem hiding this comment.
Align updated text assertions with the current MemoryWorkspace render strings.
These expectations no longer match the component output in app/src/components/intelligence/MemoryWorkspace.tsx, so this suite will fail despite valid behavior.
Suggested fix
- it('renders the Memory heading', async () => {
+ it('renders the Memory Workspace heading', async () => {
renderWithProviders(<MemoryWorkspace onToast={onToast} />);
- expect(screen.getByText('Memory')).toBeInTheDocument();
+ expect(screen.getByText('Memory Workspace')).toBeInTheDocument();
});
- it('renders the Memory Graph section', async () => {
+ it('renders the Sample Memory Graph section', async () => {
renderWithProviders(<MemoryWorkspace onToast={onToast} />);
await waitFor(() => {
- expect(screen.getByText('Memory Graph')).toBeInTheDocument();
+ expect(screen.getByText('Sample Memory Graph')).toBeInTheDocument();
});
});
it('shows empty-state message when no relations exist', async () => {
@@
renderWithProviders(<MemoryWorkspace onToast={onToast} />);
await waitFor(() => {
- expect(screen.getByText('No memory graph data yet')).toBeInTheDocument();
+ expect(
+ screen.getByText('No relations yet. Ingest documents to populate the graph.')
+ ).toBeInTheDocument();
});
});Also applies to: 105-110, 125-126
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/src/components/intelligence/__tests__/MemoryWorkspace.test.tsx` around
lines 65 - 67, Tests in MemoryWorkspace.test.tsx assert text values that no
longer match the current output of the MemoryWorkspace component; update the
assertions to match the component's actual rendered strings. Open the
MemoryWorkspace.test.tsx file and revise the getByText/getByRole expectations
used in the tests referencing MemoryWorkspace (e.g., the 'renders the Memory
heading' test and the other failing blocks) so they match the exact text
produced by MemoryWorkspace (use the component's actual heading and button/label
strings from MemoryWorkspace.tsx), or switch to text-insensitive queries (e.g.,
getByRole with accessible name) if the text can change. Ensure you update all
occurrences indicated by the reviewer (the tests around the heading and the
other failing assertions) so they reflect the component's current render output.
Summary
MemoryWorkspaceinto focused sub-components:MemoryStatsBar,MemoryGraphMap,MemoryInsights,MemoryHeatmapMemoryGraphMap(requestAnimationFrame→window.requestAnimationFrame, remove unuseduseRef)Test plan
yarn typecheck— passes cleanyarn lint— no new errors (1 pre-existing warning in MemoryGraphMap)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Changes