# git-sync: native-Obsidian vault format Статус: **дизайн (согласован с владельцем 2026-06-24), к реализации.** ## Цель Волт спейса должен быть **настоящим Obsidian-волтом**: владелец открывает папку в Obsidian (с плагином Folder Notes) и получает ровно ту же структуру страниц, не замечая разницы. Никаких служебных артефактов, которые бы выглядели чужеродно. Сторонние редакторы кладут «голые» файлы/папки — движок их **адоптирует** в страницы Docmost. Сейчас каждый `.md` несёт жирный `` блок — это уезжает. ## Формат ``` / Заметка.md # лист: чистый markdown + frontmatter id Проект/ # страница-родитель = ПАПКА Проект.md # folder-note: ТЕЛО самой страницы «Проект» Задача.md # ребёнок Подпроект/ Подпроект.md # тело «Подпроект» ... .obsidian/ # конфиг Obsidian — движок НЕ ТРОГАЕТ ``` Каждый файл страницы: ``` --- gitmost_id: 019ef6fc-2638-7ce1-9ce3-2756ce038480 --- <чистый markdown — тело страницы (wiki-ссылки, всё как в Obsidian)> ``` - **Лист** (нет детей) → `.md`. - **Родитель** (есть дети) → папка `<title>/`, его тело в `<title>/<title>.md` (folder-note по конвенции плагина LostPaul Folder Notes — заметка с именем папки внутри неё). Лист, у которого появился первый ребёнок, превращается из `<title>.md` в `<title>/<title>.md` (безопасный move по id). - **title** = имя файла (для папки — имя папки). **parentPageId** = ближайшая родительская папка (её folder-note). **spaceId** = эта репа. Всё выводимо. - **Идентичность** — `gitmost_id` (= Docmost pageId) во frontmatter. Невыводима, едет ВМЕСТЕ с файлом → переживает любой move, даже не распознанный git как rename. (Ключ namespaced `gitmost_id`, не голый `id`, чтобы не конфликтовать с пользовательскими frontmatter-полями. Имя ключа — последнее на подтверждении.) - **Коллизии имён** (2+ сиблинга с одним title): как делает сам Obsidian — добавляем натуральный суффикс ` 2`, ` 3`. id во frontmatter, так что имя файла чисто косметическое; смена суффикса — безопасный rename (идентичность по id). Никакого `.gitmost/index.json` (сайдкар отвергнут: path-keyed индекс хрупок к rename; id во frontmatter самодостаточен). Никаких `docmost:meta`/`docmost:comments` блоков (комменты и так живут инлайн-марками `<span data-comment-id>` в теле). ## Ссылки между заметками (`[[wikilinks]]`) Obsidian резолвит `[[Заметка]]` по **basename** (не по полному пути), нормализуя пробелы/`-`/`_`, с приоритетом короткого пути при неоднозначности. - В Docmost ссылки — по pageId (mention/reference node), rename переживают. - В волте — обсидиановские `[[basename]]`. - Следствие: **reparent (смена папки) ссылку НЕ ломает** (basename тот же), ломает только **retitle**. Значит переписывать `[[…]]` надо только при смене имени страницы — узкий случай. (Obsidian сам умеет «update links on rename».) - Конвертер Docmost-mention ↔ `[[wikilink]]` (обе стороны) + переписывание при retitle — **отдельная фаза** (см. план), не блокирует формат. ## PULL (Docmost → vault) 1. Прочитать дерево спейса. 2. Layout: лист→`<t>.md`, родитель→`<t>/<t>.md`, коллизии→` 2`/` 3`. 3. Записать `---\ngitmost_id: …\n---\n<тело>` (чистый markdown). 4. Переехавшие файлы — move (по id), не delete. 5. Коммит на `docmost`, merge в `main`. ## PUSH (vault → Docmost) 1. Дифф `last-pushed..main`. 2. Идентичность файла — из frontmatter `gitmost_id`. Родитель — из пути (folder-note родительской папки). 3. Классификация: - есть `gitmost_id` в дереве → update/move/rename по id (страховка 5133bb34). - нет id (новый голый файл от Obsidian) → **adopt**: create page (title=имя, parent=папка), дописать `gitmost_id` во frontmatter. - голая папка с детьми без folder-note → создать страницу-родитель, завести `<folder>/<folder>.md`. - файл пропал, а id ещё в дереве под другим путём → move. Реально пропал → delete (под delete-cap). ## Адопция (третья-сторона → Docmost) - голый `.md` без frontmatter id → create page. - голая папка с `.md` внутри без folder-note → create страницу-родитель + folder-note. - `.obsidian/`, аттачменты, dot-файлы, любые не-`.md` → **игнор** (не страницы), лежат в гите как есть, Obsidian ими владеет. Без `.gitignore`. ## Без обратной совместимости Старый `docmost:meta` формат НЕ поддерживаем (данные тестовые). Волт — кэш: на переходе `rm -rf` волты спейсов, они пересобираются из Docmost сразу в native- формате. `parsePageFile` не читает `docmost:meta`; файл без `gitmost_id` frontmatter — это голый/рукописный файл → адопция (не legacy-страница). ## Краевые случаи - Git не хранит пустые папки → «родитель без своего файла» невозможен: тело родителя — это folder-note `<t>/<t>.md`, он и держит папку (плюс дети). Childless пустая страница → просто `<t>.md`. - Конфликт folder-note `Папка/Папка.md` с ребёнком title «Папка» → ребёнку суффикс. - Переименование папки (= rename родителя) → move всего поддерева по id, без delete+create; ссылки `[[…]]` на сам родитель переписать (basename сменился). ## План фаз (каждая — юниты движка + браузерный e2e + изолированные shell-e2e) 1. ✅ Формат файла: `parsePageFile`/`serializePageFile` (frontmatter id + тело, `gitmost_id` frontmatter + тело). Юниты. Без смены поведения. (готово) 2. ✅ PULL пишет native-формат (frontmatter + folder-note layout). Волты wipe+rebuild. (2a — folder-note layout в `buildVaultLayout`; 2b — PULL пишет `serializePageFile`, `readExisting` читает frontmatter.) (готово) 3. ✅ PUSH берёт идентичность из frontmatter, title из имени файла, родителя из пути (`parentFolderFile` folder-note-aware). CREATE пишет `gitmost_id` обратно; UPDATE шлёт чистое тело (без frontmatter) на обе стороны 3-way merge. (готово) 4. Адопция голых файлов/папок (частично в фазе 3: файл без `gitmost_id` → create). ВАЖНО: тут же сохранить пользовательский frontmatter (Obsidian properties) при адопции — `parsePageFile` сейчас срезает ведущий frontmatter даже без `gitmost_id`, а write-back пишет только `gitmost_id`; нужно врезать `gitmost_id` в существующий frontmatter и сохранять остальные поля И при write-back, И при следующем pull (иначе pull перезатрёт). До этого native-формат НЕ катить на реальный Obsidian-волт с properties. 5. Чистка: выпилить старый `docmost:meta` формат-код целиком. 6. Ссылки: конвертер Docmost-mention ↔ `[[wikilink]]` + переписывание при retitle. ## Риски Смена ФОРМАТА волта на data-loss-чувствительном движке (сегодня ловили тяжёлый баг с трашем живых страниц). Каждая фаза — за инкрементом, с юнит-тестами движка И браузерным e2e (`git-sync-browser-e2e.cjs`) + изолированными shell-e2e на одноразовом спейсе. Без in-place миграций без бэкапа волта.