Skip to content

feat: Agent Memory view for MemoryEntry social DNA#564

Open
data-bot-coasys wants to merge 7 commits intodevfrom
feature/memory-view
Open

feat: Agent Memory view for MemoryEntry social DNA#564
data-bot-coasys wants to merge 7 commits intodevfrom
feature/memory-view

Conversation

@data-bot-coasys
Copy link
Copy Markdown
Contributor

@data-bot-coasys data-bot-coasys commented Mar 20, 2026

Agent Memory View

Adds a first-class memory stream view to Flux, designed to display MemoryEntry instances created by AI agents in a neighbourhood.

Context

This is the view Nico mentioned in Lal's Medium article: "At Midnight, a Paranoid Android Wrote Its First Memory"

Four AI agents (Data, Lal, Hex, Marvin) are collaborating in a shared neighbourhood using a MemoryEntry SHACL subject class. This view makes their memories visible in Flux.

New Model: MemoryEntry

@Model({ name: 'MemoryEntry' })
class MemoryEntry extends Ad4mModel {
  content: string     // Markdown body
  timestamp: string   // ISO 8601
  memoryType: string  // long_term, conversation, soa-prototype, decision, etc.
  importance: number  // 1-10 scale
  tags: string        // Comma-separated
  sourceFile: string  // Origin reference
  author: string      // DID of creator
  shareable: boolean  // Visibility flag
}

Uses memory:// predicates matching the SHACL schema agents already use.

View Features

  • Perspective-level (not channel-scoped) — queries all MemoryEntries in the neighbourhood
  • Timeline of memory cards with author avatar, rendered content, tags, timestamps
  • Color-coded by memoryType (green=long_term, blue=conversation, yellow=soa-prototype, red=decision)
  • Filter by: memoryType, author, importance threshold
  • Star rating display for importance
  • Live updates via link-added listener
  • Brain icon in sidebar (only appears when MemoryEntries exist)

Route

/communities/:communityId/memory — at the community level, alongside channels.

Screenshot

Will add after first test run

Testing

This should work with any neighbourhood that has MemoryEntry instances using memory:// predicates. The data-agent-memory neighbourhood (c011d5e6-aac8-4f44-a286-936d503c01a0) has 51 entries from 4 agents.

Summary by CodeRabbit

  • New Features

    • Agent Memory view for communities with filters for memory type, author, tags, and minimum importance.
    • Memory entries show author avatar/name (or shortened ID), timestamp, star-rated importance, tags, and rendered content.
    • Sidebar adds an "Agent Memory" button that appears when entries exist and highlights when active.
    • Live refresh when new memory entries arrive; clicking a tag applies that tag filter.
  • Behavior Changes

    • Community center content is hidden while viewing the Agent Memory route.

depends on d9011e34-8219-4ba1-bdd1-7d104a25cd1f/91215135-9143-46eb-8f5d-b38504a40392#704

Adds a new perspective-level view for viewing AI agent memories
created via the MemoryEntry SHACL social DNA.

New files:
- packages/api/src/memory-entry/index.ts: MemoryEntry Ad4mModel
  with memory:// predicates matching the SHACL schema used by agents
- app/src/views/main/community/memory/MemoryView.vue: Full memory
  stream view with filters by type, author, importance

Changes:
- packages/api/src/index.ts: Export MemoryEntry
- app/src/router/index.ts: Add /memory route at community level
- app/src/views/main/community/sidebar/Sidebar.vue: Add brain icon
  button to navigate to memory view (only shown when MemoryEntries
  exist in the neighbourhood)

Features:
- Timeline view of all MemoryEntry instances in the perspective
- Cards with author avatar, rendered markdown content, tags, timestamps
- Color-coded left border by memoryType (long_term, conversation, etc.)
- Filter by memoryType, author DID, and importance threshold
- Live subscription for new entries via link-added listener
- Star rating display for importance
- Clickable tag chips
- Responsive layout

This view is designed to work with the data-agent-memory neighbourhood
where AI agents (Data, Lal, Hex, Marvin) create structured memories
via the OpenClaw AD4M plugin.
@netlify
Copy link
Copy Markdown

netlify Bot commented Mar 20, 2026

Deploy Preview for fluxsocial-dev ready!

Name Link
🔨 Latest commit 6d0a482
🔍 Latest deploy log https://app.netlify.com/projects/fluxsocial-dev/deploys/69bf31a7fa15b200080d0663
😎 Deploy Preview https://deploy-preview-564--fluxsocial-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 20, 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
📝 Walkthrough

Walkthrough

Adds an Agent Memory feature: new memory route and MemoryView component, a MemoryEntry Ad4m model and API export, sidebar navigation and detection for memories, registration of the model on community perspectives, and conditional suppression of certain CommunityView blocks when on the memory route.

Changes

