From c851b8e3ba19b981260d277b6b23e7397c3d2bd8 Mon Sep 17 00:00:00 2001 From: Sandro Date: Sun, 1 Nov 2026 11:29:06 +0100 Subject: [PATCH] feat(capture): quick dump list row follows the target (task lists for tasks) Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 3 + .../features/capture/InstantCaptureBar.tsx | 77 ++++++++++++++++--- .../__tests__/InstantCaptureBar.test.tsx | 60 +++++++++++++++ 3 files changed, 131 insertions(+), 9 deletions(-) create mode 100644 frontend/src/features/capture/__tests__/InstantCaptureBar.test.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 725c47e..9251c66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V ## [Unreleased] +### Changed +- Quick Dump: Die Listenzeile unter den Zielen richtet sich nach dem Ziel. Bei „Direkt als Aufgabe“ wählst du die Aufgabenliste (oder „Alle Aufgaben“), bei „Direkt auf Liste“ die Einkaufs- oder Checkliste; bei Quick Dump und Notiz gibt es keine Zeile mehr. Die zuletzt gewählte Liste bleibt je Ziel für die Sitzung gemerkt. + ## [0.32.0] - 2026-11-01 ### Added diff --git a/frontend/src/features/capture/InstantCaptureBar.tsx b/frontend/src/features/capture/InstantCaptureBar.tsx index 3ba4752..f59aaf9 100644 --- a/frontend/src/features/capture/InstantCaptureBar.tsx +++ b/frontend/src/features/capture/InstantCaptureBar.tsx @@ -15,8 +15,9 @@ import { Flame, Loader2, } from 'lucide-react'; -import { PriorityLevel, SharedList, Thought } from '../../lib/types'; +import { PriorityLevel, SharedList, TaskList, Thought } from '../../lib/types'; import { api, triggerHaptic } from '../../lib/api'; +import { readViewSession, writeViewSession } from '../../lib/viewSession'; import { useVoiceInput } from '../../lib/useVoiceInput'; import { VoiceWave } from '../../components/VoiceWave'; @@ -47,7 +48,12 @@ export const InstantCaptureBar: React.FC = ({ const [priority, setPriority] = useState('normal'); const [dueDate, setDueDate] = useState(''); const [target, setTarget] = useState(fixedTarget || 'inbox'); - const [targetListId, setTargetListId] = useState(''); + // Last chosen list per target, for this tab (lib/viewSession) + const remembered = useMemo(() => readViewSession<{ listId: string; taskListId: string }>('capture'), []); + const [targetListId, setTargetListId] = useState(remembered.listId || ''); + const [targetTaskListId, setTargetTaskListId] = useState(remembered.taskListId || ''); + const [taskLists, setTaskLists] = useState([]); + const writableTaskLists = useMemo(() => taskLists.filter((l) => l.permission !== 'viewer'), [taskLists]); const [showOptions, setShowOptions] = useState(defaultShowOptions || hideTargetPicker); const [voiceError, setVoiceError] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); @@ -104,6 +110,22 @@ export const InstantCaptureBar: React.FC = ({ } }, [writableLists, targetListId]); + useEffect(() => { + writeViewSession('capture', { listId: targetListId, taskListId: targetTaskListId }); + }, [targetListId, targetTaskListId]); + + // Task lists only matter once the options are open; loaded each time, so new lists show up + useEffect(() => { + if (!showOptions || hideTargetPicker) return; + api + .getTaskLists() + .then((tl) => setTaskLists(Array.isArray(tl) ? tl : [])) + .catch(() => {}); + }, [showOptions, hideTargetPicker]); + + // A remembered task list that is gone (deleted, access removed) falls back to all tasks + const selectedTaskList = writableTaskLists.find((l) => l.id === targetTaskListId); + // Close dropdown on outside click useEffect(() => { if (inlineOptions) return; @@ -151,7 +173,8 @@ export const InstantCaptureBar: React.FC = ({ const executeCapture = async ( overrideTarget?: CaptureTarget, - overrideListId?: string + overrideListId?: string, + overrideTaskListId?: string ) => { const trimmed = content.trim(); if (!trimmed || isSubmitting) return; @@ -200,6 +223,8 @@ export const InstantCaptureBar: React.FC = ({ { target: effectiveTarget, targetListId: effectiveTarget === 'list_item' ? effectiveListId : '', + taskListId: + effectiveTarget === 'task' ? (overrideTaskListId ?? (selectedTaskList ? selectedTaskList.id : '')) : '', } ); onCaptured(created, effectiveTarget); @@ -219,7 +244,7 @@ export const InstantCaptureBar: React.FC = ({ const selectedList = writableLists.find((l) => l.id === targetListId) || writableLists[0]; const getTargetLabel = () => { - if (target === 'task') return 'Direkt-Aufgabe'; + if (target === 'task') return selectedTaskList ? `${selectedTaskList.icon || '📋'} ${selectedTaskList.title}` : 'Direkt-Aufgabe'; if (target === 'list_item') return selectedList ? `${selectedList.icon} ${selectedList.title}` : 'Auf Liste'; if (target === 'document') return 'Direkt-Notiz'; return 'Quick Dump'; @@ -430,7 +455,8 @@ export const InstantCaptureBar: React.FC = ({ type="button" onClick={() => { setTarget('task'); - if (content.trim()) { + // With task lists the row below picks one (and saves); otherwise save right away + if (content.trim() && writableTaskLists.length === 0) { executeCapture('task'); } }} @@ -441,7 +467,7 @@ export const InstantCaptureBar: React.FC = ({ }`} > - Direkt als Aufgabe + Direkt als Aufgabe{writableTaskLists.length > 0 ? '…' : ''} {/* Option 3: Direct List Item */} @@ -484,15 +510,48 @@ export const InstantCaptureBar: React.FC = ({ - {/* Optional Sub-Row: Shared List Picker */} - {writableLists.length > 0 && ( + {/* Sub-row for a direct task: which task list */} + {target === 'task' && writableTaskLists.length > 0 && ( +
+ + + Aufgabenliste: + + {[{ id: '', icon: '📥', title: 'Alle Aufgaben' }, ...writableTaskLists].map((tl) => { + const isSelected = (selectedTaskList?.id || '') === tl.id; + return ( + + ); + })} +
+ )} + + {/* Sub-row for a list item: which list (shopping, checklist …) */} + {target === 'list_item' && writableLists.length > 0 && (
Liste: {writableLists.map((lst) => { - const isSelected = target === 'list_item' && targetListId === lst.id; + const isSelected = targetListId === lst.id; return (