diff --git a/packages/web/src/picker/DirectoryPicker.tsx b/packages/web/src/picker/DirectoryPicker.tsx index a9d90bf..c4f3f12 100644 --- a/packages/web/src/picker/DirectoryPicker.tsx +++ b/packages/web/src/picker/DirectoryPicker.tsx @@ -6,7 +6,7 @@ import { Icon } from "../ui/Icon"; import { useFocusTrap } from "../ui/useFocusTrap"; import { ApiError } from "../api/client"; import { fuzzyFilter } from "./fuzzy"; -import { loadDirBranches, loadFavoriteDirs, recordDirBranch, toggleFavoriteDir } from "./recents"; +import { clearRecents, loadDirBranches, loadFavoriteDirs, recordDirBranch, toggleFavoriteDir } from "./recents"; import type { DirEntry, DirListing, FsSearchResult } from "../types/server"; export interface DirectoryPickerProps { @@ -67,6 +67,8 @@ export function DirectoryPicker({ const [loading, setLoading] = useState(true); // Favorites are managed locally (seeded from localStorage) so a pin/unpin re-renders immediately. const [favorites, setFavorites] = useState(() => loadFavoriteDirs()); + // Recents mirror the prop locally so "Clear" can empty the section without a reload. + const [recentsList, setRecentsList] = useState(recents); // Inline "New folder" flow: a reveal-on-tap input in the current directory. `newFolderError` carries // the per-attempt failure (409 → "already exists") right next to the input, not a global alert. const [newFolderOpen, setNewFolderOpen] = useState(false); @@ -203,8 +205,15 @@ export function DirectoryPicker({ }; const toggleFav = (path: string, branch?: string) => setFavorites(toggleFavoriteDir(path, branch)); + // Wipe recents (after a confirm) — favorites are separate storage and stay put. + const onClearRecents = () => { + if (!window.confirm("Clear recent directories? Favorites are kept.")) return; + clearRecents(); + setRecentsList([]); + }; + // Recents minus anything already pinned (favorites render first, so avoid a duplicate row). - const recentsOnly = recents.filter((p) => !favorites.includes(p)); + const recentsOnly = recentsList.filter((p) => !favorites.includes(p)); return (
@@ -281,7 +290,19 @@ export function DirectoryPicker({ {recentsOnly.length > 0 && (
-

Recents

+
+

Recents

+ {/* Recents accumulate dead paths after projects move — a quiet, confirmed wipe. + Favorites live in separate storage and are untouched. */} + +
    {recentsOnly.map((p) => ( { localStorage.setItem("roamcode.recents", "{not json"); expect(loadRecentDirs()).toEqual([]); }); + it("clearRecents empties recents but keeps favorites and their branch labels", () => { + pushRecentDir("/a"); + pushRecentDir("/b"); + toggleFavoriteDir("/fav", "trunk"); + clearRecents(); + expect(loadRecentDirs()).toEqual([]); + expect(loadFavoriteDirs()).toEqual(["/fav"]); + expect(dirBranch("/fav")).toBe("trunk"); + // Recents start repopulating again afterwards. + pushRecentDir("/c"); + expect(loadRecentDirs()).toEqual(["/c"]); + }); it("remembers a directory's git branch when pushed with one", () => { pushRecentDir("/repo", "main"); expect(dirBranch("/repo")).toBe("main"); diff --git a/packages/web/src/picker/recents.ts b/packages/web/src/picker/recents.ts index 3b3a050..ba75234 100644 --- a/packages/web/src/picker/recents.ts +++ b/packages/web/src/picker/recents.ts @@ -24,6 +24,12 @@ export function pushRecentDir(path: string, branch?: string): void { if (branch) recordDirBranch(path, branch); } +/** Wipe the recents list. Favorites (and their branch labels) are stored under separate keys and + * are deliberately untouched — clearing recents must never cost a pin. */ +export function clearRecents(): void { + localStorage.removeItem(KEY); +} + /** Pinned/favorite directories — shown at the very top of the picker, ahead of recents. Persisted * separately so a pin survives even after the path rolls off the (capped) recents list. */ export function loadFavoriteDirs(): string[] {