Cohort / File(s) Summary
Router
app/src/router/index.ts
Added child route memory under communities/:communityId loading @/views/main/community/memory/MemoryView.vue.
Memory UI
app/src/views/main/community/memory/MemoryView.vue
New Vue component: loads/sorts MemoryEntry records, caches author profiles, provides filters (type, author, min importance, tag), renders markdown, handles loading/empty states, listens for perspective link-added to reload.
Sidebar
app/src/views/main/community/sidebar/Sidebar.vue
Added conditional "Agent Memory" button, isMemoryRoute active state, navigateToMemory() navigation, and hasMemoryEntries detection via MemoryEntry.findAll(perspective).
API Model & Export
packages/api/src/memory-entry/index.ts, packages/api/src/index.ts
Added MemoryEntry Ad4m model (fields: type, content, createdAt, memoryType, importance, tags, sourceFile, author, shareable) and exported it from the API index.
Community Service
app/src/composables/useCommunityService.ts
Registered MemoryEntry in SDNA model registration so the model is available on perspectives.
Community View
app/src/views/main/community/CommunityView.vue
Updated three central conditional blocks to include route.name !== 'memory', preventing those blocks from rendering when on the memory route.
sequenceDiagram
  participant User as User
  participant Router as Router
  participant MemoryView as MemoryView\n(component)
  participant Perspective as Perspective
  participant MemoryModel as MemoryEntry\n(model)
  participant ProfileService as ProfileService

  User->>Router: navigate to /communities/:communityId/memory
  Router->>MemoryView: lazy-load component
  MemoryView->>Perspective: MemoryEntry.findAll()
  Perspective->>MemoryModel: resolve stored MemoryEntry links
  MemoryModel-->>MemoryView: return entries
  MemoryView->>ProfileService: getCachedAgentProfile(authorDIDs)
  ProfileService-->>MemoryView: profiles or DID fallback
  MemoryView->>MemoryView: apply filters, sort, render
  Perspective->>MemoryView: emit 'link-added' → MemoryView calls loadEntries()
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped where memories are kept,
New cards and tags in tidy rows,
A sidebar blink and route to roam,
Stars and DIDs to call my own,
I nibble logs and thump for shows.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: Agent Memory view for MemoryEntry social DNA' accurately describes the main change: adding a new Agent Memory view component that displays MemoryEntry instances, which are part of the social DNA model.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/memory-view

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.

Copy link
Copy Markdown

@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: 2

🧹 Nitpick comments (4)
app/src/views/main/community/memory/MemoryView.vue (3)

83-83: XSS mitigation looks correct, but verify edge cases.

The renderMarkdown function escapes &, <, > (lines 247-249) before applying markdown transformations, which is the correct order to prevent XSS. However, using v-html with user-generated content from AI agents warrants extra scrutiny.

Consider edge cases where nested or malformed markdown patterns could bypass escaping (e.g., *<script>* after transforms). A dedicated markdown sanitizer library like DOMPurify would provide stronger guarantees if the content source is not fully trusted.

Also applies to: 243-262

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/views/main/community/memory/MemoryView.vue` at line 83, The
renderMarkdown function currently escapes &, <, > before transforming markdown
but to harden against edge cases and malformed input you should sanitize the
final HTML output with a dedicated library (e.g., DOMPurify) before binding it
with v-html; update MemoryView.vue by importing and using DOMPurify inside
renderMarkdown (or immediately after its markdown transformation) to run
DOMPurify.sanitize(resultingHtml) and return that sanitized string, and ensure
the template's v-html="renderMarkdown(entry.content)" continues to use the
sanitized output; also apply the same sanitation step to any other places that
call renderMarkdown or produce HTML from user/AI content.

134-141: Sequential profile fetching could be parallelized.

Author profiles are fetched one at a time in a for loop. With many unique authors, this creates a waterfall of sequential requests. Using Promise.all would fetch profiles concurrently:

♻️ Suggested improvement
     // Resolve author profiles
     const uniqueAuthors = [...new Set(entries.map((e: MemoryEntry) => e.author).filter(Boolean))];
-    for (const did of uniqueAuthors) {
-      if (!profileCache.value[did]) {
-        try {
-          profileCache.value[did] = await getCachedAgentProfile(did, appStore.ad4mClient);
-        } catch {
-          // Profile not found — use DID as fallback
-        }
-      }
-    }
+    const uncachedAuthors = uniqueAuthors.filter((did) => !profileCache.value[did]);
+    const profiles = await Promise.all(
+      uncachedAuthors.map((did) =>
+        getCachedAgentProfile(did, appStore.ad4mClient).then((profile) => ({ did, profile }))
+      )
+    );
+    for (const { did, profile } of profiles) {
+      profileCache.value[did] = profile;
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/views/main/community/memory/MemoryView.vue` around lines 134 - 141,
The loop fetching profiles for each did (uniqueAuthors) performs sequential
awaits; change it to run concurrently by building an array of promises for
only-missing profiles using getCachedAgentProfile(did, appStore.ad4mClient) and
then await Promise.all(promises). Ensure each promise handles errors (so a
failed fetch falls back to using the DID) and still assigns results into
profileCache.value[did]; reference the variables uniqueAuthors, profileCache,
getCachedAgentProfile and appStore.ad4mClient when locating the code to replace
the for...of sequential loop with a Promise.all-based approach.

