134 lines
9.1 KiB
Markdown
134 lines
9.1 KiB
Markdown
# git-sync: native-Obsidian vault format
|
|
|
|
Статус: **дизайн (согласован с владельцем 2026-06-24), к реализации.**
|
|
|
|
## Цель
|
|
|
|
Волт спейса должен быть **настоящим Obsidian-волтом**: владелец открывает папку в
|
|
Obsidian (с плагином Folder Notes) и получает ровно ту же структуру страниц, не
|
|
замечая разницы. Никаких служебных артефактов, которые бы выглядели чужеродно.
|
|
Сторонние редакторы кладут «голые» файлы/папки — движок их **адоптирует** в
|
|
страницы Docmost.
|
|
|
|
Сейчас каждый `.md` несёт жирный `<!-- docmost:meta {…} -->` блок — это уезжает.
|
|
|
|
## Формат
|
|
|
|
```
|
|
<Space-vault>/
|
|
Заметка.md # лист: чистый markdown + frontmatter id
|
|
Проект/ # страница-родитель = ПАПКА
|
|
Проект.md # folder-note: ТЕЛО самой страницы «Проект»
|
|
Задача.md # ребёнок
|
|
Подпроект/
|
|
Подпроект.md # тело «Подпроект»
|
|
...
|
|
.obsidian/ # конфиг Obsidian — движок НЕ ТРОГАЕТ
|
|
```
|
|
|
|
Каждый файл страницы:
|
|
```
|
|
---
|
|
gitmost_id: 019ef6fc-2638-7ce1-9ce3-2756ce038480
|
|
---
|
|
<чистый markdown — тело страницы (wiki-ссылки, всё как в Obsidian)>
|
|
```
|
|
|
|
- **Лист** (нет детей) → `<title>.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).
|
|
5. Чистка: выпилить старый `docmost:meta` формат-код целиком.
|
|
6. Ссылки: конвертер Docmost-mention ↔ `[[wikilink]]` + переписывание при retitle.
|
|
|
|
## Риски
|
|
|
|
Смена ФОРМАТА волта на data-loss-чувствительном движке (сегодня ловили тяжёлый баг
|
|
с трашем живых страниц). Каждая фаза — за инкрементом, с юнит-тестами движка И
|
|
браузерным e2e (`git-sync-browser-e2e.cjs`) + изолированными shell-e2e на
|
|
одноразовом спейсе. Без in-place миграций без бэкапа волта.
|