Skip to content

Add reminders to notes#38

Merged
amrelsagaei merged 2 commits intoae-issue-22-accessing-notesfrom
ae-feature-20-add-reminder-for-notes
Apr 13, 2026
Merged

Add reminders to notes#38
amrelsagaei merged 2 commits intoae-issue-22-accessing-notesfrom
ae-feature-20-add-reminder-for-notes

Conversation

@amrelsagaei
Copy link
Copy Markdown
Contributor

@amrelsagaei amrelsagaei commented Apr 1, 2026

this PR solves #20

Summary by CodeRabbit

  • New Features
    • Added reminder functionality to notes. Users can create reminders with custom date and time selection using an intuitive picker.
    • Reminders display as interactive elements within the editor and appear as notifications when due.
    • Users can dismiss or delete reminders, with visual status indicators showing reminder states (upcoming, due, dismissed, missed).

@amrelsagaei amrelsagaei requested a review from a team as a code owner April 1, 2026 16:46
@amrelsagaei amrelsagaei changed the base branch from main to ae-issue-22-accessing-notes April 1, 2026 16:46
@amrelsagaei amrelsagaei requested a review from f8thl3ss April 1, 2026 16:51
@f8thl3ss
Copy link
Copy Markdown

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 10, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 10, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3a2792be-97f1-4add-bee9-21d9640b649b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The pull request introduces a complete reminder management system spanning backend and frontend. Backend changes add reminder CRUD APIs, Zod schemas for validation, utility functions for file persistence and periodic checking, and a new backend event. Frontend changes include a TipTap editor extension for rendering reminders, UI components for picking reminder times and displaying notifications, state management via Pinia, and event-driven dismissal workflows. A shared Reminder type defines the core data structure.

Changes

Cohort / File(s) Summary
Backend API Layer
packages/backend/src/api/reminder.ts, packages/backend/src/api/index.ts
New reminder.ts module exports four async functions: getReminders, createReminder, deleteReminder, dismissReminder. Index.ts re-exports these functions to the public API surface.
Backend Schemas
packages/backend/src/schemas/reminder.ts
Introduces three Zod schemas for validation: createReminderSchema (notePath, context, reminderAt), deleteReminderSchema (reminderId), dismissReminderSchema (reminderId).
Backend Utilities
packages/backend/src/utils/reminderFile.ts, packages/backend/src/utils/reminderTimer.ts
reminderFile.ts provides persistence functions: getRemindersFilePath, readRemindersFile, writeRemindersFile. reminderTimer.ts exports startReminderTimer which periodically checks reminders every 30 seconds and emits notes++:reminderDue events when due.
Backend Types & Initialization
packages/backend/src/types/events.ts, packages/backend/src/index.ts
Events.ts adds notes++:reminderDue event handler. Index.ts updates API type to include new reminder functions and invokes startReminderTimer during backend initialization.
Editor Integration
packages/frontend/src/components/content/editor/NoteEditor.vue, packages/frontend/src/components/content/editor/extensions/reminder-node.ts
NoteEditor adds right-click context menu for creating reminders and emitter handlers for reminder picker/cancellation. ReminderNode is a new TipTap inline node extension rendering reminders as non-editable chips with state-dependent styling and close buttons.
Reminder Picker UI
packages/frontend/src/components/content/editor/reminders/ReminderPicker.vue, packages/frontend/src/components/content/editor/reminders/showReminderPicker.ts
ReminderPicker.vue is a draggable datetime picker with preset quick-select buttons. showReminderPicker.ts provides a factory function to mount and manage the picker's lifecycle.
Reminder Notifications
packages/frontend/src/components/shared/reminders/ReminderNotificationManager.vue, packages/frontend/src/components/shared/reminders/ReminderToast.vue, packages/frontend/src/components/shared/reminders/mountReminderNotifications.ts
Manager renders a stack of notification toasts. Toast displays individual reminders with auto-dismiss timers and hover-paused progress bars. mountReminderNotifications creates and mounts the notification system during app initialization.
Frontend State Management
packages/frontend/src/repositories/reminders.ts, packages/frontend/src/stores/reminders.ts
Repository layer wraps SDK backend calls with error handling. Store manages activeToasts, coordinates reminder creation/deletion/dismissal, listens for backend notes++:reminderDue events, and hydrates state from persisted data.
Frontend Utilities
packages/frontend/src/utils/eventBus.ts, packages/frontend/src/utils/reminderStates.ts, packages/frontend/src/utils/notificationSound.ts
eventBus.ts adds three new event types for picker/cancellation/state changes. reminderStates.ts provides display state computation and registry management. notificationSound.ts plays a two-tone Web Audio chime on reminder notifications.
Frontend Initialization
packages/frontend/src/index.ts
Invokes mountReminderNotifications during plugin startup to initialize the notification manager.
Shared Types
packages/shared/src/index.ts
Exports Reminder interface with fields: id, notePath, context, reminderAt, createdAt, triggered, dismissed.