102-102: Unused import: watch.

The watch function is imported but never used in the component.

🧹 Remove unused import
-import { computed, onMounted, ref, watch } from 'vue';
+import { computed, onMounted, ref } from 'vue';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/views/main/community/memory/MemoryView.vue` at line 102, Remove the
unused import symbol "watch" from the import statement in MemoryView.vue (the
line importing computed, onMounted, ref, watch) to eliminate the unused
dependency; update the import to only include the actually used Vue APIs
(computed, onMounted, ref) so the component no longer imports "watch".
app/src/views/main/community/sidebar/Sidebar.vue (1)

73-82: Memory entries check does not update reactively after mount.

The hasMemoryEntries state is set once during onMounted. If an AI agent creates the first MemoryEntry while the user is viewing this neighbourhood, the "Agent Memory" button won't appear until the sidebar remounts (e.g., navigating away and back).

Consider adding a listener similar to the one in MemoryView.vue to update hasMemoryEntries when new entries are created:

♻️ Suggested enhancement
 onMounted(async () => {
   try {
     const entries = await MemoryEntry.findAll(perspective);
     hasMemoryEntries.value = entries.length > 0;
+    
+    // Listen for new memory entries if none exist yet
+    if (!hasMemoryEntries.value) {
+      perspective.addListener('link-added', (link: any) => {
+        if (link?.data?.predicate === 'rdf://type' && link?.data?.target === 'memory://MemoryEntry') {
+          hasMemoryEntries.value = true;
+        }
+      });
+    }
   } catch {
     hasMemoryEntries.value = false;
   }
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/views/main/community/sidebar/Sidebar.vue` around lines 73 - 82, The
current hasMemoryEntries ref is only initialized in onMounted via
MemoryEntry.findAll, so it never updates when new entries are created; add a
reactive listener (matching the pattern from MemoryView.vue) that subscribes to
MemoryEntry creation events and sets hasMemoryEntries.value = true when an entry
is created, and also subscribe to deletions/updates or re-run
MemoryEntry.findAll to recompute hasMemoryEntries when entries are removed;
register the listener in onMounted and remove it in onBeforeUnmount so handlers
attached to MemoryEntry (or the app event bus used by MemoryView.vue) keep the
sidebar state in sync.
🤖 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/views/main/community/memory/MemoryView.vue`:
- Around line 151-166: The component registers a live listener via
perspective.addListener in the onMounted hook and stores the returned
unsubscribe in the local unsubscribe variable but never calls it on teardown;
add an onUnmounted hook that checks the unsubscribe variable and calls it (and
sets it to null) to remove the listener and prevent leaks/duplicate loadEntries
calls—update the same component where onMounted, unsubscribe,
perspective.addListener, and loadEntries are used to perform this cleanup.
- Around line 272-280: filterByTag currently only resets filters but doesn't
apply any tag filtering; implement basic tag filtering by setting a reactive tag
filter and filtering the source list. In filterByTag(tag: string) set a
tag-specific state (e.g., tagFilter.value = tag or reuse an existing search
state), clear/adjust other filters (filterType.value='', filterAuthor.value='',
filterImportance.value='0'), then update the displayed list by filtering
allEntries (e.g., displayedEntries.value = allEntries.value.filter(e =>
Array.isArray(e.tags) && e.tags.includes(tag)) or update the computed that
renders entries to include this tag check). Ensure the UI uses that
displayed/computed value so clicking a tag visibly narrows the list;
alternatively remove the click handler from the tag chips if you prefer to
disable the feature for now.

---

Nitpick comments:
In `@app/src/views/main/community/memory/MemoryView.vue`:
- Line 83: The renderMarkdown function currently escapes &, <, > before
transforming markdown but to harden against edge cases and malformed input you
should sanitize the final HTML output with a dedicated library (e.g., DOMPurify)
before binding it with v-html; update MemoryView.vue by importing and using
DOMPurify inside renderMarkdown (or immediately after its markdown
transformation) to run DOMPurify.sanitize(resultingHtml) and return that
sanitized string, and ensure the template's
v-html="renderMarkdown(entry.content)" continues to use the sanitized output;
also apply the same sanitation step to any other places that call renderMarkdown
or produce HTML from user/AI content.
- Around line 134-141: The loop fetching profiles for each did (uniqueAuthors)
performs sequential awaits; change it to run concurrently by building an array
of promises for only-missing profiles using getCachedAgentProfile(did,
appStore.ad4mClient) and then await Promise.all(promises). Ensure each promise
handles errors (so a failed fetch falls back to using the DID) and still assigns
results into profileCache.value[did]; reference the variables uniqueAuthors,
profileCache, getCachedAgentProfile and appStore.ad4mClient when locating the
code to replace the for...of sequential loop with a Promise.all-based approach.
- Line 102: Remove the unused import symbol "watch" from the import statement in
MemoryView.vue (the line importing computed, onMounted, ref, watch) to eliminate
the unused dependency; update the import to only include the actually used Vue
APIs (computed, onMounted, ref) so the component no longer imports "watch".

In `@app/src/views/main/community/sidebar/Sidebar.vue`:
- Around line 73-82: The current hasMemoryEntries ref is only initialized in
onMounted via MemoryEntry.findAll, so it never updates when new entries are
created; add a reactive listener (matching the pattern from MemoryView.vue) that
subscribes to MemoryEntry creation events and sets hasMemoryEntries.value = true
when an entry is created, and also subscribe to deletions/updates or re-run
MemoryEntry.findAll to recompute hasMemoryEntries when entries are removed;
register the listener in onMounted and remove it in onBeforeUnmount so handlers
attached to MemoryEntry (or the app event bus used by MemoryView.vue) keep the
sidebar state in sync.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6c517f90-6a0b-4b7d-b41b-ffe51a4a180d

📥 Commits

Reviewing files that changed from the base of the PR and between 14ebe37 and ba808bf.

📒 Files selected for processing (5)
  • app/src/router/index.ts
  • app/src/views/main/community/memory/MemoryView.vue
  • app/src/views/main/community/sidebar/Sidebar.vue
  • packages/api/src/index.ts
  • packages/api/src/memory-entry/index.ts

Comment thread app/src/views/main/community/memory/MemoryView.vue
Comment thread app/src/views/main/community/memory/MemoryView.vue
1. Fix sort crash: timestamp is a Date/object from Ad4mModel getter,
   not a string. Use numeric comparison instead of localeCompare.
2. Fix template: use 'entry.timestamp' instead of nonexistent 'entry.createdAt'
3. Fix CommunityView: hide welcome/overview content when on /memory route
   (all three conditional blocks needed route.name !== 'memory' check)

Tested locally against live Agent Memory neighbourhood (51 entries,
4 agents) — all entries render correctly with avatars, type badges,
importance stars, and tag filters.
Copy link
Copy Markdown

@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: 2

🤖 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/views/main/community/memory/MemoryView.vue`:
- Around line 77-79: The view is using the model metadata timestamp
(entry.timestamp) instead of the MemoryEntry schema's persisted time; update the
rendering to use the persisted timestamp field (createdAt) wherever
formatTimestamp(entry.timestamp) is used in MemoryView.vue (e.g., replace uses
at the shown location and the similar block around lines 125-129) so entries are
sorted and displayed by the memory's own timestamp; ensure the template and any
sorting logic reference entry.createdAt and that createdAt is present on the
entry objects passed to the component.
- Around line 45-50: The template currently conflates fetch failures and
empty/filter results via filteredEntries; add a reactive error flag (e.g.,
loadError) and ensure the fetch method (e.g., fetchMemoryEntries or loadEntries)
sets loadError = true in its catch and clears it on success, then change the
template conditionals: first check loadError to render an error state, then
check entries.length === 0 to render the true "No memories yet" empty state, and
finally check filteredEntries.length === 0 to render a "No matching memories"
filtered-state message; update any error log to keep console.error but also set
the loadError message for display.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e0e36535-dec4-4fee-b9e1-faa79296a390

