Self-review of phase 3 caught a data-corruption regression: nativeMeta always supplies the run's spaceId, so the planner's 'create-without-spaceId' skip — which had doubled as the only filter for non-page files — went dead. An ADDED .obsidian/*.json, attachment, or dotfile (committed to the vault, no .gitignore) would then be classified as a CREATE: a junk Docmost page, plus a gitmost_id frontmatter written INTO the file, corrupting it. Fix: isPageFile(path) — a .md file with NO dot-segment anywhere — and filter the diff to page files at the very top of computePushActions, BEFORE any classification, so non-page A/M/D/R are ignored (design §Адопция). 2 unit tests pin it (.obsidian/json, attachment, dotfile, dot-segment, .md dotfile all ignored; real pages still created). 614 engine tests green. Also: refreshed stale docmost:meta comments to gitmost_id (review SUGGESTION), and documented the deferred adoption frontmatter-preservation gap (review WARNING) in page-file.ts + the design doc (do NOT roll native onto a real vault with Obsidian properties until phase 4 round-trips them). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
82 lines
3.4 KiB
TypeScript
82 lines
3.4 KiB
TypeScript
/**
|
|
* The native-Obsidian page-file format (design: docs/backlog/git-sync-thin-meta.md).
|
|
* A page file is CLEAN markdown with a minimal YAML frontmatter carrying ONLY the
|
|
* page's durable identity:
|
|
*
|
|
* ---
|
|
* gitmost_id: 019ef6fc-2638-7ce1-9ce3-2756ce038480
|
|
* ---
|
|
* <clean markdown body>
|
|
*
|
|
* Everything else is derived (title = filename, parentPageId = enclosing folder,
|
|
* spaceId = the vault, updatedAt = git). `gitmost_id` (a Docmost pageId) is the
|
|
* only non-derivable bit and travels WITH the file so identity survives any move,
|
|
* even one git's rename detection misses. Third-party editors (Obsidian, …) see
|
|
* clean markdown; the frontmatter is hidden in their preview.
|
|
*
|
|
* No backward-compat with the old `docmost:meta` format: vaults are a cache, wiped
|
|
* and rebuilt native. A file WITHOUT a `gitmost_id` frontmatter is an un-tracked
|
|
* (e.g. hand-written) file -> the caller ADOPTS it (creates a page, writes the id).
|
|
*/
|
|
|
|
/**
|
|
* The frontmatter key carrying the Docmost pageId. NAMESPACED (not a bare `id`)
|
|
* so it never collides with a user's own frontmatter fields.
|
|
*/
|
|
export const ID_KEY = "gitmost_id";
|
|
|
|
/** Leading YAML frontmatter block: `---\n…\n---` at the very start of the file. */
|
|
const FRONTMATTER_RE = /^?---\n([\s\S]*?)\n---\n?/;
|
|
|
|
/** The top-level `<ID_KEY>: <value>` line inside the frontmatter (quotes optional). */
|
|
function readIdFromYaml(yaml: string): string | null {
|
|
const re = new RegExp(`^${ID_KEY}:\\s*(.+?)\\s*$`);
|
|
for (const line of yaml.split("\n")) {
|
|
const m = line.match(re);
|
|
if (m) {
|
|
const v = m[1].trim().replace(/^["']|["']$/g, "");
|
|
return v === "" ? null : v;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Parse a page file into its identity (`id`) and clean markdown `body`. Tolerant:
|
|
* a file with no frontmatter (a hand-written third-party file) returns `id: null`
|
|
* and the whole text as the body — the caller then ADOPTS it (creates a page,
|
|
* writes the id back).
|
|
*
|
|
* KNOWN LIMITATION (phase 4 — adoption, see docs/backlog/git-sync-thin-meta.md):
|
|
* a leading frontmatter block is stripped from `body` even when it carries NO
|
|
* `gitmost_id` but DOES carry the user's own Obsidian properties (`tags:` etc.).
|
|
* On adoption those fields are not yet round-tripped — `serializePageFile`
|
|
* write-back persists only `gitmost_id`. Preserving arbitrary user frontmatter
|
|
* across the Docmost round-trip (BOTH adoption write-back AND the next pull's
|
|
* re-serialize) is deferred to the adoption phase; until then, do NOT roll the
|
|
* native format onto a real Obsidian vault whose notes carry properties.
|
|
*/
|
|
export function parsePageFile(full: string): {
|
|
id: string | null;
|
|
body: string;
|
|
} {
|
|
const text = (full ?? "").replace(/\r\n/g, "\n");
|
|
|
|
// Native format: a `gitmost_id` YAML frontmatter. Anything else (no frontmatter,
|
|
// or frontmatter without the key) is an un-tracked file -> adopt.
|
|
const fm = text.match(FRONTMATTER_RE);
|
|
if (fm) {
|
|
return { id: readIdFromYaml(fm[1]), body: text.slice(fm[0].length).trim() };
|
|
}
|
|
return { id: null, body: text.trim() };
|
|
}
|
|
|
|
/**
|
|
* Serialize a page into the thin format: `id` frontmatter + a blank line + the
|
|
* clean body + a trailing newline. Deterministic so an unchanged page re-syncs to
|
|
* byte-identical output (no churn — the loop-guard relies on it).
|
|
*/
|
|
export function serializePageFile(id: string, body: string): string {
|
|
return `---\n${ID_KEY}: ${id}\n---\n\n${body.trim()}\n`;
|
|
}
|