Sequence Diagrams

sequenceDiagram
    actor User
    participant Editor as NoteEditor
    participant Picker as ReminderPicker
    participant Store as RemindersStore
    participant Backend as Backend API
    participant Storage as Persistence

    User->>Editor: Right-click with selection
    Editor->>Picker: Open picker at cursor position
    User->>Picker: Select/input reminder time
    Picker->>Store: confirmReminder(notePath, context, reminderAt)
    Store->>Backend: createReminder(notePath, context, reminderAt)
    Backend->>Storage: readRemindersFile(projectID)
    Storage-->>Backend: [existing reminders]
    Backend->>Backend: Generate UUID, create Reminder object
    Backend->>Storage: writeRemindersFile(projectID, [..., newReminder])
    Storage-->>Backend: Success
    Backend-->>Store: ok(reminder)
    Store->>Editor: Insert ReminderNode into document
    Editor-->>User: Reminder chip displayed in text
Loading
sequenceDiagram
    participant Timer as ReminderTimer
    participant Storage as Persistence
    participant Backend as Backend
    participant Frontend as Frontend Store
    participant UI as ReminderToast

    Timer->>Storage: readRemindersFile(projectID)
    Storage-->>Timer: [reminders]
    Timer->>Timer: Check reminderAt vs current time
    rect rgba(255, 0, 0, 0.5)
        alt Reminder is due
            Timer->>Timer: Set triggered=true
            Timer->>Storage: writeRemindersFile(projectID, updated)
            Timer->>Backend: emit notes++:reminderDue(reminder)
        end
    end
    Backend-->>Frontend: notes++:reminderDue event
    Frontend->>Frontend: Add to activeToasts
    Frontend->>UI: Render toast with auto-dismiss timer
    User->>UI: Dismiss or auto-expire
    UI->>Frontend: dismissReminder or markMissed
    Frontend->>Backend: dismissReminder(reminderId)
    Backend->>Storage: Update reminder.dismissed flag
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A flutter of reminders takes flight,
From pickers so wise to toasts burning bright,
In chips made of TipTap they nestle just right,
With state-tracking magic, the system's a sight! 🔔✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add reminders to notes' is clear, concise, and directly summarizes the main change in the changeset - introducing reminder functionality to the notes application.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ae-feature-20-add-reminder-for-notes

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: 8

🧹 Nitpick comments (5)
packages/backend/src/index.ts (1)

70-70: Store and call the previous timer disposer to guard against duplicate intervals.

startReminderTimer(sdk) returns a disposer function but its return value is dropped at line 70. If init were called multiple times (e.g., during plugin reload or re-initialization), interval loops would stack and duplicate reminder checks.