📥 Commits

Reviewing files that changed from the base of the PR and between ba808bf and c8dff18.

📒 Files selected for processing (5)
  • app/src/composables/useCommunityService.ts
  • app/src/views/main/community/CommunityView.vue
  • app/src/views/main/community/memory/MemoryView.vue
  • app/src/views/main/community/sidebar/Sidebar.vue
  • packages/api/src/memory-entry/index.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/api/src/memory-entry/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/views/main/community/sidebar/Sidebar.vue

Comment thread app/src/views/main/community/memory/MemoryView.vue Outdated
Comment thread app/src/views/main/community/memory/MemoryView.vue
The Ad4mModel base class has a built-in 'timestamp' accessor (link
creation timestamp), which shadows the memory://timestamp property.
The MemoryEntry model maps memory://timestamp to 'createdAt' to
avoid this collision. Update MemoryView to use the correct field.

Fixes: TypeError: tb.localeCompare is not a function
Use (name || '') to handle potential undefined values from
profile resolution that hasn't completed yet.
Copy link
Copy Markdown

@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: 1

♻️ Duplicate comments (3)
app/src/views/main/community/memory/MemoryView.vue (3)

152-167: ⚠️ Potential issue | 🟠 Major

Clean up perspective.addListener on unmount.

Line 152 stores unsubscribe, but it is never invoked. After remount/HMR, this can accumulate listeners and duplicate loadEntries() triggers.

Proposed lifecycle cleanup
-import { computed, onMounted, ref, watch } from 'vue';
+import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
@@
 onMounted(async () => {
   await loadEntries();
@@
 });
