diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs
index 901283f..974a32a 100644
--- a/src-tauri/src/commands.rs
+++ b/src-tauri/src/commands.rs
@@ -9,7 +9,8 @@ use crate::indexer::{self, WatcherHandle};
use crate::tagger::{self, TaggerAcceleration, TaggerModelStatus, TaggerRuntimeProbe};
use crate::vector;
use serde::{Deserialize, Serialize};
-use std::path::PathBuf;
+use std::collections::HashSet;
+use std::path::{Path, PathBuf};
use tauri::{AppHandle, Emitter, Manager, State};
pub type DbState = DbPool;
@@ -215,19 +216,63 @@ pub struct GetImagesByIdsParams {
pub image_ids: Vec,
}
-#[tauri::command]
-pub async fn add_folder(
+#[derive(Serialize)]
+#[serde(tag = "status", content = "data", rename_all = "camelCase")]
+pub enum FolderAddResult {
+ Added(Folder),
+ Skipped(String),
+ Error(String),
+}
+
+enum AddOutcome {
+ Added(Folder),
+ Skipped(Folder),
+}
+
+fn paths_match(left: &str, right: &str) -> bool {
+ let left_path = PathBuf::from(left);
+ let right_path = PathBuf::from(right);
+ if let (Ok(left_canonical), Ok(right_canonical)) =
+ (left_path.canonicalize(), right_path.canonicalize())
+ {
+ return left_canonical == right_canonical;
+ }
+
+ #[cfg(windows)]
+ {
+ left.trim_end_matches(['\\', '/'])
+ .eq_ignore_ascii_case(right.trim_end_matches(['\\', '/']))
+ }
+
+ #[cfg(not(windows))]
+ {
+ left.trim_end_matches('/').eq(right.trim_end_matches('/'))
+ }
+}
+
+fn add_one_folder(
app: AppHandle,
- db: State<'_, DbState>,
- watcher: State<'_, WatcherHandle>,
+ db: DbPool,
+ watcher: WatcherHandle,
path: String,
-) -> Result {
+) -> Result {
let folder_path = PathBuf::from(&path);
if !folder_path.exists() || !folder_path.is_dir() {
return Err("Path is not a valid directory".into());
}
+ {
+ let conn = db.get().map_err(|e| e.to_string())?;
+ if let Some(folder) = db::get_folders(&conn)
+ .map_err(|e| e.to_string())?
+ .into_iter()
+ .find(|folder| paths_match(&folder.path, &path))
+ {
+ return Ok(AddOutcome::Skipped(folder));
+ }
+ }
+
// Let the webview load media from this folder via the asset protocol.
if let Err(error) = app
.asset_protocol_scope()
@@ -257,9 +302,41 @@ pub async fn add_folder(
.ok_or("Folder not found after insert")?;
watcher.add_folder(folder_path.clone(), folder_id);
- indexer::index_folder(app, db.inner().clone(), folder_id, folder_path);
+ indexer::index_folder(app, db, folder_id, folder_path);
- Ok(folder)
+ Ok(AddOutcome::Added(folder))
+}
+
+#[tauri::command]
+pub async fn add_folder(
+ app: AppHandle,
+ db: State<'_, DbState>,
+ watcher: State<'_, WatcherHandle>,
+ path: String,
+) -> Result {
+ match add_one_folder(app, db.inner().clone(), watcher.inner().clone(), path)? {
+ AddOutcome::Added(folder) => Ok(folder),
+ AddOutcome::Skipped(folder) => Ok(folder),
+ }
+}
+
+#[tauri::command]
+pub async fn add_folders(
+ app: AppHandle,
+ db: State<'_, DbState>,
+ watcher: State<'_, WatcherHandle>,
+ paths: Vec,
+) -> Result, String> {
+ Ok(paths
+ .into_iter()
+ .map(|path| {
+ match add_one_folder(app.clone(), db.inner().clone(), watcher.inner().clone(), path) {
+ Ok(AddOutcome::Added(folder)) => FolderAddResult::Added(folder),
+ Ok(AddOutcome::Skipped(folder)) => FolderAddResult::Skipped(folder.path),
+ Err(error) => FolderAddResult::Error(error),
+ }
+ })
+ .collect())
}
#[tauri::command]
@@ -268,6 +345,145 @@ pub async fn get_folders(db: State<'_, DbState>) -> Result, String>
db::get_folders(&conn).map_err(|e| e.to_string())
}
+#[derive(Serialize)]
+pub struct DirListing {
+ pub current: Option,
+ pub parent: Option,
+ pub entries: Vec,
+}
+
+#[derive(Serialize)]
+pub struct DirEntry {
+ pub name: String,
+ pub path: String,
+ pub has_children: bool,
+}
+
+fn path_to_string(path: &Path) -> String {
+ path.to_string_lossy().to_string()
+}
+
+fn directory_has_children(path: &Path) -> bool {
+ std::fs::read_dir(path)
+ .map(|mut entries| entries.any(|entry| {
+ entry
+ .ok()
+ .and_then(|entry| entry.file_type().ok())
+ .map(|file_type| file_type.is_dir())
+ .unwrap_or(false)
+ }))
+ .unwrap_or(false)
+}
+
+fn common_root_candidates() -> Vec {
+ let mut roots = Vec::new();
+
+ #[cfg(windows)]
+ {
+ for letter in b'A'..=b'Z' {
+ let drive = format!("{}:\\", letter as char);
+ let path = PathBuf::from(&drive);
+ if path.exists() {
+ roots.push(path);
+ }
+ }
+
+ if let Ok(profile) = std::env::var("USERPROFILE") {
+ let home = PathBuf::from(profile);
+ roots.push(home.clone());
+ for child in ["Pictures", "Videos", "Desktop", "Downloads"] {
+ roots.push(home.join(child));
+ }
+ }
+ }
+
+ #[cfg(not(windows))]
+ {
+ if let Ok(home) = std::env::var("HOME") {
+ roots.push(PathBuf::from(home));
+ }
+ roots.push(PathBuf::from("/"));
+ }
+
+ roots
+}
+
+fn list_roots() -> Vec {
+ let mut seen = HashSet::new();
+ let mut entries: Vec = common_root_candidates()
+ .into_iter()
+ .filter(|path| path.exists() && path.is_dir())
+ .filter_map(|path| {
+ let path_string = path_to_string(&path);
+ if !seen.insert(path_string.clone()) {
+ return None;
+ }
+ let name = path
+ .file_name()
+ .map(|name| name.to_string_lossy().to_string())
+ .filter(|name| !name.is_empty())
+ .unwrap_or_else(|| path_string.clone());
+ Some(DirEntry {
+ name,
+ path: path_string,
+ has_children: directory_has_children(&path),
+ })
+ })
+ .collect();
+ entries.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
+ entries
+}
+
+fn list_child_directories(path: &Path) -> Result, String> {
+ let mut entries = Vec::new();
+ let read_dir = std::fs::read_dir(path).map_err(|e| e.to_string())?;
+
+ for entry in read_dir.flatten() {
+ let file_name = entry.file_name();
+ let name = file_name.to_string_lossy().to_string();
+ if name.starts_with('.') {
+ continue;
+ }
+
+ let child_path = entry.path();
+ let is_dir = entry.file_type().map(|file_type| file_type.is_dir()).unwrap_or(false);
+ if !is_dir {
+ continue;
+ }
+
+ entries.push(DirEntry {
+ name,
+ path: path_to_string(&child_path),
+ has_children: directory_has_children(&child_path),
+ });
+ }
+
+ entries.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
+ Ok(entries)
+}
+
+#[tauri::command]
+pub async fn list_directories(path: Option) -> Result {
+ let trimmed = path.as_deref().map(str::trim).unwrap_or("");
+ if trimmed.is_empty() {
+ return Ok(DirListing {
+ current: None,
+ parent: None,
+ entries: list_roots(),
+ });
+ }
+
+ let current_path = PathBuf::from(trimmed);
+ let parent = current_path.parent().map(path_to_string);
+ let entries = list_child_directories(¤t_path)?;
+
+ Ok(DirListing {
+ current: Some(path_to_string(¤t_path)),
+ parent,
+ entries,
+ })
+}
+
#[derive(Deserialize)]
pub struct ReorderFoldersParams {
pub folder_ids: Vec,
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index 5f6cade..531c00a 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -128,6 +128,8 @@ pub fn run() {
})
.invoke_handler(tauri::generate_handler![
commands::add_folder,
+ commands::add_folders,
+ commands::list_directories,
commands::get_folders,
commands::reorder_folders,
commands::get_background_job_progress,
diff --git a/src/App.tsx b/src/App.tsx
index 4c97748..fa0adb3 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -10,6 +10,7 @@ import { DuplicateFinder } from "./components/DuplicateFinder";
import { Timeline } from "./components/Timeline";
import { TitleBar } from "./components/TitleBar";
import { SettingsModal } from "./components/SettingsModal";
+import { FolderPickerModal } from "./components/FolderPickerModal";
import { UpdateToast } from "./components/UpdateToast";
import { OnboardingOverlay } from "./components/onboarding/OnboardingOverlay";
import { DemoPanel } from "./components/DemoPanel";
@@ -93,6 +94,7 @@ export default function App() {
+
{import.meta.env.DEV && }
diff --git a/src/components/FolderPickerModal.tsx b/src/components/FolderPickerModal.tsx
new file mode 100644
index 0000000..c509993
--- /dev/null
+++ b/src/components/FolderPickerModal.tsx
@@ -0,0 +1,448 @@
+import { useEffect, useMemo, useRef, useState } from "react";
+import { useVirtualizer } from "@tanstack/react-virtual";
+import { DirEntry, DirListing, FolderAddResult, useGalleryStore } from "../store";
+
+function normalizePath(path: string): string {
+ return path.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
+}
+
+function folderName(path: string): string {
+ const trimmed = path.replace(/[\\/]+$/, "");
+ const parts = trimmed.split(/[\\/]+/).filter(Boolean);
+ return parts.length > 0 ? parts[parts.length - 1] : path;
+}
+
+function buildBreadcrumbs(path: string | null): { label: string; path: string | null }[] {
+ if (!path) return [{ label: "This PC / Home", path: null }];
+
+ const separator = path.includes("\\") ? "\\" : "/";
+ const normalized = path.replace(/[\\/]+$/, "");
+ const windowsDrive = normalized.match(/^[A-Za-z]:/);
+
+ if (windowsDrive) {
+ const drive = windowsDrive[0] + "\\";
+ const rest = normalized.slice(2).split(/[\\/]+/).filter(Boolean);
+ let current = drive;
+ return [
+ { label: "This PC", path: null },
+ { label: drive, path: drive },
+ ...rest.map((part) => {
+ current = current.endsWith("\\") ? `${current}${part}` : `${current}\\${part}`;
+ return { label: part, path: current };
+ }),
+ ];
+ }
+
+ const parts = normalized.split(/[\\/]+/).filter(Boolean);
+ let current = separator === "/" ? "" : "";
+ return [
+ { label: "Home", path: null },
+ ...parts.map((part) => {
+ current = `${current}/${part}`;
+ return { label: part, path: current };
+ }),
+ ];
+}
+
+function StatusLine({ results }: { results: FolderAddResult[] | null }) {
+ if (!results) return null;
+ const added = results.filter((result) => result.status === "added").length;
+ const skipped = results.filter((result) => result.status === "skipped").length;
+ const failed = results.filter((result) => result.status === "error").length;
+ return (
+
+ Added {added}, skipped {skipped}, failed {failed}.
+
+ );
+}
+
+function FolderRow({
+ entry,
+ selected,
+ alreadyAdded,
+ onToggle,
+ onNavigate,
+}: {
+ entry: DirEntry;
+ selected: boolean;
+ alreadyAdded: boolean;
+ onToggle: () => void;
+ onNavigate: () => void;
+}) {
+ return (
+
+
+
+
+
+ {alreadyAdded ? (
+
+ Added
+
+ ) : null}
+
+
+
+ );
+}
+
+function StagedFoldersPanel({
+ stagedPaths,
+ onRemove,
+ onClear,
+}: {
+ stagedPaths: string[];
+ onRemove: (path: string) => void;
+ onClear: () => void;
+}) {
+ return (
+
+ );
+}
+
+export function FolderPickerModal() {
+ const folderPickerOpen = useGalleryStore((state) => state.folderPickerOpen);
+ const setFolderPickerOpen = useGalleryStore((state) => state.setFolderPickerOpen);
+ const folders = useGalleryStore((state) => state.folders);
+ const listDirectories = useGalleryStore((state) => state.listDirectories);
+ const addFolders = useGalleryStore((state) => state.addFolders);
+
+ const [listing, setListing] = useState(null);
+ const [currentPath, setCurrentPath] = useState(null);
+ const [stagedPaths, setStagedPaths] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [adding, setAdding] = useState(false);
+ const [error, setError] = useState(null);
+ const [results, setResults] = useState(null);
+ const scrollRef = useRef(null);
+
+ const libraryPaths = useMemo(() => new Set(folders.map((folder) => normalizePath(folder.path))), [folders]);
+ const stagedSet = useMemo(() => new Set(stagedPaths.map(normalizePath)), [stagedPaths]);
+ const breadcrumbs = useMemo(() => buildBreadcrumbs(listing?.current ?? null), [listing?.current]);
+
+ const virtualizer = useVirtualizer({
+ count: listing?.entries.length ?? 0,
+ getScrollElement: () => scrollRef.current,
+ estimateSize: () => 48,
+ overscan: 8,
+ });
+
+ useEffect(() => {
+ if (!folderPickerOpen) return;
+ let cancelled = false;
+ setLoading(true);
+ setError(null);
+ void listDirectories(currentPath)
+ .then((nextListing) => {
+ if (cancelled) return;
+ setListing(nextListing);
+ scrollRef.current?.scrollTo({ top: 0, left: 0 });
+ })
+ .catch((loadError) => {
+ if (cancelled) return;
+ setListing({ current: currentPath, parent: null, entries: [] });
+ setError(loadError instanceof Error ? loadError.message : String(loadError));
+ })
+ .finally(() => {
+ if (!cancelled) setLoading(false);
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, [currentPath, folderPickerOpen, listDirectories]);
+
+ useEffect(() => {
+ if (!folderPickerOpen) return;
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (event.key === "Escape") setFolderPickerOpen(false);
+ };
+ window.addEventListener("keydown", handleKeyDown);
+ return () => window.removeEventListener("keydown", handleKeyDown);
+ }, [folderPickerOpen, setFolderPickerOpen]);
+
+ useEffect(() => {
+ if (folderPickerOpen) return;
+ setCurrentPath(null);
+ setListing(null);
+ setStagedPaths([]);
+ setError(null);
+ setResults(null);
+ setAdding(false);
+ }, [folderPickerOpen]);
+
+ if (!folderPickerOpen) return null;
+
+ const entries = listing?.entries ?? [];
+
+ const togglePath = (path: string) => {
+ const normalized = normalizePath(path);
+ if (libraryPaths.has(normalized)) return;
+ setResults(null);
+ setStagedPaths((current) => {
+ const exists = current.some((staged) => normalizePath(staged) === normalized);
+ return exists ? current.filter((staged) => normalizePath(staged) !== normalized) : [...current, path];
+ });
+ };
+
+ const removeStagedPath = (path: string) => {
+ const normalized = normalizePath(path);
+ setResults(null);
+ setStagedPaths((current) => current.filter((staged) => normalizePath(staged) !== normalized));
+ };
+
+ const clearStagedPaths = () => {
+ setResults(null);
+ setStagedPaths([]);
+ };
+
+ const confirmAdd = async () => {
+ if (stagedPaths.length === 0 || adding) return;
+ setAdding(true);
+ setError(null);
+ try {
+ const addResults = await addFolders(stagedPaths);
+ const failed = addResults.filter((result) => result.status === "error");
+ setResults(addResults);
+ if (failed.length > 0) {
+ setError(failed.map((failure) => failure.data).join("; "));
+ return;
+ }
+ setFolderPickerOpen(false);
+ } catch (addError) {
+ setError(addError instanceof Error ? addError.message : String(addError));
+ } finally {
+ setAdding(false);
+ }
+ };
+
+ return (
+ setFolderPickerOpen(false)}
+ >
+
event.stopPropagation()}
+ >
+
+
+
+
Add media folders
+
Choose folders from any location, then add them together.
+
+
+
+
+
+
+
+
+
+
+
+
+ {error ? (
+
+ {error}
+
+ ) : null}
+
+
+ {loading ? (
+
Loading folders...
+ ) : entries.length === 0 ? (
+
No folders found here.
+ ) : (
+
+ {virtualizer.getVirtualItems().map((virtualItem) => {
+ const entry = entries[virtualItem.index];
+ const normalized = normalizePath(entry.path);
+ return (
+
+ togglePath(entry.path)}
+ onNavigate={() => setCurrentPath(entry.path)}
+ />
+
+ );
+ })}
+
+ )}
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/MenuBar.tsx b/src/components/MenuBar.tsx
index 7d29bf6..a48dbac 100644
--- a/src/components/MenuBar.tsx
+++ b/src/components/MenuBar.tsx
@@ -1,5 +1,4 @@
import { useEffect, useRef, useState } from "react";
-import { open } from "@tauri-apps/plugin-dialog";
import { MediaFilter, ZoomPreset, useGalleryStore } from "../store";
type MenuKey = "library" | "view" | "filter";
@@ -72,7 +71,7 @@ const FILTER_OPTIONS: { value: MediaFilter; label: string }[] = [
export function MenuBar() {
const [openMenu, setOpenMenu] = useState(null);
const rootRef = useRef(null);
- const addFolder = useGalleryStore((state) => state.addFolder);
+ const setFolderPickerOpen = useGalleryStore((state) => state.setFolderPickerOpen);
const reindexFolder = useGalleryStore((state) => state.reindexFolder);
const selectedFolderId = useGalleryStore((state) => state.selectedFolderId);
const zoomPreset = useGalleryStore((state) => state.zoomPreset);
@@ -93,11 +92,8 @@ export function MenuBar() {
return () => window.removeEventListener("pointerdown", handlePointerDown);
}, []);
- const handleAddFolder = async () => {
- const selected = await open({ directory: true, multiple: false, title: "Select Media Folder" });
- if (selected && typeof selected === "string") {
- await addFolder(selected);
- }
+ const handleAddFolder = () => {
+ setFolderPickerOpen(true);
setOpenMenu(null);
};
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
index cc51fbd..a3da94e 100644
--- a/src/components/Sidebar.tsx
+++ b/src/components/Sidebar.tsx
@@ -342,7 +342,7 @@ function FolderItem({
export function Sidebar() {
const folders = useGalleryStore((state) => state.folders);
const selectedFolderId = useGalleryStore((state) => state.selectedFolderId);
- const addFolder = useGalleryStore((state) => state.addFolder);
+ const setFolderPickerOpen = useGalleryStore((state) => state.setFolderPickerOpen);
const indexingProgress = useGalleryStore((state) => state.indexingProgress);
const selectFolder = useGalleryStore((state) => state.selectFolder);
const activeView = useGalleryStore((state) => state.activeView);
@@ -459,11 +459,8 @@ export function Sidebar() {
}, 400);
};
- const handleAddFolder = async () => {
- const selected = await open({ directory: true, multiple: false, title: "Select Media Folder" });
- if (selected && typeof selected === "string") {
- await addFolder(selected);
- }
+ const handleAddFolder = () => {
+ setFolderPickerOpen(true);
};
return (
diff --git a/src/components/onboarding/StepAddLibrary.tsx b/src/components/onboarding/StepAddLibrary.tsx
index 6e0cec1..0b3411e 100644
--- a/src/components/onboarding/StepAddLibrary.tsx
+++ b/src/components/onboarding/StepAddLibrary.tsx
@@ -1,26 +1,12 @@
-import { useState } from "react";
-import { open } from "@tauri-apps/plugin-dialog";
import { useGalleryStore } from "../../store";
import { FakeTile } from "./fakes";
export function StepAddLibrary() {
const folders = useGalleryStore((s) => s.folders);
- const addFolder = useGalleryStore((s) => s.addFolder);
- const [adding, setAdding] = useState(false);
- const [addError, setAddError] = useState(null);
+ const setFolderPickerOpen = useGalleryStore((s) => s.setFolderPickerOpen);
- const handlePick = async () => {
- setAddError(null);
- const selected = await open({ directory: true, multiple: false, title: "Select Media Folder" });
- if (typeof selected !== "string") return;
- setAdding(true);
- try {
- await addFolder(selected);
- } catch (error) {
- setAddError(error instanceof Error ? error.message : String(error));
- } finally {
- setAdding(false);
- }
+ const handlePick = () => {
+ setFolderPickerOpen(true);
};
return (
@@ -49,14 +35,12 @@ export function StepAddLibrary() {
>
)}
- {addError ? {addError}
: null}
diff --git a/src/store.ts b/src/store.ts
index 880bb30..ba4f35d 100644
--- a/src/store.ts
+++ b/src/store.ts
@@ -25,6 +25,23 @@ export interface Folder {
sort_order: number;
}
+export interface DirEntry {
+ name: string;
+ path: string;
+ has_children: boolean;
+}
+
+export interface DirListing {
+ current: string | null;
+ parent: string | null;
+ entries: DirEntry[];
+}
+
+export type FolderAddResult =
+ | { status: "added"; data: Folder }
+ | { status: "skipped"; data: string }
+ | { status: "error"; data: string };
+
export type MediaKind = "image" | "video";
export type MediaFilter = "all" | MediaKind;
export type ZoomPreset = "compact" | "comfortable" | "detail";
@@ -343,6 +360,7 @@ interface GalleryState {
captionDetail: CaptionDetail;
aiCaptionsEnabled: boolean;
settingsOpen: boolean;
+ folderPickerOpen: boolean;
taggingQueueScope: TaggingQueueScope;
taggingQueueFolderIds: number[];
mutedFolderIds: number[];
@@ -390,6 +408,8 @@ interface GalleryState {
loadFolders: () => Promise;
loadBackgroundJobProgress: () => Promise;
addFolder: (path: string) => Promise;
+ addFolders: (paths: string[]) => Promise;
+ listDirectories: (path: string | null) => Promise;
removeFolder: (folderId: number) => Promise;
reindexFolder: (folderId: number) => Promise;
renameFolder: (folderId: number, newName: string) => Promise;
@@ -439,6 +459,7 @@ interface GalleryState {
setCaptionDetail: (detail: CaptionDetail) => Promise;
setAiCaptionsEnabled: (enabled: boolean) => void;
setSettingsOpen: (open: boolean) => void;
+ setFolderPickerOpen: (open: boolean) => void;
loadTaggingQueueScope: () => Promise;
setTaggingQueueScope: (scope: TaggingQueueScope) => void;
loadTaggingQueueFolderIds: () => Promise;
@@ -773,6 +794,7 @@ export const useGalleryStore = create((set, get) => ({
captionDetail: "paragraph",
aiCaptionsEnabled: initialAiCaptionsEnabled(),
settingsOpen: false,
+ folderPickerOpen: false,
taggingQueueScope: "all",
taggingQueueFolderIds: [],
mutedFolderIds: [],
@@ -848,6 +870,16 @@ export const useGalleryStore = create((set, get) => ({
await loadBackgroundJobProgress();
},
+ addFolders: async (paths) => {
+ const { loadFolders, loadBackgroundJobProgress } = get();
+ const results = await invoke("add_folders", { paths });
+ await loadFolders();
+ await loadBackgroundJobProgress();
+ return results;
+ },
+
+ listDirectories: (path) => invoke("list_directories", { path }),
+
removeFolder: async (folderId) => {
const { selectedFolderId, loadFolders, loadImages, loadBackgroundJobProgress } = get();
// Optimistically drop it from the sidebar for instant feedback (the backend
@@ -1580,6 +1612,7 @@ export const useGalleryStore = create((set, get) => ({
},
setSettingsOpen: (settingsOpen) => set({ settingsOpen }),
+ setFolderPickerOpen: (folderPickerOpen) => set({ folderPickerOpen }),
loadTaggingQueueScope: async () => {
try {