From de5aa0a35d21420f5e53cd107f75a16540048376 Mon Sep 17 00:00:00 2001 From: AKHIL Date: Tue, 21 Jul 2026 22:46:41 +0530 Subject: [PATCH 01/39] feat(api-client): git-friendly folder-backed collections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Open a folder as a collection: requests stored as YAML files (NNN- prefix = order, dirs = folders, collection.yaml manifest), so collections can be committed, diffed and PR-reviewed. Filesystem is the store — mutations diff the serialized tree and write only changed files; window focus re-reads external edits. Secrets stay out of files ({{vault.*}} tokens pass through; environments never serialized). Desktop only: tauri fs/dialog/persisted-scope plugins plus a recursive fs-scope grant command for picked dirs. Co-Authored-By: Claude Fable 5 --- apps/desktop-ui/messages/af.json | 2 + apps/desktop-ui/messages/ar.json | 2 + apps/desktop-ui/messages/ca.json | 2 + apps/desktop-ui/messages/cs.json | 2 + apps/desktop-ui/messages/da.json | 2 + apps/desktop-ui/messages/de.json | 2 + apps/desktop-ui/messages/el.json | 2 + apps/desktop-ui/messages/en.json | 2 + apps/desktop-ui/messages/es.json | 2 + apps/desktop-ui/messages/fa.json | 2 + apps/desktop-ui/messages/fr.json | 2 + apps/desktop-ui/messages/id.json | 2 + apps/desktop-ui/messages/it.json | 2 + apps/desktop-ui/messages/ja.json | 2 + apps/desktop-ui/messages/ko.json | 2 + apps/desktop-ui/messages/ms.json | 2 + apps/desktop-ui/messages/nb.json | 2 + apps/desktop-ui/messages/nl.json | 2 + apps/desktop-ui/messages/pl.json | 2 + apps/desktop-ui/messages/pt-BR.json | 2 + apps/desktop-ui/messages/pt.json | 2 + apps/desktop-ui/messages/ru.json | 2 + apps/desktop-ui/messages/sv.json | 2 + apps/desktop-ui/messages/tr.json | 2 + apps/desktop-ui/messages/uk.json | 2 + apps/desktop-ui/messages/vi.json | 2 + apps/desktop-ui/messages/zh.json | 2 + apps/desktop-ui/package.json | 2 + .../collections/collections-sidebar.tsx | 24 +- .../api-client/collections/use-collections.ts | 276 +++++++++++- .../context/collections-context.tsx | 6 +- .../src/components/api-client/types.ts | 3 + .../api-client/__tests__/file-store.test.ts | 185 ++++++++ .../src/lib/api-client/file-store.ts | 405 ++++++++++++++++++ .../src/lib/desktop/collection-files.ts | 88 ++++ apps/desktop/src-tauri/Cargo.lock | 94 ++++ apps/desktop/src-tauri/Cargo.toml | 3 + .../src-tauri/capabilities/default.json | 17 +- apps/desktop/src-tauri/src/lib.rs | 17 + 39 files changed, 1150 insertions(+), 24 deletions(-) create mode 100644 apps/desktop-ui/src/lib/api-client/__tests__/file-store.test.ts create mode 100644 apps/desktop-ui/src/lib/api-client/file-store.ts create mode 100644 apps/desktop-ui/src/lib/desktop/collection-files.ts diff --git a/apps/desktop-ui/messages/af.json b/apps/desktop-ui/messages/af.json index 4973c624..7c361478 100644 --- a/apps/desktop-ui/messages/af.json +++ b/apps/desktop-ui/messages/af.json @@ -1923,6 +1923,8 @@ "collectionsSidebar": { "title": "API-hulpbronne", "newCollection": "Nuwe versameling", + "openFolderCollection": "Maak vouer oop as versameling", + "folderCollection": "Vouerversameling", "tabCollections": "Versamelings", "tabHistory": "Geskiedenis", "noCollectionsTitle": "Geen versamelings", diff --git a/apps/desktop-ui/messages/ar.json b/apps/desktop-ui/messages/ar.json index 0d61ee09..dbd633ef 100644 --- a/apps/desktop-ui/messages/ar.json +++ b/apps/desktop-ui/messages/ar.json @@ -2008,6 +2008,8 @@ "collectionsSidebar": { "title": "موارد API", "newCollection": "مجموعة جديدة", + "openFolderCollection": "فتح مجلد كمجموعة", + "folderCollection": "مجموعة مجلد", "tabCollections": "المجموعات", "tabHistory": "السجل", "noCollectionsTitle": "لا مجموعات", diff --git a/apps/desktop-ui/messages/ca.json b/apps/desktop-ui/messages/ca.json index 8db95bdf..6b316f56 100644 --- a/apps/desktop-ui/messages/ca.json +++ b/apps/desktop-ui/messages/ca.json @@ -2008,6 +2008,8 @@ "collectionsSidebar": { "title": "Recursos API", "newCollection": "Col·lecció nova", + "openFolderCollection": "Obre una carpeta com a col·lecció", + "folderCollection": "Col·lecció de carpeta", "tabCollections": "Col·leccions", "tabHistory": "Historial", "noCollectionsTitle": "Sense col·leccions", diff --git a/apps/desktop-ui/messages/cs.json b/apps/desktop-ui/messages/cs.json index d2d63a26..ed9a80c2 100644 --- a/apps/desktop-ui/messages/cs.json +++ b/apps/desktop-ui/messages/cs.json @@ -2008,6 +2008,8 @@ "collectionsSidebar": { "title": "API zdroje", "newCollection": "Nová kolekce", + "openFolderCollection": "Otevřít složku jako kolekci", + "folderCollection": "Kolekce ze složky", "tabCollections": "Kolekce", "tabHistory": "Historie", "noCollectionsTitle": "Žádné kolekce", diff --git a/apps/desktop-ui/messages/da.json b/apps/desktop-ui/messages/da.json index d2020c77..f1b2c4d6 100644 --- a/apps/desktop-ui/messages/da.json +++ b/apps/desktop-ui/messages/da.json @@ -2008,6 +2008,8 @@ "collectionsSidebar": { "title": "API-ressourcer", "newCollection": "Ny kollektion", + "openFolderCollection": "Åbn mappe som samling", + "folderCollection": "Mappesamling", "tabCollections": "Samlinger", "tabHistory": "Historie", "noCollectionsTitle": "Ingen samlinger", diff --git a/apps/desktop-ui/messages/de.json b/apps/desktop-ui/messages/de.json index 6c29a90c..1770dd2e 100644 --- a/apps/desktop-ui/messages/de.json +++ b/apps/desktop-ui/messages/de.json @@ -2008,6 +2008,8 @@ "collectionsSidebar": { "title": "API-Ressourcen", "newCollection": "Neue Sammlung", + "openFolderCollection": "Ordner als Sammlung öffnen", + "folderCollection": "Ordner-Sammlung", "tabCollections": "Sammlungen", "tabHistory": "Verlauf", "noCollectionsTitle": "Keine Sammlungen", diff --git a/apps/desktop-ui/messages/el.json b/apps/desktop-ui/messages/el.json index 7c76d021..cad4cac6 100644 --- a/apps/desktop-ui/messages/el.json +++ b/apps/desktop-ui/messages/el.json @@ -2008,6 +2008,8 @@ "collectionsSidebar": { "title": "Πόροι API", "newCollection": "Νέα συλλογή", + "openFolderCollection": "Άνοιγμα φακέλου ως συλλογή", + "folderCollection": "Συλλογή φακέλου", "tabCollections": "Συλλογές", "tabHistory": "Ιστορία", "noCollectionsTitle": "Χωρίς συλλογές", diff --git a/apps/desktop-ui/messages/en.json b/apps/desktop-ui/messages/en.json index e94e917f..624c1c32 100644 --- a/apps/desktop-ui/messages/en.json +++ b/apps/desktop-ui/messages/en.json @@ -1948,6 +1948,8 @@ "collectionsSidebar": { "title": "API resources", "newCollection": "New collection", + "openFolderCollection": "Open folder as collection", + "folderCollection": "Folder collection", "tabCollections": "Collections", "tabHistory": "History", "noCollectionsTitle": "No collections", diff --git a/apps/desktop-ui/messages/es.json b/apps/desktop-ui/messages/es.json index a93bd0c0..a07d5274 100644 --- a/apps/desktop-ui/messages/es.json +++ b/apps/desktop-ui/messages/es.json @@ -1923,6 +1923,8 @@ "collectionsSidebar": { "title": "Recursos API", "newCollection": "Nueva colección", + "openFolderCollection": "Abrir carpeta como colección", + "folderCollection": "Colección de carpeta", "tabCollections": "Colecciones", "tabHistory": "Historial", "noCollectionsTitle": "Sin colecciones", diff --git a/apps/desktop-ui/messages/fa.json b/apps/desktop-ui/messages/fa.json index 414e3619..2ec79c56 100644 --- a/apps/desktop-ui/messages/fa.json +++ b/apps/desktop-ui/messages/fa.json @@ -1923,6 +1923,8 @@ "collectionsSidebar": { "title": "منابع API", "newCollection": "مجموعه جدید", + "openFolderCollection": "باز کردن پوشه به عنوان مجموعه", + "folderCollection": "مجموعه پوشه‌ای", "tabCollections": "مجموعه ها", "tabHistory": "تاریخچه", "noCollectionsTitle": "بدون مجموعه", diff --git a/apps/desktop-ui/messages/fr.json b/apps/desktop-ui/messages/fr.json index b1035452..b0a84b80 100644 --- a/apps/desktop-ui/messages/fr.json +++ b/apps/desktop-ui/messages/fr.json @@ -1923,6 +1923,8 @@ "collectionsSidebar": { "title": "Ressources API", "newCollection": "Nouvelle collection", + "openFolderCollection": "Ouvrir un dossier comme collection", + "folderCollection": "Collection de dossier", "tabCollections": "Collections", "tabHistory": "Historique", "noCollectionsTitle": "Aucune collection", diff --git a/apps/desktop-ui/messages/id.json b/apps/desktop-ui/messages/id.json index 1f7c7675..c703e8ca 100644 --- a/apps/desktop-ui/messages/id.json +++ b/apps/desktop-ui/messages/id.json @@ -2008,6 +2008,8 @@ "collectionsSidebar": { "title": "API resources", "newCollection": "New collection", + "openFolderCollection": "Buka folder sebagai koleksi", + "folderCollection": "Koleksi folder", "tabCollections": "Collections", "tabHistory": "History", "noCollectionsTitle": "No collections", diff --git a/apps/desktop-ui/messages/it.json b/apps/desktop-ui/messages/it.json index cc6e30a0..fdbdb7ec 100644 --- a/apps/desktop-ui/messages/it.json +++ b/apps/desktop-ui/messages/it.json @@ -1923,6 +1923,8 @@ "collectionsSidebar": { "title": "Risorse API", "newCollection": "Nuova collezione", + "openFolderCollection": "Apri cartella come raccolta", + "folderCollection": "Raccolta da cartella", "tabCollections": "Collezioni", "tabHistory": "Storia", "noCollectionsTitle": "Nessuna raccolta", diff --git a/apps/desktop-ui/messages/ja.json b/apps/desktop-ui/messages/ja.json index 471da4fe..31293c68 100644 --- a/apps/desktop-ui/messages/ja.json +++ b/apps/desktop-ui/messages/ja.json @@ -2008,6 +2008,8 @@ "collectionsSidebar": { "title": "APIリソース", "newCollection": "新しいコレクション", + "openFolderCollection": "フォルダーをコレクションとして開く", + "folderCollection": "フォルダーコレクション", "tabCollections": "コレクション", "tabHistory": "履歴", "noCollectionsTitle": "コレクションなし", diff --git a/apps/desktop-ui/messages/ko.json b/apps/desktop-ui/messages/ko.json index 39581cf4..c30ff58e 100644 --- a/apps/desktop-ui/messages/ko.json +++ b/apps/desktop-ui/messages/ko.json @@ -2008,6 +2008,8 @@ "collectionsSidebar": { "title": "API 리소스", "newCollection": "새 컬렉션", + "openFolderCollection": "폴더를 컬렉션으로 열기", + "folderCollection": "폴더 컬렉션", "tabCollections": "컬렉션", "tabHistory": "기록", "noCollectionsTitle": "컬렉션 없음", diff --git a/apps/desktop-ui/messages/ms.json b/apps/desktop-ui/messages/ms.json index 15e1a393..a7b29e4a 100644 --- a/apps/desktop-ui/messages/ms.json +++ b/apps/desktop-ui/messages/ms.json @@ -1923,6 +1923,8 @@ "collectionsSidebar": { "title": "Sumber API", "newCollection": "Koleksi baharu", + "openFolderCollection": "Buka folder sebagai koleksi", + "folderCollection": "Koleksi folder", "tabCollections": "Koleksi", "tabHistory": "Sejarah", "noCollectionsTitle": "Tiada koleksi", diff --git a/apps/desktop-ui/messages/nb.json b/apps/desktop-ui/messages/nb.json index db3c3c56..24828968 100644 --- a/apps/desktop-ui/messages/nb.json +++ b/apps/desktop-ui/messages/nb.json @@ -2008,6 +2008,8 @@ "collectionsSidebar": { "title": "API-ressurser", "newCollection": "Ny samling", + "openFolderCollection": "Åpne mappe som samling", + "folderCollection": "Mappesamling", "tabCollections": "Samlinger", "tabHistory": "Historikk", "noCollectionsTitle": "Ingen samlinger", diff --git a/apps/desktop-ui/messages/nl.json b/apps/desktop-ui/messages/nl.json index 8e3b0277..d2ef1e33 100644 --- a/apps/desktop-ui/messages/nl.json +++ b/apps/desktop-ui/messages/nl.json @@ -2008,6 +2008,8 @@ "collectionsSidebar": { "title": "API-bronnen", "newCollection": "Nieuwe collectie", + "openFolderCollection": "Map openen als collectie", + "folderCollection": "Mapcollectie", "tabCollections": "Collecties", "tabHistory": "Geschiedenis", "noCollectionsTitle": "Geen collecties", diff --git a/apps/desktop-ui/messages/pl.json b/apps/desktop-ui/messages/pl.json index 4cd12d26..7d7b981c 100644 --- a/apps/desktop-ui/messages/pl.json +++ b/apps/desktop-ui/messages/pl.json @@ -2008,6 +2008,8 @@ "collectionsSidebar": { "title": "API resources", "newCollection": "New collection", + "openFolderCollection": "Otwórz folder jako kolekcję", + "folderCollection": "Kolekcja z folderu", "tabCollections": "Collections", "tabHistory": "History", "noCollectionsTitle": "No collections", diff --git a/apps/desktop-ui/messages/pt-BR.json b/apps/desktop-ui/messages/pt-BR.json index 00f8e012..8c5787cc 100644 --- a/apps/desktop-ui/messages/pt-BR.json +++ b/apps/desktop-ui/messages/pt-BR.json @@ -2008,6 +2008,8 @@ "collectionsSidebar": { "title": "Recursos API", "newCollection": "Nova coleção", + "openFolderCollection": "Abrir pasta como coleção", + "folderCollection": "Coleção de pasta", "tabCollections": "Coleções", "tabHistory": "Histórico", "noCollectionsTitle": "Sem coleções", diff --git a/apps/desktop-ui/messages/pt.json b/apps/desktop-ui/messages/pt.json index 8dc4365a..0b54e559 100644 --- a/apps/desktop-ui/messages/pt.json +++ b/apps/desktop-ui/messages/pt.json @@ -2008,6 +2008,8 @@ "collectionsSidebar": { "title": "Recursos API", "newCollection": "Nova coleção", + "openFolderCollection": "Abrir pasta como coleção", + "folderCollection": "Coleção de pasta", "tabCollections": "Coleções", "tabHistory": "Histórico", "noCollectionsTitle": "Sem coleções", diff --git a/apps/desktop-ui/messages/ru.json b/apps/desktop-ui/messages/ru.json index 82592912..b2692b3e 100644 --- a/apps/desktop-ui/messages/ru.json +++ b/apps/desktop-ui/messages/ru.json @@ -2008,6 +2008,8 @@ "collectionsSidebar": { "title": "API-ресурсы", "newCollection": "Новая коллекция", + "openFolderCollection": "Открыть папку как коллекцию", + "folderCollection": "Коллекция из папки", "tabCollections": "Коллекции", "tabHistory": "История", "noCollectionsTitle": "Нет коллекций", diff --git a/apps/desktop-ui/messages/sv.json b/apps/desktop-ui/messages/sv.json index badc30d6..79a20744 100644 --- a/apps/desktop-ui/messages/sv.json +++ b/apps/desktop-ui/messages/sv.json @@ -1923,6 +1923,8 @@ "collectionsSidebar": { "title": "API-resurser", "newCollection": "Ny samling", + "openFolderCollection": "Öppna mapp som samling", + "folderCollection": "Mappsamling", "tabCollections": "Samlingar", "tabHistory": "Historik", "noCollectionsTitle": "Inga samlingar", diff --git a/apps/desktop-ui/messages/tr.json b/apps/desktop-ui/messages/tr.json index eefca454..d5bce185 100644 --- a/apps/desktop-ui/messages/tr.json +++ b/apps/desktop-ui/messages/tr.json @@ -1923,6 +1923,8 @@ "collectionsSidebar": { "title": "API kaynakları", "newCollection": "Yeni koleksiyon", + "openFolderCollection": "Klasörü koleksiyon olarak aç", + "folderCollection": "Klasör koleksiyonu", "tabCollections": "Koleksiyonlar", "tabHistory": "Geçmiş", "noCollectionsTitle": "Koleksiyon yok", diff --git a/apps/desktop-ui/messages/uk.json b/apps/desktop-ui/messages/uk.json index 51c34f08..a992e42d 100644 --- a/apps/desktop-ui/messages/uk.json +++ b/apps/desktop-ui/messages/uk.json @@ -1923,6 +1923,8 @@ "collectionsSidebar": { "title": "API ресурси", "newCollection": "Нова колекція", + "openFolderCollection": "Відкрити папку як колекцію", + "folderCollection": "Колекція з папки", "tabCollections": "Колекції", "tabHistory": "Історія", "noCollectionsTitle": "Немає колекцій", diff --git a/apps/desktop-ui/messages/vi.json b/apps/desktop-ui/messages/vi.json index 7cc54820..32645338 100644 --- a/apps/desktop-ui/messages/vi.json +++ b/apps/desktop-ui/messages/vi.json @@ -2008,6 +2008,8 @@ "collectionsSidebar": { "title": "Tài nguyên API", "newCollection": "Bộ sưu tập mới", + "openFolderCollection": "Mở thư mục dưới dạng bộ sưu tập", + "folderCollection": "Bộ sưu tập thư mục", "tabCollections": "Bộ sưu tập", "tabHistory": "Lịch sử", "noCollectionsTitle": "Chưa có bộ sưu tập", diff --git a/apps/desktop-ui/messages/zh.json b/apps/desktop-ui/messages/zh.json index 42b1770e..efe9096e 100644 --- a/apps/desktop-ui/messages/zh.json +++ b/apps/desktop-ui/messages/zh.json @@ -2008,6 +2008,8 @@ "collectionsSidebar": { "title": "API 资源", "newCollection": "新建集合", + "openFolderCollection": "将文件夹作为集合打开", + "folderCollection": "文件夹集合", "tabCollections": "集合", "tabHistory": "历史", "noCollectionsTitle": "暂无集合", diff --git a/apps/desktop-ui/package.json b/apps/desktop-ui/package.json index 4a9b01e9..ba820e1c 100644 --- a/apps/desktop-ui/package.json +++ b/apps/desktop-ui/package.json @@ -55,6 +55,8 @@ "@tanstack/react-virtual": "^3.14.3", "@tauri-apps/api": "^2.11.1", "@tauri-apps/plugin-deep-link": "^2.4.9", + "@tauri-apps/plugin-dialog": "^2.4.0", + "@tauri-apps/plugin-fs": "^2.4.0", "@tauri-apps/plugin-opener": "^2.5.4", "@tauri-apps/plugin-process": "^2.3.1", "@tauri-apps/plugin-updater": "^2.10.1", diff --git a/apps/desktop-ui/src/components/api-client/collections/collections-sidebar.tsx b/apps/desktop-ui/src/components/api-client/collections/collections-sidebar.tsx index 686de5f2..26c03d12 100644 --- a/apps/desktop-ui/src/components/api-client/collections/collections-sidebar.tsx +++ b/apps/desktop-ui/src/components/api-client/collections/collections-sidebar.tsx @@ -6,7 +6,7 @@ import { Checkbox } from "@/components/ui/checkbox" import { ScrollArea } from "@/components/ui/scroll-area" import { Collection, CollectionRequest } from "../types" import { CollectionItem } from "./collection-item" -import { FolderPlus, Trash2, Pencil, MoreHorizontal, Search, X, FileDown, Play, Server, Link2, Globe, PanelRightClose } from "lucide-react" +import { FolderPlus, FolderGit2, Trash2, Pencil, MoreHorizontal, Search, X, FileDown, Play, Server, Link2, Globe, PanelRightClose } from "lucide-react" import { buildShareUrl } from "@/lib/share-link" import { backendFetch } from "@/lib/backend-auth" import { toast } from "sonner" @@ -58,7 +58,11 @@ export function CollectionsSidebar({ onCollapse, }: CollectionsSidebarProps) { const { collections, isLoading } = useCollectionsState() - const { addFolder: onAddFolder, deleteItem: onDelete, toggleFolder: onToggle, createCollection: onCreateCollection, renameCollection: onRenameCollection, renameFolder: onRenameFolder, patchFolder, deleteMultipleCollections: onDeleteMultiple } = useCollectionsActions() + const { addFolder: onAddFolder, deleteItem: onDelete, toggleFolder: onToggle, createCollection: onCreateCollection, renameCollection: onRenameCollection, renameFolder: onRenameFolder, patchFolder, deleteMultipleCollections: onDeleteMultiple, openFolderCollection } = useCollectionsActions() + + // isDesktop() is false during SSR — gate behind a mounted flag to avoid hydration mismatch. + const [desktop, setDesktop] = React.useState(false) + React.useEffect(() => { setDesktop(isDesktop()) }, []) const { history, isLoading: isHistoryLoading } = useHistoryState() const { clearHistory: onClearHistory, deleteHistoryItem: onDeleteHistoryItem } = useHistoryActions() const t = useTranslations("ApiClient.collectionsSidebar") @@ -215,6 +219,17 @@ export function CollectionsSidebar({ > + {desktop && ( + + )} {onCollapse && (