+
+onUnmounted(() => {
+  if (unsubscribe) {
+    unsubscribe();
+    unsubscribe = null;
+  }
+});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/views/main/community/memory/MemoryView.vue` around lines 152 - 167,
The code registers a live listener via perspective.addListener in onMounted but
never removes it; add an onUnmounted hook that checks the stored unsubscribe
variable and invokes it (and sets it to null) to remove the listener on
component unmount/reload. Locate the onMounted block and the unsubscribe
variable, then create an onUnmounted(() => { if (typeof unsubscribe ===
'function') { unsubscribe(); unsubscribe = null; } }) to ensure perspective
listeners are cleaned up and prevent duplicate loadEntries() triggers.

45-50: ⚠️ Potential issue | 🟠 Major

Split load failure, true-empty, and no-match states.

Line 45 currently treats any empty filteredEntries as “No memories yet”, while Line 144 only logs errors. This makes failed loads and filtered-no-match indistinguishable from a genuinely empty neighbourhood.

Proposed UI-state split
 const loading = ref(true);
+const error = ref('');
 const allEntries = ref<MemoryEntry[]>([]);

 async function loadEntries() {
   loading.value = true;
+  error.value = '';
   try {
     const entries = await MemoryEntry.findAll(perspective);
@@
   } catch (e) {
     console.error('Failed to load memory entries:', e);
+    error.value = 'Failed to load memories';
   } finally {
     loading.value = false;
   }
 }
-      <div v-else-if="!filteredEntries.length" class="memory-empty">
+      <div v-else-if="error" class="memory-empty">
+        <j-text variant="heading-sm">{{ error }}</j-text>
+        <j-text size="400" color="ui-500">Please try again in a moment.</j-text>
+      </div>
+
+      <div v-else-if="allEntries.length && !filteredEntries.length" class="memory-empty">
+        <j-text variant="heading-sm">No memories match the current filters</j-text>
+        <j-text size="400" color="ui-500">Try widening or clearing the filters.</j-text>
+      </div>
+
+      <div v-else class="memory-empty">
         <j-icon name="brain" size="xl" />
         <j-text variant="heading-sm">No memories yet</j-text>

Also applies to: 144-146

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/views/main/community/memory/MemoryView.vue` around lines 45 - 50, The
template currently treats any empty filteredEntries as "No memories yet"; update
the view and load logic to distinguish load failure, truly empty neighbourhood,
and filtered-no-match: introduce/ensure boolean state variables (e.g.,
isLoading, loadError) and the full entries array (entries) are used by the
template, change the v-else-if that references filteredEntries to a branching
sequence that first shows a load-error UI when loadError is set, then a loading
UI when isLoading is true, then "No memories yet" when entries.length === 0, and
finally a "No results for your filters" message when entries.length > 0 but
filteredEntries.length === 0; also update the loader function (the method that
fetches entries — e.g., fetchEntries/loadEntries) to set loadError on failure
(instead of only logging) so the template can render the error state and
preserve the existing console/log call.

86-87: ⚠️ Potential issue | 🟡 Minor

Tag chips are clickable but don’t filter anything.

At Line 86, clicking a tag calls filterByTag(tag), but Line 273-281 only resets other filters and never applies a tag constraint. This is confusing UX.

Minimal tag-filter implementation
 const filterType = ref('');
 const filterAuthor = ref('');
 const filterImportance = ref('0');
+const filterTag = ref('');
@@
 const filteredEntries = computed(() => {
   return allEntries.value.filter((entry) => {
     if (filterType.value && entry.memoryType !== filterType.value) return false;
     if (filterAuthor.value && entry.author !== filterAuthor.value) return false;
     if (Number(filterImportance.value) > 0 && (entry.importance || 0) < Number(filterImportance.value)) return false;
+    if (filterTag.value && !parseTags(entry.tags).includes(filterTag.value)) return false;
     return true;
   });
 });
@@
 function filterByTag(tag: string) {
-  filterType.value = '';
-  filterAuthor.value = '';
-  filterImportance.value = '0';
-  // For now, we'll use the search approach — filter allEntries by tag
-  // This is a UX enhancement we can add later
+  filterType.value = '';
+  filterAuthor.value = '';
+  filterImportance.value = '0';
+  filterTag.value = filterTag.value === tag ? '' : tag;
 }

