diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8efd20c..6cc4c7a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -34,7 +34,8 @@ aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
- **Tag management** — Explore → Tag Cloud gains a Manage mode with a flat tag
list where you can rename a tag, merge it into another (rename it to an
existing tag's name), or delete it from every image. Changes apply across the
- whole library.
+ whole library. Settings → AI Workspace also has an "Open tag manager" button
+ that jumps straight into it.
- **Camera info in the lightbox** — the image info panel now shows EXIF details
(camera, lens, aperture, shutter, ISO, focal length) and, when a photo is
geotagged, its GPS coordinates as a link that opens the location in your
diff --git a/src/components/SettingsModal.tsx b/src/components/SettingsModal.tsx
index c9a2a41..92e43e1 100644
--- a/src/components/SettingsModal.tsx
+++ b/src/components/SettingsModal.tsx
@@ -247,6 +247,7 @@ export function SettingsModal() {
const openOnboarding = useGalleryStore((state) => state.openOnboarding);
const theme = useGalleryStore((state) => state.theme);
const setTheme = useGalleryStore((state) => state.setTheme);
+ const openTagManager = useGalleryStore((state) => state.openTagManager);
const lightboxAutoplay = useGalleryStore((state) => state.lightboxAutoplay);
const setLightboxAutoplay = useGalleryStore((state) => state.setLightboxAutoplay);
const lightboxAutoMute = useGalleryStore((state) => state.lightboxAutoMute);
@@ -686,6 +687,17 @@ export function SettingsModal() {
{taggerQueueStatus ?
{taggerQueueStatus}
: null}
+
+
+
+
+
+
) : (
diff --git a/src/components/TagCloud.tsx b/src/components/TagCloud.tsx
index c38026a..380ae5d 100644
--- a/src/components/TagCloud.tsx
+++ b/src/components/TagCloud.tsx
@@ -983,7 +983,9 @@ export function TagCloud() {
const renameTag = useGalleryStore((state) => state.renameTag);
const deleteTag = useGalleryStore((state) => state.deleteTag);
const selectedFolderId = useGalleryStore((state) => state.selectedFolderId);
- const [manageTags, setManageTags] = useState(false);
+ // Manage mode lives in the store so it can be opened from elsewhere (Settings).
+ const manageTags = useGalleryStore((state) => state.tagManagerOpen);
+ const setManageTags = useGalleryStore((state) => state.setTagManagerOpen);
const handleDeleteTag = async (tag: string) => { await deleteTag(tag); };
useEffect(() => {
@@ -1028,7 +1030,7 @@ export function TagCloud() {
? "border-white/15 bg-white/10 text-white"
: "border-white/8 bg-white/[0.03] text-gray-500 hover:text-gray-300"
}`}
- onClick={() => setManageTags((v) => !v)}
+ onClick={() => setManageTags(!manageTags)}
>
{manageTags ? "Done" : "Manage"}
diff --git a/src/store.ts b/src/store.ts
index f8e0d52..6a2fa65 100644
--- a/src/store.ts
+++ b/src/store.ts
@@ -375,20 +375,21 @@ interface GalleryState {
galleryScrollResetKey: number;
activeView: ActiveView;
exploreMode: ExploreMode;
+ tagManagerOpen: boolean;
tagCloudEntries: TagCloudEntry[];
tagCloudLoading: boolean;
tagCloudFolderId: number | null | undefined; // undefined = never loaded
exploreTagEntries: ExploreTagEntry[];
exploreTagLoading: boolean;
- exploreTagsFolderId: number | null | undefined;
// Cache-freshness key: the folder the loaded tags belong to. Set to undefined
// by content mutations (tag add/remove/rename/delete) to mark the cache dirty
// and force the next load to refetch.
- relatedTagsByKey: Record;
+ exploreTagsFolderId: number | null | undefined;
// The folder whose tags are actually on screen. Kept separate from the dirty
// marker above so a same-folder invalidation isn't mistaken for a folder switch
// (which would wipe the visible list and remount manager UI mid-refresh).
exploreTagsShownFolderId: number | null | undefined;
+ relatedTagsByKey: Record;
indexingProgress: Record;
mediaJobProgress: Record;
cacheDir: string;
@@ -494,6 +495,8 @@ interface GalleryState {
closeImage: () => void;
setView: (view: ActiveView) => void;
setExploreMode: (mode: ExploreMode) => void;
+ setTagManagerOpen: (open: boolean) => void;
+ openTagManager: () => void;
loadTagCloud: (options?: { force?: boolean }) => Promise;
loadExploreTags: (options?: { force?: boolean }) => Promise;
loadRelatedTags: (tag: string) => Promise;
@@ -874,17 +877,18 @@ export const useGalleryStore = create((set, get) => ({
galleryScrollResetKey: 0,
activeView: "gallery",
exploreMode: "visual",
+ tagManagerOpen: false,
tagCloudEntries: [],
tagCloudLoading: false,
tagCloudFolderId: undefined,
exploreTagEntries: [],
exploreTagLoading: false,
exploreTagsFolderId: undefined,
+ exploreTagsShownFolderId: undefined,
relatedTagsByKey: {},
indexingProgress: {},
mediaJobProgress: {},
cacheDir: "",
- exploreTagsShownFolderId: undefined,
captionModelStatus: null,
captionModelPreparing: false,
captionModelError: null,
@@ -1399,10 +1403,27 @@ export const useGalleryStore = create((set, get) => ({
return;
}
}
- set({ activeView, similarSourceAlbumId: null, similarScope: similarScopeReset });
+ // Entering Explore normally always starts in browse mode; openTagManager() is
+ // the only path that re-opens manage mode (it runs this then sets the flag).
+ set({
+ activeView,
+ similarSourceAlbumId: null,
+ similarScope: similarScopeReset,
+ ...(activeView === "explore" ? { tagManagerOpen: false } : {}),
+ });
},
- setExploreMode: (exploreMode) => set({ exploreMode }),
+ setExploreMode: (exploreMode) =>
+ // Manage mode only exists for the tag view; drop it when switching to visual
+ // clusters so re-entering the tag view starts in the normal browse state.
+ set(exploreMode === "visual" ? { exploreMode, tagManagerOpen: false } : { exploreMode }),
+ setTagManagerOpen: (tagManagerOpen) => set({ tagManagerOpen }),
+ // Jump straight to the tag manager from anywhere (e.g. the Settings panel):
+ // switch to Explore, select the tag view, open manage mode, and close Settings.
+ openTagManager: () => {
+ get().setView("explore");
+ set({ exploreMode: "tags", tagManagerOpen: true, settingsOpen: false });
+ },
loadTagCloud: async (options) => {
const { selectedFolderId, tagCloudFolderId, tagCloudLoading } = get();