diff --git a/frontend/src/features/lists/SharedListsView.tsx b/frontend/src/features/lists/SharedListsView.tsx index 0bd02cb..77106ea 100644 --- a/frontend/src/features/lists/SharedListsView.tsx +++ b/frontend/src/features/lists/SharedListsView.tsx @@ -19,6 +19,7 @@ import { CountBadge } from '../../components/CountBadge'; import { ListSwitcher } from '../../components/ListSwitcher'; import { LivePill } from '../../components/LivePill'; import { isPhoneWidth } from '../../lib/breakpoints'; +import { reportNav, onNavStep, restoringNavPlace } from '../../lib/navHistory'; import { OriginBadge } from '../../components/OriginBadge'; import { VoiceInputButton } from '../../components/VoiceInputButton'; import { FormSheet } from '../../components/FormSheet'; @@ -92,8 +93,24 @@ export const SharedListsView: React.FC = ({ onRefreshGloba const { setGroups, setPrimaryAction } = useMobileActions(); const [lists, setLists] = useState([]); // The open list is remembered on this device (other tabs of the app, new tab, restart) - const [activeListId, setActiveListId] = useState( - () => initialListId || readViewMemory<{ listId: string }>('lists').listId || '' + const [activeListId, setActiveListId] = useState(() => { + // Coming back here by the browser's back/forward: the list of that step + const step = restoringNavPlace(); + return (step?.tab === 'lists' && step.shopList) || initialListId || readViewMemory<{ listId: string }>('lists').listId || ''; + }); + // Switching lists is a step of the browser history (lib/navHistory.ts) + const autoSelectedListRef = useRef(false); + useEffect(() => { + const replace = autoSelectedListRef.current; + autoSelectedListRef.current = false; + if (activeListId) reportNav({ shopList: activeListId }, { replace }); + }, [activeListId]); + useEffect( + () => + onNavStep((place) => { + if (place.tab === 'lists' && place.shopList) setActiveListId(place.shopList); + }), + [] ); useEffect(() => { if (activeListId) writeViewMemory('lists', { listId: activeListId }); @@ -167,6 +184,7 @@ export const SharedListsView: React.FC = ({ onRefreshGloba if (initialListId && deduplicated.some((l) => l.id === initialListId)) { setActiveListId(initialListId); } else if (deduplicated.length > 0 && (!activeListId || !deduplicated.some((l) => l.id === activeListId))) { + autoSelectedListRef.current = true; // chosen by the app, not a step of its own in the history setActiveListId(deduplicated[0].id); } } catch { diff --git a/frontend/src/features/triage/TasksView.tsx b/frontend/src/features/triage/TasksView.tsx index 2af2451..28b5710 100644 --- a/frontend/src/features/triage/TasksView.tsx +++ b/frontend/src/features/triage/TasksView.tsx @@ -42,6 +42,7 @@ import { AvatarStack, AvatarPerson } from '../../components/AvatarStack'; import { CountBadge } from '../../components/CountBadge'; import { ListSwitcher } from '../../components/ListSwitcher'; import { isPhoneWidth } from '../../lib/breakpoints'; +import { reportNav, onNavStep, restoringNavPlace } from '../../lib/navHistory'; import { LivePill } from '../../components/LivePill'; import { FormSheet } from '../../components/FormSheet'; import { EmojiIconButton } from '../../components/EmojiIconButton'; @@ -181,7 +182,11 @@ export const TasksView: React.FC = ({ const [tasks, setTasks] = useState([]); const [taskLists, setTaskLists] = useState([]); const [selectedListId, setSelectedListIdState] = useState( - () => remembered.listId || 'all' + () => { + // Coming back here by the browser's back/forward: the list of that step + const step = restoringNavPlace(); + return (step?.tab === 'tasks' && step.taskList) || remembered.listId || 'all'; + } ); const viewMode: ViewMode = viewModes[selectedListId] || 'list'; // Only Kanban is stored; the list view is the default @@ -365,6 +370,16 @@ export const TasksView: React.FC = ({ setSelectedListIdState(id); }, []); + // Switching task lists is a step of the browser history (lib/navHistory.ts) + useEffect(() => reportNav({ taskList: selectedListId }), [selectedListId]); + useEffect( + () => + onNavStep((place) => { + if (place.tab === 'tasks' && place.taskList) setSelectedListId(place.taskList); + }), + [setSelectedListId] + ); + const onRefreshStatsRef = useRef(onRefreshStats); onRefreshStatsRef.current = onRefreshStats; // The realtime handler is registered once; it must reload with the current list, not the first one diff --git a/frontend/src/lib/navHistory.ts b/frontend/src/lib/navHistory.ts index 73957d0..f058982 100644 --- a/frontend/src/lib/navHistory.ts +++ b/frontend/src/lib/navHistory.ts @@ -1,5 +1,5 @@ // In-app back and forward: where the user is (page, open note, workspace view, zoomed bullet, on phones -// list or note) goes into the browser history, so the browser's back button (mouse button, Android back) +// list or note, open task list and list) goes into the browser history, so the browser's back button (mouse button, Android back) // steps back inside the app instead of leaving it. Each part reports its own piece; changes from one // action (e.g. opening a note from a task switches page and note) become one entry. While the app follows // a back/forward step, reports change nothing: the step itself already is the entry. @@ -10,6 +10,8 @@ export interface NavPlace { view?: string | null; // workspace view: editor, todos, tags, trash, journal … zoom?: string | null; // bullet the open note is zoomed into detail?: boolean; // phones: the note (true) or the list of notes (false) + taskList?: string | null; // open task list on the tasks page ("all" for Alle Aufgaben) + shopList?: string | null; // open list on the lists page } const RESTORE_MS = 600; // the app needs a moment to follow a step (views mount, notes load) @@ -25,7 +27,9 @@ const same = (a: NavPlace, b: NavPlace) => (a.docId ?? null) === (b.docId ?? null) && (a.view ?? null) === (b.view ?? null) && (a.zoom ?? null) === (b.zoom ?? null) && - Boolean(a.detail) === Boolean(b.detail); + Boolean(a.detail) === Boolean(b.detail) && + (a.taskList ?? null) === (b.taskList ?? null) && + (a.shopList ?? null) === (b.shopList ?? null); function flush() { if (!pending) return;