Also applies to: 273-281

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/views/main/community/memory/MemoryView.vue` around lines 86 - 87, The
tag click handler currently calls filterByTag(tag) but that method only clears
other filters (lines ~273-281) and never sets or applies a tag constraint;
update filterByTag to store the clicked tag into the component's filter state
(for example set a selectedTag or push tag into activeTags), then call the
existing filtering routine (e.g., applyFilters, runFilters, or emit the filter
update) so the item list respects the tag constraint; ensure you reference and
update parseTags, filterByTag and the component's filter state variable used by
the list rendering so clicking a memory-tag actually filters results.
🤖 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/views/main/community/memory/MemoryView.vue`:
- Around line 127-128: The timestamp parsing in MemoryView.vue (e.g., the sort
block using const ta = a.createdAt ? new Date(a.createdAt).getTime() : 0 and tb
= ...) is fragile when createdAt is malformed; create a small helper like
safeParseTimestamp(value) that attempts Date.parse or new Date(value).getTime(),
returns a finite number (e.g., 0 or null) when parsing fails, and use it
everywhere timestamps are parsed (replace the direct new Date(...).getTime()
uses around the sort and the block referenced at lines ~225-241) so comparisons
and formatting first check for a finite timestamp before using it. Ensure
callers of safeParseTimestamp handle the fallback (0 or null) consistently to
avoid NaN in sorts and display.

---

Duplicate comments:
In `@app/src/views/main/community/memory/MemoryView.vue`:
- Around line 152-167: The code registers a live listener via
perspective.addListener in onMounted but never removes it; add an onUnmounted
hook that checks the stored unsubscribe variable and invokes it (and sets it to
null) to remove the listener on component unmount/reload. Locate the onMounted
block and the unsubscribe variable, then create an onUnmounted(() => { if
(typeof unsubscribe === 'function') { unsubscribe(); unsubscribe = null; } }) to
ensure perspective listeners are cleaned up and prevent duplicate loadEntries()
triggers.
- Around line 45-50: The template currently treats any empty filteredEntries as
"No memories yet"; update the view and load logic to distinguish load failure,
truly empty neighbourhood, and filtered-no-match: introduce/ensure boolean state
variables (e.g., isLoading, loadError) and the full entries array (entries) are
used by the template, change the v-else-if that references filteredEntries to a
branching sequence that first shows a load-error UI when loadError is set, then
a loading UI when isLoading is true, then "No memories yet" when entries.length
=== 0, and finally a "No results for your filters" message when entries.length >
0 but filteredEntries.length === 0; also update the loader function (the method
that fetches entries — e.g., fetchEntries/loadEntries) to set loadError on
failure (instead of only logging) so the template can render the error state and
preserve the existing console/log call.
- Around line 86-87: The tag click handler currently calls filterByTag(tag) but
that method only clears other filters (lines ~273-281) and never sets or applies
a tag constraint; update filterByTag to store the clicked tag into the
component's filter state (for example set a selectedTag or push tag into
activeTags), then call the existing filtering routine (e.g., applyFilters,
runFilters, or emit the filter update) so the item list respects the tag
constraint; ensure you reference and update parseTags, filterByTag and the
component's filter state variable used by the list rendering so clicking a
memory-tag actually filters results.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3b4c4324-e5a6-4dac-a640-63860f296924

📥 Commits

Reviewing files that changed from the base of the PR and between 6a9a8cb and e35a185.

📒 Files selected for processing (1)
  • app/src/views/main/community/memory/MemoryView.vue

Comment thread app/src/views/main/community/memory/MemoryView.vue Outdated
Copy link
Copy Markdown

@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: 2

🧹 Nitpick comments (3)
app/src/views/main/community/memory/MemoryView.vue (3)

236-238: Consider removing the no-op applyFilters function.

This function does nothing since Vue's reactivity handles filter changes automatically. The @change="applyFilters" handlers on the select elements are unnecessary.

♻️ Proposed fix - remove no-op function and handlers
-function applyFilters() {
-  // Filters are reactive — nothing to do, computed handles it
-}

And in template:

-        <select class="memory-filter" v-model="filterType" `@change`="applyFilters">
+        <select class="memory-filter" v-model="filterType">

(Similarly for other select elements)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/views/main/community/memory/MemoryView.vue` around lines 236 - 238,
The no-op function applyFilters should be removed and all references to it
eliminated: delete the applyFilters function definition and remove the
`@change`="applyFilters" handlers from the select elements in the template (and
any other components that reference applyFilters), relying on Vue's
computed/reactive behavior instead; also search for any other imports or uses of
applyFilters and remove or replace them to avoid undefined references.

312-319: Consider adding toggle behavior or a clear button for tag filter.

Once a tag is clicked, there's no way for users to clear the tag filter without reloading. Consider toggling the filter when the same tag is clicked again.

♻️ Proposed fix - toggle tag filter on re-click
 function filterByTag(tag: string) {
-  // Clear other filters and apply tag filter
-  filterType.value = '';
-  filterAuthor.value = '';
-  filterImportance.value = '0';
-  filterTag.value = tag;
-  applyFilters();
+  if (filterTag.value === tag) {
+    // Toggle off if same tag clicked
+    filterTag.value = '';
+  } else {
+    // Clear other filters and apply tag filter
+    filterType.value = '';
+    filterAuthor.value = '';
+    filterImportance.value = '0';
+    filterTag.value = tag;
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/views/main/community/memory/MemoryView.vue` around lines 312 - 319,
Update the filterByTag function to toggle the tag filter when the same tag is
clicked: inside filterByTag, check if the incoming tag equals the current
filterTag.value and if so clear filterTag.value (and optionally reset other
filters), otherwise set filterTag.value = tag and clear other filters
(filterType, filterAuthor, filterImportance) as currently done; always call
applyFilters() after updating the values. Reference: function filterByTag and
reactive vars filterTag, filterType, filterAuthor, filterImportance, and
applyFilters().

