feat: Agent Memory view for MemoryEntry social DNA#564
feat: Agent Memory view for MemoryEntry social DNA#564data-bot-coasys wants to merge 7 commits intodevfrom
Conversation
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.
✅ Deploy Preview for fluxsocial-dev ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds 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
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()
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 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 docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
renderMarkdownfunction escapes&,<,>(lines 247-249) before applying markdown transformations, which is the correct order to prevent XSS. However, usingv-htmlwith 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 likeDOMPurifywould 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
forloop. With many unique authors, this creates a waterfall of sequential requests. UsingPromise.allwould 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
watchfunction 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
hasMemoryEntriesstate is set once duringonMounted. If an AI agent creates the firstMemoryEntrywhile 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.vueto updatehasMemoryEntrieswhen 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
📒 Files selected for processing (5)
app/src/router/index.tsapp/src/views/main/community/memory/MemoryView.vueapp/src/views/main/community/sidebar/Sidebar.vuepackages/api/src/index.tspackages/api/src/memory-entry/index.ts
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.
There was a problem hiding this comment.
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
📒 Files selected for processing (5)
app/src/composables/useCommunityService.tsapp/src/views/main/community/CommunityView.vueapp/src/views/main/community/memory/MemoryView.vueapp/src/views/main/community/sidebar/Sidebar.vuepackages/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
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.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
app/src/views/main/community/memory/MemoryView.vue (3)
152-167:⚠️ Potential issue | 🟠 MajorClean up
perspective.addListeneron unmount.Line 152 stores
unsubscribe, but it is never invoked. After remount/HMR, this can accumulate listeners and duplicateloadEntries()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 | 🟠 MajorSplit load failure, true-empty, and no-match states.
Line 45 currently treats any empty
filteredEntriesas “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 | 🟡 MinorTag 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
📒 Files selected for processing (1)
app/src/views/main/community/memory/MemoryView.vue
…, tag filter, safe timestamp parsing
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
app/src/views/main/community/memory/MemoryView.vue (3)
236-238: Consider removing the no-opapplyFiltersfunction.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 unusedwatchimport.
watchis 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
📒 Files selected for processing (1)
app/src/views/main/community/memory/MemoryView.vue
| <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> |
There was a problem hiding this comment.
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.
| <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.
| function renderMarkdown(content: string): string { | ||
| if (!content) return ''; | ||
| // Simple markdown-to-HTML: headings, bold, italic, code, line breaks | ||
| return content | ||
| .replace(/&/g, '&') | ||
| .replace(/</g, '<') | ||
| .replace(/>/g, '>') | ||
| .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>'); | ||
| } |
There was a problem hiding this comment.
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, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.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.
Agent Memory View
Adds a first-class memory stream view to Flux, designed to display
MemoryEntryinstances 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
MemoryEntrySHACL subject class. This view makes their memories visible in Flux.New Model: MemoryEntry
Uses
memory://predicates matching the SHACL schema agents already use.View Features
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
Behavior Changes
depends on d9011e34-8219-4ba1-bdd1-7d104a25cd1f/91215135-9143-46eb-8f5d-b38504a40392#704