-
Notifications
You must be signed in to change notification settings - Fork 51
feat(notes): add notes functionality to replace memory system #197
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Introduce a new notes system that allows users to save, manage, and organize information persistently. This replaces the previous memory functionality with a more flexible and user-friendly notes approach. - Add notes database schema and Convex functions - Create ManageNotesDialog component for viewing/editing notes - Add NotesToolHandler for AI tool integration - Update system prompt to support notes operations - Integrate notes into sidebar and settings - Update user deletion to handle notes cleanup Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds a notes subsystem: Convex backend table and APIs for notes, AI tool wrappers and tool surface for create/list/update/delete notes, frontend UI and handlers (sidebar, tool renderers, Manage Notes dialog), types/sidebar extraction for SidebarNotes, system-prompt integration using notes, and user-deletion flow updated to remove notes. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Frontend as Frontend (UI / Sidebar)
participant Tool as NotesToolHandler
participant Convex as Convex Backend
participant System as System Prompt
User->>Frontend: Trigger note tool action (create/list/update/delete)
Frontend->>Tool: Render tool block / accept interaction
Tool->>Convex: call create/list/update/delete note API
Convex->>Convex: validate, persist, or query notes
Convex-->>Tool: return notes / status / ids
Tool->>Frontend: open/update sidebar with SidebarNotes
Frontend->>System: include notes in prompt context (when applicable)
System-->>Frontend: updated system prompt
sequenceDiagram
participant User
participant UI as ManageNotesDialog
participant API as getUserNotesPaginated
participant Convex as Convex Backend
User->>UI: Open Manage Notes
UI->>API: Request page (cursor, limit)
API->>Convex: Fetch paginated notes
Convex-->>API: notes[], nextCursor, hasMore
API-->>UI: Return page
UI->>UI: Render notes list
loop on scroll
UI->>API: Request next page
API->>Convex: Fetch next page
Convex-->>API: notes[], nextCursor, hasMore
API-->>UI: Append notes
end
User->>UI: Delete note
UI->>Convex: deleteUserNote(noteId)
Convex-->>UI: Success
UI->>UI: Optimistically remove note from list
Estimated code review effort🎯 4 (Complex) | ⏱️ ~70 minutes Possibly related PRs
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
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/system-prompt.ts (1)
269-313: Disable notes section in temporary chats.In temporary mode you skip fetching notes but still pass
shouldIncludeNotes=true, so the prompt omits the explicit “notes tool disabled” message. If notes should be unavailable in temporary chats, gate the flag with!isTemporary.🔧 Suggested fix
- const shouldIncludeNotes = userCustomization?.include_memory_entries ?? true; + const shouldIncludeNotes = + !isTemporary && (userCustomization?.include_memory_entries ?? true);
🤖 Fix all issues with AI agents
In `@app/components/ManageNotesDialog.tsx`:
- Around line 92-107: handleDeleteAllNotes currently clears notes but leaves
pagination state (cursor and isLoadingMore) stale; after successful
deleteAllNotes and setAllNotes([]) reset the pagination state by clearing the
cursor (e.g., setCursor(null or undefined) or the variable used to track it) and
set isLoadingMore to false (and any related flags like hasMore if present)
inside handleDeleteAllNotes so subsequent fetches start from a fresh pagination
state; change references to cursor and isLoadingMore in that function to their
setter functions (e.g., setCursor, setIsLoadingMore) to perform the reset.
In `@app/components/tools/NotesToolHandler.tsx`:
- Around line 128-139: The update branch currently hardcodes category: "general"
which may be incorrect; change the construction of notes (inside the action ===
"update" branch in NotesToolHandler.tsx) so category is set from the update
payload or the existing note instead of a fixed string — e.g. use input.category
if present, otherwise pull the category from the existing note record (lookup by
input.note_id or use the result returned by the update), and only fall back to
"general" as a last resort; ensure you reference input.note_id, input.title,
input.content and keep updated_at as Date.now().
In `@convex/notes.ts`:
- Around line 239-259: The current search uses
ctx.db.query("notes").withSearchIndex("search_notes", ...) that only searches
"content", so title-only matches never make it into notes before the client-side
filter; update the logic either by adding "title" to the search index definition
(so withSearchIndex(search_notes) searches both "content" and "title") or by
performing a second DB search for title matches and merging results: run a
separate ctx.db.query("notes").withSearchIndex(...).search("title", args.search)
(apply args.category and eq("user_id", args.userId) to that query too), merge
the two result arrays deduplicating by note id, and then apply the final
client-side filtering using searchLower only if you still want extra safeguards
(ensure you use args.search.trim() and args.search.toLowerCase() as in the
existing code).
In `@lib/ai/tools/index.ts`:
- Around line 106-113: The comment "Notes tools - always available (not gated by
memory preference)" contradicts the implementation that gates note tools behind
!isTemporary && memoryEnabled; update the comment to accurately reflect the
gating (e.g., "Notes tools - available when not temporary and memory is
enabled") or, if notes truly must be always available, remove the gating
condition (!isTemporary && memoryEnabled) so
create_note/createListNotes/createUpdateNote/createDeleteNote are always
included; locate the conditional around isTemporary and memoryEnabled in
lib/ai/tools/index.ts that wraps createCreateNote, createListNotes,
createUpdateNote, and createDeleteNote and change either the comment text or the
conditional accordingly.
In `@lib/ai/tools/notes.ts`:
- Around line 286-289: The success response currently exposes the internal
note_id in the returned object (the string `Note '${note_id}' updated
successfully`); remove any direct reference to note_id and instead return a
user-facing message using the note's title when available (e.g., use note.title
or title variable), or fall back to a generic confirmation like "Note updated
successfully" to respect the "NEVER reference or cite note IDs" rule; update the
return object in the function that constructs this response (the block returning
success: true / message) to use title or a generic message instead of note_id.
- Around line 41-46: The guidance in the when_not_to_use block still references
the disabled memory tool; update the phrase "User preferences or personal facts
(use memory tool instead)" to point to an available alternative such as "User
preferences or personal facts (store in notes)" or instruct to first check with
list_notes — edit the Do NOT use notes for section in lib/ai/tools/notes.ts (the
when_not_to_use block) to remove the memory tool reference and replace it with
the chosen alternative text.
In `@lib/ai/tools/update-memory.ts`:
- Around line 1-166: The file contains an entirely commented-out
createUpdateMemory tool (symbols: createUpdateMemory, createMemory,
updateMemory, deleteMemory, getMemoryById) — remove the dead code by either
deleting this file or uncommenting and restoring a working implementation; if
you must keep it temporarily, replace the large commented block with a short
deprecation header/TODO explaining why it’s retained, the replacement (e.g.,
notes system), and a removal timeline, and ensure the file no longer contains
long commented code blocks.
In `@lib/utils/sidebar-utils.ts`:
- Around line 431-440: The update branch currently hardcodes category: "general"
which overwrites any provided category in the update input; in the block that
builds the notes entry for action === "update" (where variables action and input
are used) replace the fixed "general" with a conditional that uses
input.category when present (e.g., input.category || "general") so the sidebar
preserves a supplied category while still defaulting to "general" if none is
provided.
🧹 Nitpick comments (16)
convex/memories.ts (1)
93-95: Centralize the max content length constant to avoid drift.Both mutations hardcode the same limit; defining a shared constant will prevent future divergence.
♻️ Suggested refactor
+const MEMORY_CONTENT_MAX_LENGTH = 8192; + export const createMemoryForBackend = mutation({ args: { @@ - const maxContentLength = 8192; - if (args.content.length > maxContentLength) { + if (args.content.length > MEMORY_CONTENT_MAX_LENGTH) { throw new ConvexError({ code: "VALIDATION_ERROR", - message: `Memory content exceeds maximum length of ${maxContentLength} characters`, + message: `Memory content exceeds maximum length of ${MEMORY_CONTENT_MAX_LENGTH} characters`, }); } @@ export const updateMemoryForBackend = mutation({ args: { @@ - const maxContentLength = 8192; - if (args.content.length > maxContentLength) { + if (args.content.length > MEMORY_CONTENT_MAX_LENGTH) { throw new ConvexError({ code: "VALIDATION_ERROR", - message: `Memory content exceeds maximum length of ${maxContentLength} characters`, + message: `Memory content exceeds maximum length of ${MEMORY_CONTENT_MAX_LENGTH} characters`, }); }Also applies to: 169-170
lib/system-prompt.ts (1)
5-8: Remove commented memory imports to reduce clutter.These commented imports add noise now that notes are the intended path. Consider removing them outright.
♻️ Proposed cleanup
-// import { generateMemorySection } from "./system-prompt/memory"; import { generateNotesSection } from "./system-prompt/notes"; -// import { getMemories, getNotes } from "@/lib/db/actions"; import { getNotes } from "@/lib/db/actions";app/components/SettingsDialog.tsx (1)
16-18: Remove commented-out memories dialog remnants.The commented import, state, handler, and JSX block add noise and can be dropped now that notes fully replace memories.
♻️ Proposed cleanup
-// import { ManageMemoriesDialog } from "@/app/components/ManageMemoriesDialog"; ... - // const [showMemoriesDialog, setShowMemoriesDialog] = useState(false); ... - // const handleManageMemories = () => { - // setShowMemoriesDialog(true); - // }; ... - {/* <ManageMemoriesDialog - open={showMemoriesDialog} - onOpenChange={setShowMemoriesDialog} - /> */}Also applies to: 38-78, 190-194
convex/userDeletion.ts (1)
52-55: Align query iteration with project guidelines: consider usingfor awaitinstead of.collect().The
.collect()method is functionally correct and follows the Convex pattern for deletions (.collect()thenctx.db.delete()). However, project guidelines preferfor await (const row of query)for async query iteration. You can refactor lines 52–55 to usefor awaitwhile maintaining the parallel deletion behavior at lines 171–179:// Instead of: ctx.db.query("notes")...collect() const notesArray = []; for await (const note of ctx.db.query("notes").withIndex("by_user_id", (q) => q.eq("user_id", user.subject))) { notesArray.push(note); }This maintains performance (notes are still deleted in parallel) while aligning with the project's streaming iteration preference. The same applies to other queries in the
Promise.all()block if they have similar scale concerns.app/components/tools/NotesToolHandler.tsx (1)
7-11: Consider adding stronger typing forpartprop.Using
anyfor thepartprop loses type safety. Consider defining a specific interface or importing a shared tool part type that includestoolCallId,state,input,output, andresultproperties.app/components/PersonalizationTab.tsx (3)
89-94: Remove commented-out dead code.The commented-out button and prop reference (
onManageMemories) should be removed rather than left in the codebase.Proposed cleanup
- {/* <Button variant="outline" size="sm" onClick={onManageMemories}> - Manage - </Button> */} <Button variant="outline" size="sm" onClick={onManageNotes}> Manage </Button>
13-14: Remove commented-out prop declarations.Similar to the button, the commented-out
onManageMemoriesreferences in the interface and destructuring should be removed.Proposed cleanup
interface PersonalizationTabProps { onCustomInstructions: () => void; - // onManageMemories: () => void; onManageNotes: () => void; } const PersonalizationTab = ({ onCustomInstructions, - // onManageMemories, onManageNotes, }: PersonalizationTabProps) => {Also applies to: 19-20
62-66: UI/backend field naming inconsistency.The UI now refers to "Notes" but still uses the
include_memory_entriesfield. This works functionally but creates a terminology mismatch. Consider renaming the backend field in a future iteration or adding a comment explaining the legacy naming.types/chat.ts (1)
129-133: Type guard could be more specific to avoid future collisions.The current guard checks for
"notes" in content && "action" in content. While this works now, if anotherSidebarContentvariant adds anactionfield in the future, this could cause issues. Consider checking for a more unique property combination or adding a discriminator field.Alternative approach with explicit check
export const isSidebarNotes = ( content: SidebarContent, ): content is SidebarNotes => { - return "notes" in content && "action" in content; + return "notes" in content && "action" in content && "totalCount" in content; };app/share/[shareId]/components/SharedMessagePartHandler.tsx (2)
818-823: Duplicate local type definition instead of importing.
NoteCategoryis redefined locally here but already exists intypes/chat.ts. Import it instead to maintain consistency and avoid drift.Proposed fix
+import type { NoteCategory } from "@/types/chat"; + // Notes tool renderer function renderNotesTool( part: MessagePart, idx: number, openSidebar: ReturnType<typeof useSharedChatContext>["openSidebar"], ) { const notesInput = part.input as { title?: string; content?: string; note_id?: string; category?: string; tags?: string[]; search?: string; }; - type NoteCategory = - | "general" - | "findings" - | "methodology" - | "questions" - | "plan";
804-1002: Significant duplication with NotesToolHandler.The
renderNotesToolfunction duplicates most of the logic fromNotesToolHandler.tsxincludinggetIcon,getActionText,getTarget,getActionType, and the notes payload construction. Consider extracting shared utilities to reduce maintenance burden.Potential shared utilities could include:
getNotesIcon(toolName)getNotesActionText(toolName, isStreaming)getNotesTarget(toolName, input)buildNotesPayload(action, input, output)This would ensure consistency between live chat and shared chat views.
convex/notes.ts (4)
5-14: Type duplication creates maintenance risk.
VALID_CATEGORIESis duplicated here and intypes/chat.ts(asVALID_NOTE_CATEGORIES). The comment on line 5 acknowledges this with "keep in sync", but manual synchronization is error-prone. Consider exporting the constant from a shared location or using a build-time validation to ensure consistency.
19-21: Consider a more robust ID generation approach.
Math.random()has limited entropy and could theoretically produce collisions in high-throughput scenarios. While 8 characters from base-36 (~2.8 trillion combinations) is likely sufficient for note IDs, consider usingcrypto.randomUUID()or a similar approach if stronger uniqueness guarantees are desired.
536-564: Pagination fetches all notes and slices in memory.The current implementation collects all user notes (lines 539-543), sorts them (line 546), then slices by cursor position (lines 548-560). This defeats the purpose of pagination for performance since all records are loaded regardless of page size.
Consider using Convex's native cursor-based pagination with
.paginate()for true database-level pagination. However, since the sort is byupdated_atand the index is byuser_id, you may need to add a composite indexby_user_and_updated_atto support efficient paginated queries sorted byupdated_at.
147-156: Redundant index ordering when custom sort is applied.The
.order("desc")on line 152 orders by_creationTime, but line 156 re-sorts byupdated_at. The index ordering becomes redundant work. Consider removing.order("desc")since the results will be re-sorted anyway, or document why both are needed.lib/db/actions.ts (1)
419-457: Remove commented-out code instead of leaving it in the codebase.Large blocks of commented-out code (lines 419-457, 555-629) add noise and maintenance burden. Since the code is in version control, it can be retrieved if needed. Consider removing these commented blocks entirely.
Update getTarget() to properly display search criteria: - Show category name when filtering by category - Show "tagged: x, y" when filtering by tags - Show search term in quotes when searching - Combine multiple filters with separator - Return undefined when no filters (cleaner "Listed notes" message) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this 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
🤖 Fix all issues with AI agents
In `@app/components/tools/NotesToolHandler.tsx`:
- Around line 172-187: The auto-open useEffect that opens the sidebar for
list_notes should only run for live streaming results: update the effect in
NotesToolHandler.tsx to require status === "streaming" in addition to state ===
"output-available" and the existing hasOpenedSidebarRef check before calling
handleOpenSidebar; keep hasOpenedSidebarRef reset on toolCallId changes and
reference the useEffect that reads state, status, toolName, hasOpenedSidebarRef,
and calls handleOpenSidebar so the logic only triggers for live streaming
outputs and not when viewing historical tool parts.
Remove auto-open sidebar on output-available for notes tools to match behavior of other tool handlers (Terminal, File, etc.) where sidebar only opens on user click. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Removed outdated comments regarding notes tool availability and usage instructions to enhance code clarity and maintainability. This change streamlines the documentation within the codebase, ensuring it accurately reflects the current functionality of the notes system.
There was a problem hiding this 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
🤖 Fix all issues with AI agents
In `@app/components/tools/NotesToolHandler.tsx`:
- Around line 66-84: The getTarget function should not return internal note IDs
for "update_note" or "delete_note"; update it to return the user-facing title
(input.title or input.deleted_title) or a generic label like "Note" when title
is missing, and keep returning the category/tag/search string for "list_notes";
similarly, locate the other affected helper that currently surfaces
input.note_id (the block around the other reference at lines ~141-145) and
change it to return deleted_title/title or a generic label instead of the ID so
no internal IDs are exposed in tool targets or affectedTitle.
In `@lib/ai/tools/notes.ts`:
- Around line 336-339: The delete response currently falls back to the raw
note_id in the success message which may leak IDs; update the return object so
the message uses result.deleted_title if present and otherwise a generic term
like "note" (i.e., replace `note_id` fallback with a generic fallback), locating
the change where the code returns { success: true, message: `Note
'${result.deleted_title || note_id}' deleted successfully` } and modify the
template to use only result.deleted_title or "note" instead of note_id.
- Shorten note IDs from 8 to 5 characters for easier reference - Return original/modified data from update_note for before/after comparison - Display before/after comparison in sidebar for note updates - Show confirmation message in sidebar for note deletes - Remove note ID display from UI (internal use only) - Use consistent "Failed to X note:" error message format - Strip original/modified from persisted messages to reduce payload - Add toModelOutput to update_note tool to strip diff data from model - Sync SharedMessagePartHandler with regular NotesToolHandler logic Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Reset cursor and isLoadingMore when all notes are deleted to prevent stale pagination state that could skip newly created notes. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The filter couldn't add title-only matches since the search index only returns content matches. Removed dead code for clarity. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@app/share/`[shareId]/components/SharedMessagePartHandler.tsx:
- Around line 934-1012: The code treats any output as a success; inside the
part.state === "output-available" block (where getActionType(), notesOutput,
notesInput, original, modified are used) validate notesOutput.success (or
equivalent boolean) before populating notes, totalCount, affectedTitle,
newNoteId or treating the action as successful—if success is false, set an
error/failure state (e.g., mark isExecuting false and pass a failure
flag/message into openSidebar) and avoid showing “Created/Updated note” UI;
update handleOpenInSidebar/openSidebar and the ToolBlock props
(isClickable/onClick) to reflect failures so the UI shows the error instead of
success.
In `@convex/notes.ts`:
- Around line 17-99: The generateNoteId() function produces short 5-char IDs
that can collide; update createNoteForBackend to guard uniqueness by checking
the notes index (e.g., query by_note_id) after generating noteId and retrying
(with a limited number of attempts) until an unused ID is found (or fall back to
a longer/UUID if retries exhausted), then persist the note using that unique ID;
reference generateNoteId, createNoteForBackend, and the DB lookup/insert logic
(ctx.db.insert and the by_note_id lookup) when implementing the retry/check
loop.
- Around line 538-611: getUserNotesPaginated currently loads all notes with
.collect() and does in-memory pagination; change it to use Convex's server-side
pagination: update the args to accept paginationOpts: paginationOptsValidator,
remove manual cursor/limit handling and sorting, and call
ctx.db.query("notes").withIndex("by_user_id", q => q.eq("user_id",
identity.subject)).order("desc", "updated_at").paginate(args.paginationOpts);
then map the returned page results to the same shape (note_id, title, content,
category, tags, _creationTime, updated_at) and return the paginator-provided
nextCursor/hasMore instead of computing them manually.
🧹 Nitpick comments (3)
app/components/ComputerSidebar.tsx (1)
156-171: Consider centralizing note category colors.
If category colors are reused elsewhere (e.g., Manage Notes dialog), a shared helper avoids drift.app/share/[shareId]/components/SharedMessagePartHandler.tsx (1)
818-824: Reuse shared NoteCategory to avoid drift.
This local union duplicates VALID_NOTE_CATEGORIES; importing the shared type keeps the UI in sync when categories change.♻️ Suggested refactor
-import type { Todo } from "@/types"; +import type { Todo } from "@/types"; +import type { NoteCategory } from "@/types/chat"; ... - type NoteCategory = - | "general" - | "findings" - | "methodology" - | "questions" - | "plan";types/chat.ts (1)
86-109: Use NoteCategory for update snapshots too.
Alignoriginal/modified.categorywith the same NoteCategory type for consistency.♻️ Suggested refactor
original?: { title: string; content: string; - category: string; + category: NoteCategory; tags: string[]; }; /** For update - modified note data after update (for before/after comparison) */ modified?: { title: string; content: string; - category: string; + category: NoteCategory; tags: string[]; };
…tion - Replace manual in-memory pagination with Convex's .paginate() for O(1) memory - Add by_user_and_updated index for efficient sorted pagination - Remove redundant by_user_id index (covered by composite index) - Update ManageNotesDialog to use usePaginatedQuery - Add collision check for note ID generation with retry logic - Extract shared notes tool utilities to reduce duplication Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this 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
🤖 Fix all issues with AI agents
In `@convex/notes.ts`:
- Around line 551-592: The query getUserNotesPaginated is missing a Convex
"returns" validator; add a returns validator that enforces the response shape
returned in the handler: an object with page (array of note-summary objects),
isDone (boolean) and continueCursor (string). The note-summary object should
validate the fields you map in the handler (note_id, title, content, category,
tags, _creationTime, updated_at) with appropriate primitive validators
(string/number/array/nullable as used elsewhere) so the response shape is
stable; add this returns entry alongside the existing args
(paginationOptsValidator) in the getUserNotesPaginated query definition.
🧹 Nitpick comments (1)
convex/notes.ts (1)
730-740: Prefer async iteration over.collect()when deleting all notes (Line 730-740).
This avoids loading every note into memory before deletion.Based on learnings: When using async iteration with queries, use the `for await (const row of query)` syntax instead of `.collect()` or `.take(n)`.🛠️ Suggested refactor
- const notes = await ctx.db - .query("notes") - .withIndex("by_user_and_updated", (q) => - q.eq("user_id", identity.subject), - ) - .collect(); - - for (const note of notes) { - await ctx.db.delete(note._id); - } + for await (const note of ctx.db + .query("notes") + .withIndex("by_user_and_updated", (q) => + q.eq("user_id", identity.subject), + )) { + await ctx.db.delete(note._id); + }
| export const getUserNotesPaginated = query({ | ||
| args: { | ||
| paginationOpts: paginationOptsValidator, | ||
| }, | ||
| handler: async (ctx, args) => { | ||
| const identity = await ctx.auth.getUserIdentity(); | ||
| if (!identity) { | ||
| return { | ||
| page: [], | ||
| isDone: true, | ||
| continueCursor: "", | ||
| }; | ||
| } | ||
|
|
||
| try { | ||
| const result = await ctx.db | ||
| .query("notes") | ||
| .withIndex("by_user_and_updated", (q) => | ||
| q.eq("user_id", identity.subject), | ||
| ) | ||
| .order("desc") | ||
| .paginate(args.paginationOpts); | ||
|
|
||
| return { | ||
| page: result.page.map((note) => ({ | ||
| note_id: note.note_id, | ||
| title: note.title, | ||
| content: note.content, | ||
| category: note.category, | ||
| tags: note.tags, | ||
| _creationTime: note._creationTime, | ||
| updated_at: note.updated_at, | ||
| })), | ||
| isDone: result.isDone, | ||
| continueCursor: result.continueCursor, | ||
| }; | ||
| } catch (error) { | ||
| console.error("Failed to get paginated notes:", error); | ||
| return { page: [], isDone: true, continueCursor: "" }; | ||
| } | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's find the convex/notes.ts file and check its structure
find . -name "notes.ts" -type f | head -20Repository: hackerai-tech/hackerai
Length of output: 136
🏁 Script executed:
# Let's check if convex/notes.ts exists and examine the relevant lines
if [ -f convex/notes.ts ]; then
wc -l convex/notes.ts
echo "---"
sed -n '551,592p' convex/notes.ts
fiRepository: hackerai-tech/hackerai
Length of output: 1198
🏁 Script executed:
# Let's also check the Convex rules file mentioned in learnings
if [ -f .cursor/rules/convex_rules.mdc ]; then
cat .cursor/rules/convex_rules.mdc | head -100
fiRepository: hackerai-tech/hackerai
Length of output: 6055
🏁 Script executed:
# Check other query/mutation functions in the file to see if they have returns validators
rg "export const.*query\(|export const.*mutation\(|export const.*action\(" convex/notes.ts -A 5Repository: hackerai-tech/hackerai
Length of output: 1506
Add a return validator for getUserNotesPaginated (Line 551-592).
This query is missing returns, which the Convex rules require and helps keep response shapes stable.
🛠️ Suggested fix
export const getUserNotesPaginated = query({
args: {
paginationOpts: paginationOptsValidator,
},
+ returns: v.object({
+ page: v.array(
+ v.object({
+ note_id: v.string(),
+ title: v.string(),
+ content: v.string(),
+ category: v.string(),
+ tags: v.array(v.string()),
+ _creationTime: v.number(),
+ updated_at: v.number(),
+ }),
+ ),
+ isDone: v.boolean(),
+ continueCursor: v.string(),
+ }),
handler: async (ctx, args) => {🤖 Prompt for AI Agents
In `@convex/notes.ts` around lines 551 - 592, The query getUserNotesPaginated is
missing a Convex "returns" validator; add a returns validator that enforces the
response shape returned in the handler: an object with page (array of
note-summary objects), isDone (boolean) and continueCursor (string). The
note-summary object should validate the fields you map in the handler (note_id,
title, content, category, tags, _creationTime, updated_at) with appropriate
primitive validators (string/number/array/nullable as used elsewhere) so the
response shape is stable; add this returns entry alongside the existing args
(paginationOptsValidator) in the getUserNotesPaginated query definition.
Introduce a new notes system that allows users to save, manage, and organize information persistently. This replaces the previous memory functionality with a more flexible and user-friendly notes approach.
Summary by CodeRabbit
New Features
UI Updates
✏️ Tip: You can customize this high-level summary in your review settings.