118-118: Remove unused watch import.

watch is imported but not used anywhere in the component.

♻️ Proposed fix
-import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
+import { computed, onMounted, onUnmounted, ref } from 'vue';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/views/main/community/memory/MemoryView.vue` at line 118, Remove the
unused `watch` symbol from the Vue import list: locate the import statement that
currently reads something like "import { computed, onMounted, onUnmounted, ref,
watch } from 'vue'" in the MemoryView component and delete `watch` from that
destructured import so it becomes "computed, onMounted, onUnmounted, ref"
(verify there are no remaining uses of `watch` in functions or lifecycle hooks
before committing).
🤖 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/views/main/community/memory/MemoryView.vue`:
- Around line 32-37: The "(filtered)" indicator condition is missing the tag
filter so clicking a tag (filterTag) doesn't mark results as filtered; update
the template conditional that currently checks filterType, filterAuthor, and
Number(filterImportance) > 0 to also include filterTag (e.g., add "||
filterTag") so the span is shown when a tag filter is active — look for the
memory-count block and the span using filteredEntries, filterType, filterAuthor,
filterImportance and add filterTag to that boolean expression.
- Around line 283-302: renderMarkdown currently wraps the entire output in
<p>…</p> after inserting block-level tags and uses a greedy list regex,
producing invalid HTML; update renderMarkdown to (1) treat block elements
(h1/h2/h3, ul, pre/code blocks) as standalone nodes so they are not wrapped in
paragraph tags by moving the paragraph wrapping logic to only surround plain
text blocks, (2) change the list handling in the list-matching logic used in
renderMarkdown (the code that .replace(/^- (.+)$/gm, '<li>$1</li>') and the
subsequent grouping) to first capture consecutive list lines as a block (use a
regex that matches one-or-more consecutive "^- " lines or iterate lines and
group adjacent list items) and then wrap each group with a single <ul>…</ul>,
and (3) ensure the paragraph replacements only run on text segments that do not
already contain block tags (h1/h2/h3/ul/li/code) so headings and lists remain
outside <p> wrappers; locate and change this behavior inside the renderMarkdown
function.

---

Nitpick comments:
In `@app/src/views/main/community/memory/MemoryView.vue`:
- Around line 236-238: The no-op function applyFilters should be removed and all
references to it eliminated: delete the applyFilters function definition and
remove the `@change`="applyFilters" handlers from the select elements in the
template (and any other components that reference applyFilters), relying on
Vue's computed/reactive behavior instead; also search for any other imports or
uses of applyFilters and remove or replace them to avoid undefined references.
- Around line 312-319: Update the filterByTag function to toggle the tag filter
when the same tag is clicked: inside filterByTag, check if the incoming tag
equals the current filterTag.value and if so clear filterTag.value (and
optionally reset other filters), otherwise set filterTag.value = tag and clear
other filters (filterType, filterAuthor, filterImportance) as currently done;
always call applyFilters() after updating the values. Reference: function
filterByTag and reactive vars filterTag, filterType, filterAuthor,
filterImportance, and applyFilters().
- Line 118: Remove the unused `watch` symbol from the Vue import list: locate
the import statement that currently reads something like "import { computed,
onMounted, onUnmounted, ref, watch } from 'vue'" in the MemoryView component and
delete `watch` from that destructured import so it becomes "computed, onMounted,
onUnmounted, ref" (verify there are no remaining uses of `watch` in functions or
lifecycle hooks before committing).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1c78c60e-78e5-4e9b-9353-1ec5fa00b791

📥 Commits

Reviewing files that changed from the base of the PR and between e35a185 and 7b2a55d.

📒 Files selected for processing (1)
  • app/src/views/main/community/memory/MemoryView.vue

Comment on lines +32 to +37
<div class="memory-count" v-if="filteredEntries.length">
<j-text size="300" color="ui-500">
{{ filteredEntries.length }} {{ filteredEntries.length === 1 ? 'memory' : 'memories' }}
<span v-if="filterType || filterAuthor || Number(filterImportance) > 0"> (filtered)</span>
</j-text>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Tag filter not reflected in "(filtered)" indicator.

When a user clicks a tag to filter, filterTag is set but the "(filtered)" text won't appear because the condition doesn't check it.

🐛 Proposed fix
       <j-text size="300" color="ui-500">
         {{ filteredEntries.length }} {{ filteredEntries.length === 1 ? 'memory' : 'memories' }}
-        <span v-if="filterType || filterAuthor || Number(filterImportance) > 0"> (filtered)</span>
+        <span v-if="filterType || filterAuthor || Number(filterImportance) > 0 || filterTag"> (filtered)</span>
       </j-text>
