fix: mobile layout, task clicks, list tab corners, hashtags in URLs
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m25s

Mobile
- Plus button in triage did nothing: the stored action returned the
  capture function instead of calling it
- Workspace: tabs share the full width; save status sits as an icon next
  to the note title instead of wrapping into its own row; "Neue Notiz" /
  "Neuer Ordner" only in the top menu (and +), not repeated in the list
- Outliner fields measured while hidden stayed one line high (cut-off
  callout); they are measured again when their width changes
- Task dialog: scrolls in a div instead of a fieldset, height in dvh,
  notes editor grows with its text, so long notes are fully reachable

Desktop
- Delete button of the selected task/shopping list sits inside the tab as
  its own rounded button (matches rounded themes like Soft)

Both
- Tasks sometimes did not open: every reload after an action replaced the
  list with a loading placeholder, swallowing clicks; reloads are now
  silent after the first load of a list, stale responses are dropped
- Hashtags need whitespace or "(" before "#", so URL anchors are no
  longer tags (server, browser); one-time cleanup of stored anchor tags

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-28 16:50:51 +01:00
1 parent 41b02b9564
commit f82ab2fd32
16 files changed
+283 -47

No files matched your search

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useCallback } from 'react';
import {
X,
Check,
@@ -71,6 +71,12 @@ export const TaskDetailModal: React.FC<TaskDetailModalProps> = ({
const [isEditingNotes, setIsEditingNotes] = useState(false);
const [notesDraft, setNotesDraft] = useState(task.notes || '');
// The notes field grows with its text, so long notes scroll with the dialog instead of inside a small box
const growNotes = useCallback((el: HTMLTextAreaElement | null) => {
if (!el) return;
el.style.height = 'auto';
el.style.height = `${el.scrollHeight + 2}px`;
}, []);
const [newSubtaskTitle, setNewSubtaskTitle] = useState('');
const [newTagDraft, setNewTagDraft] = useState('');
@@ -172,7 +178,7 @@ export const TaskDetailModal: React.FC<TaskDetailModalProps> = ({
return (
<div className="fixed inset-0 !m-0 modal-overlay z-50 flex items-center justify-center p-3 sm:p-4 overflow-y-auto">
<div
className="w-full max-w-2xl panel-surface shadow-panel border border-border my-auto flex flex-col max-h-[92vh] overflow-hidden"
className="w-full max-w-2xl panel-surface shadow-panel border border-border my-auto flex flex-col max-h-[92vh] max-h-[92dvh] overflow-hidden"
onClick={(e) => e.stopPropagation()}
>
{/* Top Header */}
@@ -272,7 +278,9 @@ export const TaskDetailModal: React.FC<TaskDetailModalProps> = ({
</div>
{/* Scrollable Body (all controls disabled for read-only shares) */}
<fieldset disabled={readOnly} className="p-4 overflow-y-auto space-y-4 flex-1 min-w-0 border-0 m-0">
{/* The scroll container is a div: a scrolling <fieldset> is unreliable in mobile browsers */}
<div className="flex-1 min-h-0 overflow-y-auto overscroll-contain [-webkit-overflow-scrolling:touch]">
<fieldset disabled={readOnly} className="p-4 space-y-4 min-w-0 border-0 m-0">
{/* Task Title */}
<div>
{isEditingTitle ? (
@@ -672,7 +680,9 @@ export const TaskDetailModal: React.FC<TaskDetailModalProps> = ({
placeholder="Detailnotizen eingeben… (Markdown unterstützt)"
rows={6}
autoFocus
className="w-full input-field p-2.5 text-xs font-mono leading-relaxed resize-y min-h-[100px]"
ref={growNotes}
onInput={(e) => growNotes(e.currentTarget)}
className="w-full input-field p-2.5 text-xs font-mono leading-relaxed resize-none min-h-[100px] overflow-hidden"
/>
<div className="flex justify-end gap-1.5">
<button
@@ -725,6 +735,7 @@ export const TaskDetailModal: React.FC<TaskDetailModalProps> = ({
)}
</div>
</fieldset>
</div>
{/* Modal Footer */}
<div className="flex items-center justify-between px-4 py-2.5 border-t border-border bg-panel">
+20 -10
View File
@@ -134,16 +134,25 @@ export const TasksView: React.FC<TasksViewProps> = ({
const [groupBy, setGroupBy] = useState<'none' | 'tag' | 'status' | 'priority' | 'due'>('none');
const [sortBy, setSortBy] = useState<'custom' | 'created' | 'due' | 'priority' | 'alpha'>('custom');
// Reloads happen after almost every action and on realtime events. Only the first load of a list shows
// the loading placeholder: replacing the rendered list on every reload swallowed clicks and taps that
// happened to fall into the reload (press and release then hit different elements).
const loadedListRef = useRef<string | null>(null);
const loadSeqRef = useRef(0);
const loadData = async () => {
setLoading(true);
const seq = ++loadSeqRef.current;
const listId = selectedListId;
if (loadedListRef.current !== listId) setLoading(true);
try {
const [allTasks, allLists, allTeams, allCollabs, allDiscarded] = await Promise.all([
api.getTasks(selectedListId === 'all' ? undefined : selectedListId).catch(() => []),
api.getTasks(listId === 'all' ? undefined : listId).catch(() => []),
api.getTaskLists().catch(() => []),
api.getUserTeams().catch(() => []),
api.getCollaborators().catch(() => []),
api.getThoughts('discarded').catch(() => []),
]);
if (seq !== loadSeqRef.current) return; // a newer reload is under way
loadedListRef.current = listId;
setTasks(Array.isArray(allTasks) ? allTasks : []);
setTaskLists(Array.isArray(allLists) ? allLists : []);
setTeams(Array.isArray(allTeams) ? allTeams : []);
@@ -152,7 +161,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
} catch {
// Prevent unhandled promise rejection from crashing view
} finally {
setLoading(false);
if (seq === loadSeqRef.current) setLoading(false);
}
};
@@ -1333,14 +1342,15 @@ export const TasksView: React.FC<TasksViewProps> = ({
</button>
{safeTaskLists.map((tl) => (
<div key={tl.id} className="relative group flex items-center">
<div
key={tl.id}
className={`relative group h-8 flex items-center border rounded-btn transition-colors ${
selectedListId === tl.id ? 'bg-panel border-accent text-main pr-1' : 'bg-canvas border-border text-muted hover:text-main'
}`}
>
<button
onClick={() => setSelectedListId(tl.id)}
className={`h-8 px-3 text-xs font-medium border flex items-center gap-1.5 transition-colors ${
selectedListId === tl.id
? 'bg-panel border-accent text-main rounded-l-btn rounded-r-none border-r-0'
: 'bg-canvas border-border text-muted hover:text-main rounded-btn'
}`}
className="h-full px-3 text-xs font-medium flex items-center gap-1.5 rounded-btn"
>
<span>{tl.icon}</span>
<span>{tl.title}</span>
@@ -1377,7 +1387,7 @@ export const TasksView: React.FC<TasksViewProps> = ({
<button
onClick={() => handleDeleteTaskList(tl.id)}
title="Liste löschen"
className="h-8 px-2 bg-panel border-y border-r border-accent text-faint hover:text-status-err hover:bg-status-err/10 rounded-r-btn flex items-center justify-center transition-colors cursor-pointer"
className="h-6 w-6 text-faint hover:text-status-err hover:bg-status-err/10 rounded-btn flex items-center justify-center transition-colors cursor-pointer"
>
<X className="w-3 h-3" />
</button>