diff --git a/src/views/components/AddTaskModal.jsx b/src/views/components/AddTaskModal.jsx
index ef9a402..75dabc7 100644
--- a/src/views/components/AddTaskModal.jsx
+++ b/src/views/components/AddTaskModal.jsx
@@ -60,7 +60,7 @@ const getDefaultNotification = () => {
return defaultNotification
}
-const TaskInput = ({ onChoreUpdate, isModalOpen, onClose }) => {
+const TaskInput = ({ onChoreUpdate, isModalOpen, onClose, initialMode }) => {
const { ResponsiveModal } = useResponsiveModal()
const isMobile = useMediaQuery(theme => theme.breakpoints.down('sm'))
const pickerEmptyDisplay = isMobile ? 'icon' : 'icon-text'
@@ -136,6 +136,29 @@ const TaskInput = ({ onChoreUpdate, isModalOpen, onClose }) => {
voiceInputService.isSupported().then(setVoiceAvailable)
}, [])
+ // Quick-capture widget entry points (donetick://chores/add?mode=voice|scan)
+ // land here: open straight into the requested panel, once per modal open so
+ // backing out of the panel doesn't bounce the user right back into it.
+ const appliedInitialModeRef = useRef(false)
+ useEffect(() => {
+ if (!isModalOpen) {
+ appliedInitialModeRef.current = false
+ return
+ }
+ if (appliedInitialModeRef.current) return
+
+ if (initialMode === 'voice') {
+ // isSupported() resolves async — wait for the answer before deciding.
+ if (!voiceAvailable) return
+ appliedInitialModeRef.current = true
+ setShowVoice(true)
+ } else if (initialMode === 'scan') {
+ appliedInitialModeRef.current = true
+ setScanAutoCapture(true)
+ setShowScan(true)
+ }
+ }, [isModalOpen, initialMode, voiceAvailable])
+
// Priority colors
const priorityColors = {
0: TASK_COLOR.NO_PRIORITY,
@@ -817,18 +840,21 @@ const TaskInput = ({ onChoreUpdate, isModalOpen, onClose }) => {
/>
)}
-
+ {/* Sub-panels (voice/scan) own their own confirm action */}
+ {!showScan && !showVoice && (
+
+ )}
}
>
@@ -1177,7 +1203,6 @@ const TaskInput = ({ onChoreUpdate, isModalOpen, onClose }) => {
userLabels={userLabels || []}
members={circleMembers?.res || []}
userProfile={userProfile}
- onClose={() => setShowVoice(false)}
onUseSingle={handleVoiceSingle}
onCreateMany={handleVoiceCreateMany}
/>
diff --git a/src/views/components/ScanToTask/ScanPanel.jsx b/src/views/components/ScanToTask/ScanPanel.jsx
index 4732193..be7aa02 100644
--- a/src/views/components/ScanToTask/ScanPanel.jsx
+++ b/src/views/components/ScanToTask/ScanPanel.jsx
@@ -171,9 +171,6 @@ const ScanPanel = ({ open, onTaskExtracted, onClose, initialImageUrl, autoCaptur
/>
-
{isNativeScanner ? (
-
)}
diff --git a/src/views/components/VoiceToTask/VoicePanel.jsx b/src/views/components/VoiceToTask/VoicePanel.jsx
index 97601d2..7d6bae3 100644
--- a/src/views/components/VoiceToTask/VoicePanel.jsx
+++ b/src/views/components/VoiceToTask/VoicePanel.jsx
@@ -411,7 +411,6 @@ const VoicePanel = ({
userLabels = [],
members = [],
userProfile,
- onClose,
onUseSingle,
onCreateMany,
}) => {
@@ -426,7 +425,6 @@ const VoicePanel = ({
removeSegment,
updateSegment,
patchSegment,
- reset,
isNative,
} = useVoiceToTask({ members, userLabels })
const [creating, setCreating] = useState(false)
@@ -463,11 +461,6 @@ const VoicePanel = ({
const isListening = phase === 'listening'
const showActions = segments.length > 0 && !isListening && !creating
- const handleCancel = () => {
- reset()
- onClose()
- }
-
const mergedTask = segment => ({
...parseVoiceTask(segment.text, parseCtx),
...(segment.overrides || {}),
@@ -644,62 +637,52 @@ const VoicePanel = ({
level='body-xs'
sx={{ opacity: 0.5, px: 2, textAlign: 'center' }}
>
- Pause or say “also” between tasks · say
- “scratch that” to remove the last one
+ Pause between tasks · say “scratch that” to
+ remove the last one
)}
- {/* ── Footer ── */}
-
-
-
- {creating && (
+ {creating ? (
+ ) : segments.length === 1 ? (
+
+ ) : (
+
)}
- {showActions &&
- (segments.length === 1 ? (
-
- ) : (
-
- ))}
-
+ )}
)
}