feat(voice): microphone in the add-task and add-item rows; phone fixes in the column dialog
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m38s

VoiceInputButton brings the Quick Dump microphone into other input rows (useVoiceInput, appends
to the text already there, hidden when voice input is off or unsupported): the add-task row above
the task list and the add-item field of lists (desktop row and phone sheet).

Column dialog on phones: the add-column buttons go below the field, and the Erledigt toggle of a
column shows only its tick, so the bin stays on screen. Docs and changelog; v0.32.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-11-01 11:17:49 +01:00
1 parent 08081e6c1d
commit 83b3f27b3e
9 files changed
+168 -44

No files matched your search

@@ -17,6 +17,7 @@ import { UnifiedShareModal } from '../../components/UnifiedShareModal';
import { AvatarStack, AvatarPerson } from '../../components/AvatarStack';
import { CountBadge } from '../../components/CountBadge';
import { OriginBadge } from '../../components/OriginBadge';
import { VoiceInputButton } from '../../components/VoiceInputButton';
import { FormSheet } from '../../components/FormSheet';
import { useMobileActions } from '../../context/MobileActionContext';
@@ -102,6 +103,7 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
const [newItemText, setNewItemText] = useState('');
const [newItemQty, setNewItemQty] = useState('');
const [itemVoiceError, setItemVoiceError] = useState<string | null>(null);
const [isSubmittingItem, setIsSubmittingItem] = useState(false);
const [editingQtyItemId, setEditingQtyItemId] = useState<string | null>(null);
const [editingQtyValue, setEditingQtyValue] = useState('');
@@ -955,9 +957,19 @@ export const SharedListsView: React.FC<SharedListsViewProps> = ({ onRefreshGloba
onBlur={() => {
setTimeout(() => setShowSuggestions(false), 200);
}}
placeholder={`Was soll auf „${activeList.title}“? (z. B. Hafermilch #Kühlregal)`}
placeholder={itemVoiceError || `Was soll auf „${activeList.title}“? (z. B. Hafermilch #Kühlregal)`}
// 16px on phones: iOS zooms into smaller fields and the layout jumps under the keyboard
className="w-full h-11 sm:h-9 pl-9 pr-3 input-field text-base sm:text-sm"
className="w-full h-11 sm:h-9 pl-9 pr-11 sm:pr-9 input-field text-base sm:text-sm"
/>
<VoiceInputButton
value={newItemText}
onChange={setNewItemText}
focusRef={inputRef}
onError={(message) => {
setItemVoiceError(message);
window.setTimeout(() => setItemVoiceError(null), 4000);
}}
className="absolute right-1 top-1/2 -translate-y-1/2"
/>
{/* Hashtag Autocomplete Suggestions Dropdown */}
@@ -273,7 +273,7 @@ const ManageColumnsModalContent: React.FC<ManageColumnsModalProps> = ({
type="text"
value={col.name}
onChange={(e) => handleRename(col.id, e.target.value)}
className="flex-1 h-7 px-2 text-xs input-field"
className="flex-1 min-w-0 h-7 px-2 text-xs input-field"
placeholder="Spaltenname"
/>
@@ -281,15 +281,18 @@ const ManageColumnsModalContent: React.FC<ManageColumnsModalProps> = ({
<button
type="button"
onClick={() => handleToggleDone(col.id)}
className={`h-7 px-2 text-[10px] rounded-btn border transition-colors flex items-center gap-1 ${
className={`h-7 px-2 text-[10px] rounded-btn border transition-colors flex items-center gap-1 flex-shrink-0 ${
col.isDone
? 'bg-status-ok/15 border-status-ok text-status-ok font-semibold'
: 'bg-panel border-border text-faint hover:text-muted'
}`}
title={col.isDone ? 'Gilt als Erledigt' : 'Gilt als Offen'}
aria-label={col.isDone ? 'Gilt als Erledigt' : 'Gilt als Offen'}
aria-pressed={!!col.isDone}
>
<Check className={`w-2.5 h-2.5 ${col.isDone ? 'stroke-[3]' : 'opacity-40'}`} />
<span>Erledigt</span>
{/* Phones: only the tick, so the row keeps room for the bin */}
<span className="hidden sm:inline">Erledigt</span>
</button>
<button
@@ -310,35 +313,38 @@ const ManageColumnsModalContent: React.FC<ManageColumnsModalProps> = ({
{/* Add New Column Form */}
<form onSubmit={handleAddColumn} className="space-y-2 pt-2 border-t border-border">
<label className="block text-xs font-semibold text-main">Neue Spalte hinzufügen:</label>
<div className="flex items-center gap-1.5">
{/* Phones: the buttons go below the field, so nothing is cut off */}
<div className="flex flex-col sm:flex-row sm:items-center gap-1.5">
<input
type="text"
value={newColName}
onChange={(e) => setNewColName(e.target.value)}
placeholder="z.B. Wartet auf Feedback, Review…"
className="flex-1 h-8 px-2.5 text-xs input-field"
className="w-full sm:w-auto sm:flex-1 min-w-0 h-11 sm:h-8 px-2.5 text-base sm:text-xs input-field"
/>
<button
type="button"
onClick={() => setNewColIsDone((prev) => !prev)}
className={`h-8 px-2.5 text-xs rounded-btn border transition-colors flex items-center gap-1 ${
newColIsDone
? 'bg-status-ok/15 border-status-ok text-status-ok font-semibold'
: 'bg-panel border-border text-muted hover:text-main'
}`}
title="Sollen Aufgaben in dieser Spalte als erledigt gelten?"
>
<Check className="w-3 h-3" />
<span>Erledigt</span>
</button>
<button
type="submit"
disabled={!newColName.trim()}
className="btn-primary h-8 px-3 text-xs flex items-center gap-1 disabled:opacity-40"
>
<Plus className="w-3.5 h-3.5" />
<span>Hinzufügen</span>
</button>
<div className="flex items-center gap-1.5">
<button
type="button"
onClick={() => setNewColIsDone((prev) => !prev)}
className={`flex-1 sm:flex-none justify-center h-11 sm:h-8 px-2.5 text-xs rounded-btn border transition-colors flex items-center gap-1 ${
newColIsDone
? 'bg-status-ok/15 border-status-ok text-status-ok font-semibold'
: 'bg-panel border-border text-muted hover:text-main'
}`}
title="Sollen Aufgaben in dieser Spalte als erledigt gelten?"
>
<Check className="w-3 h-3" />
<span>Erledigt</span>
</button>
<button
type="submit"
disabled={!newColName.trim()}
className="flex-1 sm:flex-none justify-center btn-primary h-11 sm:h-8 px-3 text-xs flex items-center gap-1 disabled:opacity-40"
>
<Plus className="w-3.5 h-3.5" />
<span>Hinzufügen</span>
</button>
</div>
</div>
</form>
+36 -11
View File
@@ -35,6 +35,7 @@ import { api, triggerHaptic } from '../../lib/api';
import { UnifiedShareModal } from '../../components/UnifiedShareModal';
import { TaskDetailModal } from './TaskDetailModal';
import { OriginBadge } from '../../components/OriginBadge';
import { VoiceInputButton } from '../../components/VoiceInputButton';
import { ManageColumnsModal, DEFAULT_KANBAN_COLUMNS, columnDotClass } from './ManageColumnsModal';
import { AvatarStack, AvatarPerson } from '../../components/AvatarStack';
import { CountBadge } from '../../components/CountBadge';
@@ -138,6 +139,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
// Quick Task Add state
const [quickTitle, setQuickTitle] = useState('');
const [quickVoiceError, setQuickVoiceError] = useState<string | null>(null);
const [quickDueDate, setQuickDueDate] = useState('');
const quickInputRef = useRef<HTMLInputElement | null>(null);
const { setGroups, setPrimaryAction } = useMobileActions();
@@ -145,6 +147,8 @@ export const TasksView: React.FC<TasksViewProps> = ({
// Mobile task creation & filter modal state
const [showMobileCreateModal, setShowMobileCreateModal] = useState(false);
const [mobileCreateTitle, setMobileCreateTitle] = useState('');
const [mobileVoiceError, setMobileVoiceError] = useState<string | null>(null);
const mobileCreateInputRef = useRef<HTMLInputElement | null>(null);
const [mobileCreateDueDate, setMobileCreateDueDate] = useState('');
const [mobileCreatePriority, setMobileCreatePriority] = useState<PriorityLevel>('normal');
const [mobileCreateListId, setMobileCreateListId] = useState<string>('');
@@ -1679,13 +1683,21 @@ export const TasksView: React.FC<TasksViewProps> = ({
value={quickTitle}
onChange={(e) => setQuickTitle(e.target.value)}
placeholder={
currentSelectedList
? `Aufgabe zu "${currentSelectedList.title}" hinzufügen…`
: 'Neue Aufgabe anlegen…'
quickVoiceError ||
(currentSelectedList ? `Aufgabe zu "${currentSelectedList.title}" hinzufügen…` : 'Neue Aufgabe anlegen…')
}
className="flex-1 bg-transparent px-2 py-1.5 sm:py-1 text-sm text-main placeholder-faint focus:outline-none min-w-0"
/>
<div className="flex items-center gap-1 flex-shrink-0">
<VoiceInputButton
value={quickTitle}
onChange={setQuickTitle}
focusRef={quickInputRef}
onError={(message) => {
setQuickVoiceError(message);
window.setTimeout(() => setQuickVoiceError(null), 4000);
}}
/>
<input
type="date"
value={quickDueDate}
@@ -1879,14 +1891,27 @@ export const TasksView: React.FC<TasksViewProps> = ({
<form onSubmit={handleMobileCreateTask} className="space-y-3">
<div>
<label className="text-[11px] font-semibold text-muted block mb-1">Aufgabentext</label>
<input
type="text"
autoFocus
value={mobileCreateTitle}
onChange={(e) => setMobileCreateTitle(e.target.value)}
placeholder="Was ist zu tun?…"
className="w-full input-field px-3 py-2 text-sm"
/>
<div className="relative">
<input
ref={mobileCreateInputRef}
type="text"
autoFocus
value={mobileCreateTitle}
onChange={(e) => setMobileCreateTitle(e.target.value)}
placeholder={mobileVoiceError || 'Was ist zu tun?…'}
className="w-full input-field pl-3 pr-11 py-2 text-sm"
/>
<VoiceInputButton
value={mobileCreateTitle}
onChange={setMobileCreateTitle}
focusRef={mobileCreateInputRef}
onError={(message) => {
setMobileVoiceError(message);
window.setTimeout(() => setMobileVoiceError(null), 4000);
}}
className="absolute right-1 top-1/2 -translate-y-1/2"
/>
</div>
</div>
{/* List Selector (if lists exist) */}