fix: heap convert dialog supports nested subfolders + visiblePhotoIds loop guard

- HeapConvertDialog: switch the target picker from sourceFolders.list
  (top-level source roots only) to useFolderTreeQuery, flattened
  depth-first into a list with depth info. Each option is indented
  with non-breaking spaces so nested subfolders read as a tree in
  the native dropdown. Backend already accepts any Folder id, so no
  server change needed.
- photoStore.setVisiblePhotoIds: short-circuit when the new id list
  matches the existing one element-for-element. Avoids feedback loops
  if a publisher fires from an effect on a render where the contents
  haven't actually changed (which was triggering React error #185).
- Timeline: pull setVisiblePhotoIds via a focused selector instead of
  the wholesale destructure so the publisher subscription doesn't
  re-render Timeline on unrelated photo store changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-08 21:17:32 +02:00
parent a56062d353
commit 5a0f9ff592
3 changed files with 58 additions and 14 deletions

View File

@@ -81,7 +81,23 @@ export const usePhotoStore = create<PhotoStore>((set) => ({
setViewMode: (mode) => set({ viewMode: mode }),
setVisiblePhotoIds: (visiblePhotoIds) => set({ visiblePhotoIds }),
// No-op when the content is identical so callers can fire from an
// effect without risking a re-render loop.
setVisiblePhotoIds: (visiblePhotoIds) =>
set((s) => {
const prev = s.visiblePhotoIds
if (prev.length === visiblePhotoIds.length) {
let same = true
for (let i = 0; i < prev.length; i++) {
if (prev[i] !== visiblePhotoIds[i]) {
same = false
break
}
}
if (same) return s
}
return { visiblePhotoIds }
}),
openPreview: (id) => set({ viewMode: 'preview', activePhotoId: id }),