🔧 Proposed fix
+let stopReminderTimer: (() => void) | undefined;
+
 export function init(sdk: SDK<API, BackendEvents>) {
@@
-  startReminderTimer(sdk);
+  stopReminderTimer?.();
+  stopReminderTimer = startReminderTimer(sdk);
 
   sdk.console.log("Notes++ backend initialized successfully");
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/backend/src/index.ts` at line 70, The call to
startReminderTimer(sdk) returns a disposer that is currently ignored; modify the
init flow so you store that returned disposer (e.g., in a module-level variable
like reminderTimerDisposer) and before assigning a new disposer call the
existing one if present to clear the previous interval; update the place that
invokes startReminderTimer (function name startReminderTimer and the init
routine that calls it) to return and keep the disposer and invoke it on
subsequent init/reload to prevent stacked intervals.
packages/frontend/src/components/content/editor/reminders/ReminderPicker.vue (1)

61-65: Consider validating that the selected date is in the future.

While the datetime-local input has a :min attribute for client-side enforcement, programmatic manipulation or edge cases could allow submission of past dates. Adding a validation check would provide defense in depth.

🛡️ Proposed fix
 function handleConfirm() {
   const date = new Date(dateValue.value);
   if (isNaN(date.getTime())) return;
+  if (date <= new Date()) return;
   emit("confirm", date);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/frontend/src/components/content/editor/reminders/ReminderPicker.vue`
around lines 61 - 65, In handleConfirm(), add a defensive check that the parsed
dateValue (new Date(dateValue.value)) is strictly in the future before emitting:
compute now = Date.now() and if date.getTime() <= now then abort (return) and
optionally emit an error/event or set a validation state; otherwise
emit("confirm", date). This ensures programmatic or edge-case submissions of
past dates are rejected while keeping dateValue and emit as the points of
change.
packages/backend/src/utils/reminderFile.ts (1)

27-33: Consider validating parsed JSON against the Reminder schema.

The parsed data is cast to Reminder[] without validation. If reminders.json becomes corrupted or malformed, invalid objects could propagate through the system. Since Zod schemas exist for reminders, consider using them here for runtime safety.

🛡️ Proposed fix
+import { z } from "zod";
+
+const ReminderSchema = z.object({
+  id: z.string(),
+  notePath: z.string(),
+  context: z.string(),
+  reminderAt: z.string(),
+  createdAt: z.string(),
+  triggered: z.boolean(),
+  dismissed: z.boolean(),
+});
+
+const RemindersArraySchema = z.array(ReminderSchema);
+
 export function readRemindersFile(projectID: string): Reminder[] {
   // ...
   try {
     const raw = fs.readFileSync(toSystemPath(filePath), "utf8");
     const parsed = JSON.parse(raw);
-    return Array.isArray(parsed) ? (parsed as Reminder[]) : [];
+    const result = RemindersArraySchema.safeParse(parsed);
+    return result.success ? result.data : [];
   } catch {
     return [];
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/backend/src/utils/reminderFile.ts` around lines 27 - 33, The parsed
JSON is being cast to Reminder[] without runtime validation; update the logic in
reminderFile.ts to validate `parsed` using the existing Zod reminder schema
(e.g., ReminderSchema or reminderSchema) instead of a blind cast: run the Zod
parse/safeParse on `parsed`, return the validated array when valid, and fall
back to [] (and optionally log the validation error) when validation fails so
malformed reminders cannot propagate.
packages/frontend/src/repositories/reminders.ts (1)

6-48: Consider using the existing handleBackendCall utility to reduce repetition.

The error handling pattern is duplicated across all four methods. The codebase has a handleBackendCall utility in utils/backend.ts that encapsulates this pattern and also displays toast notifications on errors.

♻️ Example refactor for one method
+import { handleBackendCall } from "@/utils/backend";
+
 export const useRemindersRepository = () => {
   const sdk = useSDK();

   async function getReminders() {
-    const result = await sdk.backend.getReminders();
-    if (result.kind === "Error") {
-      throw new Error(`Error loading reminders: ${result.error}`);
-    }
-    return result.value;
+    return handleBackendCall(sdk.backend.getReminders(), sdk);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/frontend/src/repositories/reminders.ts` around lines 6 - 48, Replace
the repeated try/error-check patterns in getReminders, createReminder,
deleteReminder, and dismissReminder by delegating to the shared
handleBackendCall utility from utils/backend.ts; for each function call
handleBackendCall with the corresponding sdk.backend method (e.g.,
handleBackendCall(() => sdk.backend.getReminders()) for getReminders,
handleBackendCall(() => sdk.backend.createReminder(notePath, context,
reminderAt)) for createReminder, and similarly for deleteReminder(reminderId)
and dismissReminder(reminderId)) and return the resolved value so toast
notifications and unified error handling are used instead of manually checking
result.kind in each function.
packages/backend/src/utils/reminderTimer.ts (1)

45-45: Cleanup function is returned but not captured.

The function returns a cleanup callback that stops the interval timer, but the return value is discarded at line 70 in packages/backend/src/index.ts. While this is acceptable for a long-running plugin, consider capturing and exposing this cleanup function if graceful shutdown is required.

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

In `@packages/backend/src/utils/reminderTimer.ts` at line 45, The reminderTimer
function currently returns a cleanup callback (() => clearInterval(intervalId))
but that return value is discarded where it’s invoked; capture and expose that
cleanup function so the app can perform graceful shutdown. Modify the caller in
packages/backend/src/index.ts to store the returned cleanup (e.g., const
stopReminder = startReminderTimer(...)) and register it with your shutdown
handlers (process.on('SIGINT'/'SIGTERM') or the app/plugin shutdown flow) to
call stopReminder() during termination; if the function is not exported in
reminderTimer.ts, export the timer starter (the function that returns the
cleanup) so it can be imported and its cleanup invoked from index.ts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/backend/src/api/reminder.ts`:
- Around line 43-56: The code accepts reminderAt as any non-empty string and
persists it, which can produce invalid dates; instead, after
createReminderSchema.parse(...) and before constructing the Reminder object in
this handler, parse reminderAt into a Date, verify !isNaN(date.getTime()),
normalize it to an ISO timestamp (e.g. date.toISOString()) and use that
normalized value in the Reminder.reminderAt field; if parsing fails return
error(...) (similar to the existing project error path) so invalid timestamps
are rejected before calling ensureProjectDirectory / writing the file.

In `@packages/backend/src/schemas/reminder.ts`:
- Line 6: Replace the loose validation on reminderAt (currently
z.string().min(1) in the reminder schema) with an ISO 8601 datetime validation
so invalid strings don't produce Invalid Date in reminderTimer.ts; specifically,
update the reminderAt schema entry to use a refinement (e.g.,
z.string().refine(val => !Number.isNaN(Date.parse(val)), { message: 'Invalid ISO
8601 datetime' })) or a strict ISO regex check to ensure Date.parse/new
Date(reminderAt) yields a valid date before saving.

In `@packages/frontend/src/components/content/editor/extensions/reminder-node.ts`:
- Around line 38-57: The close button (.reminder-chip__close) is hidden with
display:none until hover which prevents keyboard users from tabbing to it;
update the CSS selector that reveals the button
(.reminder-chip--cancellable:hover .reminder-chip__close) to also reveal it on
keyboard focus (e.g., include :focus and/or :focus-visible like
.reminder-chip--cancellable:hover .reminder-chip__close,
.reminder-chip--cancellable:focus-within .reminder-chip__close) so the button
becomes tabbable, and update the markup for the icon-only button (the element
using class reminder-chip__close) to include an explicit aria-label attribute
instead of relying on title so assistive tech can announce the action.

In
`@packages/frontend/src/components/content/editor/reminders/showReminderPicker.ts`:
- Around line 22-25: The onConfirm passed into showReminderPicker is async (from
NoteEditor.vue -> createReminder) but is invoked without awaiting before calling
cleanup; update showReminderPicker's signature to accept an async handler
(onConfirm: (date: Date) => Promise<void>) and await the call inside the picker:
await onConfirm(date); then call cleanup(), and consider wrapping the await in a
try/catch to ensure cleanup always runs (cleanup in finally) and to surface/log
errors from onConfirm.

In `@packages/frontend/src/components/shared/reminders/ReminderToast.vue`:
- Around line 92-101: The toast currently only pauses auto-dismiss on mouse
hover (isHovered) so keyboard users tabbing to interactive elements (e.g.,
"Dismiss" or "Go to Note") can still trigger handleExpire(); add focus handling
to pause the countdown by listening for focusin/focusout (or focus/blur) and
setting the same pause state used for hover. Update the root toast element in
ReminderToast.vue (where `@mouseenter`="@isHovered = true" and
`@mouseleave`="@isHovered = false" are set) to also handle `@focusin`="isHovered =
true" and `@focusout`="isHovered = false" (or introduce an explicit isPaused
computed/boolean that ORs isHovered || isFocused and use that for the timer),
and ensure interactive buttons (Dismiss, Go to Note) remain focusable so focus
events fire and prevent handleExpire() while focused.

In `@packages/frontend/src/stores/reminders.ts`:
- Around line 51-66: The UI marks reminders dismissed before persistence: in
dismissReminder(reminderId) either await repository.dismissReminder(reminderId)
before mutating activeToasts, calling setReminderState and emitter.emit, or keep
the optimistic update but add a rollback in the catch that restores
activeToasts, calls setReminderState(reminderId, previousState) and re-emits
reminderStateChanged; in both cases use sdk.window.showToast for errors and
don't leave the UI in the dismissed state when repository.dismissReminder fails.
- Around line 85-88: After hydrating the in-memory registry in
repository.getReminders().then(loadReminderStates).catch(() => {}), broadcast
reminder state-change events so mounted chips update immediately: update the
promise chain in the reminders store to, after calling
loadReminderStates(registryData), iterate the registry entries and emit the
existing reminderStateChanged event (the same event used by reminder-node.ts and
the 15s poll) for each reminder whose state is now set (or simply emit for every
hydrated id) so node views restyle themselves right after hydration; reference
repository.getReminders, loadReminderStates, the registry, and the
reminderStateChanged event when implementing this.

In `@packages/frontend/src/utils/notificationSound.ts`:
- Around line 6-32: The code creates a new AudioContext each time (const ctx in
notificationSound.ts) which can exhaust browser limits and also calls
ctx.resume() without handling rejected promises; fix by caching a singleton
AudioContext (e.g., module-level let cachedCtx and reuse/create only if
!cachedCtx or cachedCtx.state === "closed"), move the play() function to use
that cached ctx (referencing play and frequencies in the diff), and replace the
direct ctx.resume().then(play) with explicit promise handling (await or
.then(...).catch(err => process/log or silently ignore) so resume rejections are
handled); ensure osc/gain nodes are created from the cached ctx and stop/cleanup
per playback to avoid leaks.

---

Nitpick comments:
In `@packages/backend/src/index.ts`:
- Line 70: The call to startReminderTimer(sdk) returns a disposer that is
currently ignored; modify the init flow so you store that returned disposer
(e.g., in a module-level variable like reminderTimerDisposer) and before
assigning a new disposer call the existing one if present to clear the previous
interval; update the place that invokes startReminderTimer (function name
startReminderTimer and the init routine that calls it) to return and keep the
disposer and invoke it on subsequent init/reload to prevent stacked intervals.

In `@packages/backend/src/utils/reminderFile.ts`:
- Around line 27-33: The parsed JSON is being cast to Reminder[] without runtime
validation; update the logic in reminderFile.ts to validate `parsed` using the
existing Zod reminder schema (e.g., ReminderSchema or reminderSchema) instead of
a blind cast: run the Zod parse/safeParse on `parsed`, return the validated
array when valid, and fall back to [] (and optionally log the validation error)
when validation fails so malformed reminders cannot propagate.

In `@packages/backend/src/utils/reminderTimer.ts`:
- Line 45: The reminderTimer function currently returns a cleanup callback (()
=> clearInterval(intervalId)) but that return value is discarded where it’s
invoked; capture and expose that cleanup function so the app can perform
graceful shutdown. Modify the caller in packages/backend/src/index.ts to store
the returned cleanup (e.g., const stopReminder = startReminderTimer(...)) and
register it with your shutdown handlers (process.on('SIGINT'/'SIGTERM') or the
app/plugin shutdown flow) to call stopReminder() during termination; if the
function is not exported in reminderTimer.ts, export the timer starter (the
function that returns the cleanup) so it can be imported and its cleanup invoked
from index.ts.

In
`@packages/frontend/src/components/content/editor/reminders/ReminderPicker.vue`:
- Around line 61-65: In handleConfirm(), add a defensive check that the parsed
dateValue (new Date(dateValue.value)) is strictly in the future before emitting:
compute now = Date.now() and if date.getTime() <= now then abort (return) and
optionally emit an error/event or set a validation state; otherwise
emit("confirm", date). This ensures programmatic or edge-case submissions of
past dates are rejected while keeping dateValue and emit as the points of
change.

In `@packages/frontend/src/repositories/reminders.ts`:
- Around line 6-48: Replace the repeated try/error-check patterns in
getReminders, createReminder, deleteReminder, and dismissReminder by delegating
to the shared handleBackendCall utility from utils/backend.ts; for each function
call handleBackendCall with the corresponding sdk.backend method (e.g.,
handleBackendCall(() => sdk.backend.getReminders()) for getReminders,
handleBackendCall(() => sdk.backend.createReminder(notePath, context,
reminderAt)) for createReminder, and similarly for deleteReminder(reminderId)
and dismissReminder(reminderId)) and return the resolved value so toast
notifications and unified error handling are used instead of manually checking
result.kind in each function.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6c746c0d-90a8-46c6-97e0-255f13a6cdf8

📥 Commits

Reviewing files that changed from the base of the PR and between 66360b0 and ec542d8.

📒 Files selected for processing (21)
  • packages/backend/src/api/index.ts
  • packages/backend/src/api/reminder.ts
  • packages/backend/src/index.ts
  • packages/backend/src/schemas/reminder.ts
  • packages/backend/src/types/events.ts
  • packages/backend/src/utils/reminderFile.ts
  • packages/backend/src/utils/reminderTimer.ts
  • packages/frontend/src/components/content/editor/NoteEditor.vue
  • packages/frontend/src/components/content/editor/extensions/reminder-node.ts
  • packages/frontend/src/components/content/editor/reminders/ReminderPicker.vue
  • packages/frontend/src/components/content/editor/reminders/showReminderPicker.ts
  • packages/frontend/src/components/shared/reminders/ReminderNotificationManager.vue
  • packages/frontend/src/components/shared/reminders/ReminderToast.vue
  • packages/frontend/src/components/shared/reminders/mountReminderNotifications.ts
  • packages/frontend/src/index.ts
  • packages/frontend/src/repositories/reminders.ts
  • packages/frontend/src/stores/reminders.ts
  • packages/frontend/src/utils/eventBus.ts
  • packages/frontend/src/utils/notificationSound.ts
  • packages/frontend/src/utils/reminderStates.ts
  • packages/shared/src/index.ts

Comment on lines +43 to +56
createReminderSchema.parse({ notePath, context, reminderAt });

const projectIDResult = await ensureProjectDirectory(sdk);
if (projectIDResult.kind === "Error") {
return error(projectIDResult.error);
}

const projectID = projectIDResult.value;
const reminder: Reminder = {
id: randomUUID(),
notePath,
context,
reminderAt,
createdAt: new Date().toISOString(),
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 | 🟠 Major

Validate reminderAt as a real timestamp before persisting it.

This currently accepts any non-empty string, so bad values can be written and then parsed with new Date(reminderAt) in the frontend. The result is Invalid Date output or reminders that never transition to due/missed. Parse and normalize the timestamp here before writing the file.

💡 Suggested change
   try {
     createReminderSchema.parse({ notePath, context, reminderAt });
+    const parsedReminderAt = new Date(reminderAt);
+    if (Number.isNaN(parsedReminderAt.getTime())) {
+      return error("Invalid reminderAt");
+    }
@@
     const reminder: Reminder = {
       id: randomUUID(),
       notePath,
       context,
-      reminderAt,
+      reminderAt: parsedReminderAt.toISOString(),
       createdAt: new Date().toISOString(),
       triggered: false,
       dismissed: false,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/backend/src/api/reminder.ts` around lines 43 - 56, The code accepts
reminderAt as any non-empty string and persists it, which can produce invalid
dates; instead, after createReminderSchema.parse(...) and before constructing
the Reminder object in this handler, parse reminderAt into a Date, verify
!isNaN(date.getTime()), normalize it to an ISO timestamp (e.g.
date.toISOString()) and use that normalized value in the Reminder.reminderAt
field; if parsing fails return error(...) (similar to the existing project error
path) so invalid timestamps are rejected before calling ensureProjectDirectory /
writing the file.

Comment thread packages/backend/src/schemas/reminder.ts Outdated
Comment thread packages/frontend/src/components/shared/reminders/ReminderToast.vue
Comment thread packages/frontend/src/stores/reminders.ts
Comment thread packages/frontend/src/stores/reminders.ts
Comment thread packages/frontend/src/utils/notificationSound.ts Outdated
@amrelsagaei amrelsagaei merged commit 71f3ef5 into ae-issue-22-accessing-notes Apr 13, 2026
4 checks passed
@amrelsagaei amrelsagaei deleted the ae-feature-20-add-reminder-for-notes branch April 13, 2026 14:18
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.

2 participants