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
Codex review on #1124:
- P1: mirror layout root component is now <basename>-<roothash>
(sha256 of the normalized root path), so two roots sharing a basename
no longer map to the same mirror directory and overwrite each other's
sidecars
- P1: changing sidecar_storage_path while centralized no longer strands
assets in the old root — new relocate_root migration direction moves
the whole mirror tree, rewrites preview_url prefixes inside sidecars,
reconciles scanner caches, and prunes the emptied old tree; the
settings UI detects the path change and offers the relocation
- P2: migration enumerates the same preview candidates as
find_preview_file — case-insensitive variants (model.WEBP) and the
legacy .example.0.jpeg suffix — instead of exact lowercase
PREVIEW_EXTENSIONS only
- P2: _rollback_model_staging restores staged files with the
EXDEV-tolerant mover, so a failed undoable-delete staging no longer
strands a cross-filesystem centralized sidecar copy
Tests: same-basename root injectivity, mixed-case/example preview
migration, relocate_root happy path + guards + route 400, frontend
relocation prompt flow. Verified end-to-end in a sandboxed standalone
server: uppercase/legacy previews migrate, root relocation moves the
tree and the list API serves the new locations immediately without a
rescan.
Add an opt-in 'centralized' sidecar storage mode alongside the default
'alongside' layout. In centralized mode, .metadata.json sidecars and
preview assets live under a configurable root (sidecar_storage_path,
default <settings_dir>/sidecars), mirroring the library-relative
directory structure: <root>/<library>/<root_basename>/<rel_dir>/.
Backend:
- settings: sidecar_storage_mode / sidecar_storage_path with validation;
changing either refreshes the preview allowlist
- config: centralized root added to preview-serving allowlist
- lifecycle: delete / move / rename / folder-rename / folder-delete and
undoable-delete staging all operate on the mirror tree in centralized
mode (model files themselves never move); EXDEV-tolerant cross-
filesystem moves
- scanners: pending-hash filesystem scan walks the mirror tree in
centralized mode; preview discovery reads from the sidecar dir;
.civitai.info stays co-located in both modes
- migration: SidecarMigrationUseCase moves sidecars+previews between
layouts both directions (keep-newer conflict resolution, preview_url
rewriting, WebSocket progress), exposed as POST+GET
/api/lm/sidecars/migrate with a mode guard (force=true for the
settings-first flow)
Frontend:
- settings modal: sidecar storage section (mode select + path input with
browse/validation), mode-change confirmation offering immediate
migration (force=true), and a 'Migrate Sidecars Now' action
- i18n keys synced to all locales ([TODO: Translate] placeholders)
Docs: metadata-json-schema.md gains a storage-location section;
AGENTS.md records the sidecar_paths helper convention.