fix(sidecars): keep mirrored sidecars when a model root moves

Centralized sidecars were addressed by a hash of the model root's absolute
path, so moving or renaming a root produced a new mirror directory. The
scanner then found no sidecar there, rebuilt default metadata, and silently
lost favorites, notes, tags and usage tips for every model under that root,
leaving the old metadata orphaned on disk.

Mirrors are now addressed by a root identity pinned in
<sidecar_root>/.lm-sidecar-roots.json. The identity starts as the existing
deterministic <basename>-<path digest> -- so pre-existing mirrors keep
resolving even if the map is lost, and a relocated sidecar root keeps its
names -- and is re-anchored to the root's new path when it moves, matched by
basename and recorded sample directories. Ambiguous matches are never
guessed: the mirror is left untouched and reported.

Also:
- drop the <library> path segment (single-library direction); a legacy
  library prefix is only recognised while adopting an existing mirror, which
  also keeps the unreleased centralized layout usable
- surface stranded mirrors in Doctor and in the log instead of silently
  rebuilding sidecars
- keep the default alongside mode untouched: the identity map is loaded and
  reconciled lazily, only while centralized storage is in use
This commit is contained in:
Will Miao
2026-09-28 21:11:38 +08:00
parent 0a262cbe0c
commit c3a9350155
18 changed files with 1223 additions and 61 deletions
+3
View File
@@ -2769,6 +2769,9 @@
"filename_conflicts": {
"title": "Konflikte durch doppelte Dateinamen"
},
"sidecar_mirror_orphans": {
"title": "[TODO: Translate] Centralized Sidecars"
},
"ui_version": {
"title": "UI-Version"
}
+3
View File
@@ -2769,6 +2769,9 @@
"filename_conflicts": {
"title": "Duplicate Filename Conflicts"
},
"sidecar_mirror_orphans": {
"title": "Centralized Sidecars"
},
"ui_version": {
"title": "UI Version"
}
+3
View File
@@ -2769,6 +2769,9 @@
"filename_conflicts": {
"title": "Conflictos de nombres de archivo duplicados"
},
"sidecar_mirror_orphans": {
"title": "[TODO: Translate] Centralized Sidecars"
},
"ui_version": {
"title": "Versión de la interfaz"
}
+3
View File
@@ -2769,6 +2769,9 @@
"filename_conflicts": {
"title": "Conflits de noms de fichiers en double"
},
"sidecar_mirror_orphans": {
"title": "[TODO: Translate] Centralized Sidecars"
},
"ui_version": {
"title": "Version de l'interface"
}
+3
View File
@@ -2769,6 +2769,9 @@
"filename_conflicts": {
"title": "התנגשויות שמות קבצים כפולים"
},
"sidecar_mirror_orphans": {
"title": "[TODO: Translate] Centralized Sidecars"
},
"ui_version": {
"title": "גרסת הממשק"
}
+3
View File
@@ -2769,6 +2769,9 @@
"filename_conflicts": {
"title": "ファイル名重複競合"
},
"sidecar_mirror_orphans": {
"title": "[TODO: Translate] Centralized Sidecars"
},
"ui_version": {
"title": "UI バージョン"
}
+3
View File
@@ -2769,6 +2769,9 @@
"filename_conflicts": {
"title": "파일명 중복 충돌"
},
"sidecar_mirror_orphans": {
"title": "[TODO: Translate] Centralized Sidecars"
},
"ui_version": {
"title": "UI 버전"
}
+3
View File
@@ -2769,6 +2769,9 @@
"filename_conflicts": {
"title": "Конфликты дублирующихся имён файлов"
},
"sidecar_mirror_orphans": {
"title": "[TODO: Translate] Centralized Sidecars"
},
"ui_version": {
"title": "Версия интерфейса"
}
+3
View File
@@ -2769,6 +2769,9 @@
"filename_conflicts": {
"title": "文件名重复冲突"
},
"sidecar_mirror_orphans": {
"title": "[TODO: Translate] Centralized Sidecars"
},
"ui_version": {
"title": "UI 版本"
}
+3
View File
@@ -2769,6 +2769,9 @@
"filename_conflicts": {
"title": "檔案名稱重複衝突"
},
"sidecar_mirror_orphans": {
"title": "[TODO: Translate] Centralized Sidecars"
},
"ui_version": {
"title": "UI 版本"
}