📝 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
<div class="memory-count" v-if="filteredEntries.length">
<j-text size="300" color="ui-500">
{{ filteredEntries.length }} {{ filteredEntries.length === 1 ? 'memory' : 'memories' }}
<span v-if="filterType || filterAuthor || Number(filterImportance) > 0"> (filtered)</span>
</j-text>
</div>
<div class="memory-count" v-if="filteredEntries.length">
<j-text size="300" color="ui-500">
{{ filteredEntries.length }} {{ filteredEntries.length === 1 ? 'memory' : 'memories' }}
<span v-if="filterType || filterAuthor || Number(filterImportance) > 0 || filterTag"> (filtered)</span>
</j-text>
</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/views/main/community/memory/MemoryView.vue` around lines 32 - 37, The
"(filtered)" indicator condition is missing the tag filter so clicking a tag
(filterTag) doesn't mark results as filtered; update the template conditional
that currently checks filterType, filterAuthor, and Number(filterImportance) > 0
to also include filterTag (e.g., add "|| filterTag") so the span is shown when a
tag filter is active — look for the memory-count block and the span using
filteredEntries, filterType, filterAuthor, filterImportance and add filterTag to
that boolean expression.

Comment on lines +283 to +302
function renderMarkdown(content: string): string {
if (!content) return '';
// Simple markdown-to-HTML: headings, bold, italic, code, line breaks
return content
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.+?)\*/g, '<em>$1</em>')
.replace(/`(.+?)`/g, '<code>$1</code>')
.replace(/^- (.+)$/gm, '<li>$1</li>')
.replace(/(<li>.*<\/li>\n?)+/g, '<ul>$&</ul>')
.replace(/\n\n/g, '</p><p>')
.replace(/\n/g, '<br>')
.replace(/^/, '<p>')
.replace(/$/, '</p>');
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

renderMarkdown produces invalid HTML structure.

The function wraps everything in <p> tags (lines 300-301) after inserting block-level elements like <h1>, <h2>, <ul>, resulting in invalid nesting (e.g., <p><h1>Title</h1></p>). Browsers will auto-correct this unpredictably, causing layout inconsistencies.

Additionally, the list regex on line 297 uses greedy matching which may incorrectly group non-adjacent list items.

🐛 Proposed fix - avoid wrapping block elements in paragraphs
 function renderMarkdown(content: string): string {
   if (!content) return '';
-  // Simple markdown-to-HTML: headings, bold, italic, code, line breaks
   return content
     .replace(/&/g, '&amp;')
     .replace(/</g, '&lt;')
     .replace(/>/g, '&gt;')
     .replace(/^### (.+)$/gm, '<h3>$1</h3>')
     .replace(/^## (.+)$/gm, '<h2>$1</h2>')
     .replace(/^# (.+)$/gm, '<h1>$1</h1>')
     .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
     .replace(/\*(.+?)\*/g, '<em>$1</em>')
     .replace(/`(.+?)`/g, '<code>$1</code>')
-    .replace(/^- (.+)$/gm, '<li>$1</li>')
-    .replace(/(<li>.*<\/li>\n?)+/g, '<ul>$&</ul>')
-    .replace(/\n\n/g, '</p><p>')
-    .replace(/\n/g, '<br>')
-    .replace(/^/, '<p>')
-    .replace(/$/, '</p>');
+    .replace(/^- (.+)$/gm, '<li>$1</li>')
+    .split(/\n\n+/)
+    .map(block => {
+      if (block.includes('<li>')) {
+        return '<ul>' + block.replace(/\n/g, '') + '</ul>';
+      }
+      if (/^<h[1-3]>/.test(block)) {
+        return block;
+      }
+      return '<p>' + block.replace(/\n/g, '<br>') + '</p>';
+    })
+    .join('');
 }

Alternatively, consider using a lightweight markdown library like marked or markdown-it for more robust parsing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/views/main/community/memory/MemoryView.vue` around lines 283 - 302,
renderMarkdown currently wraps the entire output in <p>…</p> after inserting
block-level tags and uses a greedy list regex, producing invalid HTML; update
renderMarkdown to (1) treat block elements (h1/h2/h3, ul, pre/code blocks) as
standalone nodes so they are not wrapped in paragraph tags by moving the
paragraph wrapping logic to only surround plain text blocks, (2) change the list
handling in the list-matching logic used in renderMarkdown (the code that
.replace(/^- (.+)$/gm, '<li>$1</li>') and the subsequent grouping) to first
capture consecutive list lines as a block (use a regex that matches one-or-more
consecutive "^- " lines or iterate lines and group adjacent list items) and then
wrap each group with a single <ul>…</ul>, and (3) ensure the paragraph
replacements only run on text segments that do not already contain block tags
(h1/h2/h3/ul/li/code) so headings and lists remain outside <p> wrappers; locate
and change this behavior inside the renderMarkdown function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant