M3 reminders: notes.remind_at + Reminders view
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 5s
CI & Build / Python tests (push) Successful in 7s
CI & Build / Build & push image (push) Successful in 32s

- Migration 0010: notes.remind_at (nullable tz). PATCH accepts remind_at
  (ISO set / null clear); GET /api/notes/reminders (soonest first, non-trashed);
  serialize includes remind_at.
- Frontend: datetime util (local<->ISO, format, overdue); notes store setReminder;
  editor datetime-local picker + clear; card reminder chip (overdue = red);
  sidebar Reminders entry + /reminders view.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
This commit is contained in:
2026-07-20 08:12:14 -04:00
co-authored by Claude Opus 4.8
parent ad006ccb58
commit c57982d910
12 changed files with 205 additions and 1 deletions
+25
View File
@@ -7,6 +7,7 @@ import ColorPicker from "./ColorPicker.vue";
import Icon from "./Icon.vue";
import LabelPicker from "./LabelPicker.vue";
import NoteChecklist from "./NoteChecklist.vue";
import { fromLocalInput, toLocalInput } from "../notes/datetime";
import type { Note, NoteLabel } from "../stores/notes";
import type { NoteColor } from "../notes/colors";
@@ -86,6 +87,12 @@ async function openLink(link: { title: string; id: string | null }) {
emit("navigate", created.id);
}
const reminderLocal = computed(() => toLocalInput(liveNote.value.remind_at));
function onReminderChange(e: Event) {
void notes.setReminder(props.note.id, fromLocalInput((e.target as HTMLInputElement).value));
}
async function onLabelsChange(next: NoteLabel[]) {
labelList.value = next;
await notes.setLabels(
@@ -226,6 +233,24 @@ async function act(fn: () => Promise<void>) {
</span>
</div>
<div class="flex items-center gap-2 pt-1">
<Icon name="bell" class="text-neutral-400" />
<input
type="datetime-local"
:value="reminderLocal"
class="rounded-md border border-neutral-300 bg-white px-2 py-1 text-xs text-neutral-700 outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-200"
@change="onReminderChange"
/>
<button
v-if="liveNote.remind_at"
type="button"
class="text-xs text-neutral-400 hover:text-neutral-700 dark:hover:text-neutral-200"
@click="notes.setReminder(note.id, null)"
>
Clear
</button>
</div>
<div
v-if="outgoingLinks.length || backlinks.length"
class="flex flex-col gap-2 border-t border-neutral-100 pt-2 dark:border-neutral-800"