From 80324fba3b6d31ccb551189a441d007813d55c80 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 22 Jul 2026 08:26:19 -0400 Subject: [PATCH] =?UTF-8?q?m4.5:=20lists=20first-class=20=E2=80=94=20creat?= =?UTF-8?q?e=20a=20checklist=20straight=20from=20quick-add?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Checklist notes existed (M2) but could only be made by creating a text note and toggling it in the editor — so they were undiscoverable. Now the board's quick-add can make one in one shot. - create endpoint accepts kind + items: POST /api/notes with kind:"list" and items:[...] creates a checklist note and its items atomically. A list note is non-empty when it has a title or ≥1 item. - quick-add gets a checklist toggle (checkbox icon): flip it and each body line becomes an item on save; placeholder switches to "One item per line"; resets to a plain note after close. - notes store create() accepts kind + items; parse_list_items() helper (trims, drops blanks) with a unit test. - title placeholders now read "Title (optional)" in quick-add too. Third item of M4.5. Display/editing of checklists was already built in M2; this closes the creation gap. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm --- frontend/src/components/QuickAdd.vue | 44 +++++++++++++++++++++++++--- frontend/src/stores/notes.ts | 8 ++++- src/thoughtsync/notes.py | 21 +++++++++++-- tests/test_notes.py | 8 +++++ 4 files changed, 74 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/QuickAdd.vue b/frontend/src/components/QuickAdd.vue index 982fda1..1d0f8ce 100644 --- a/frontend/src/components/QuickAdd.vue +++ b/frontend/src/components/QuickAdd.vue @@ -2,6 +2,7 @@ import { onBeforeUnmount, onMounted, nextTick, ref } from "vue"; import { useNotesStore } from "../stores/notes"; import ColorPicker from "./ColorPicker.vue"; +import Icon from "./Icon.vue"; import type { NoteColor } from "../notes/colors"; const props = withDefaults(defineProps<{ autofocus?: boolean }>(), { autofocus: false }); @@ -13,9 +14,17 @@ const saving = ref(false); const title = ref(""); const body = ref(""); const color = ref("default"); +// 'text' = freeform note; 'list' = checklist (each body line becomes an item). +const mode = ref<"text" | "list">("text"); const root = ref(null); const bodyInput = ref(null); +function toggleMode() { + mode.value = mode.value === "list" ? "text" : "list"; + bodyInput.value?.focus(); + void nextTick(autoGrow); +} + async function open() { expanded.value = true; await nextTick(); @@ -45,7 +54,16 @@ async function persist(): Promise { if (!hasContent()) return; saving.value = true; try { - await notes.create({ title: title.value, body: body.value, color: color.value }); + if (mode.value === "list") { + // Each non-empty body line becomes a checklist item. + const items = body.value + .split("\n") + .map((l) => l.trim()) + .filter(Boolean); + await notes.create({ title: title.value, body: "", color: color.value, kind: "list", items }); + } else { + await notes.create({ title: title.value, body: body.value, color: color.value }); + } } finally { saving.value = false; } @@ -56,6 +74,7 @@ async function commit() { await persist(); clearFields(); expanded.value = false; + mode.value = "text"; // next capture starts as a plain note } // Shift+Enter: save the current note and immediately start a fresh one, staying @@ -102,7 +121,7 @@ defineExpose({ open });
- +
+ + +