From 84f93ba56da3c466e3805070185ff35db3d081ed Mon Sep 17 00:00:00 2001 From: ZDDC Date: Mon, 8 Jun 2026 08:54:59 -0500 Subject: [PATCH] fix(browse): don't inject front matter on open; no conflict modal on rename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two fixes to the markdown editor's identity handling: - Sync-on-open now only RECONCILES front-matter identity keys the author already wrote (correcting a stale title/revision/…); it never ADDS them. A blank or new file opens blank instead of getting an auto-generated title at the top. The converter derives identity from the filename regardless, so an empty front matter needs nothing baked in. (Cancel/revert likewise only touches existing keys.) - The "Rename file & reopen" flow force-writes the buffer (no If-Match) instead of routing through save(), which raised the conflict-resolution modal on a (spurious) 412. The rename is a deliberate user action and the identity edit that triggered it is consumed by the new filename, so we commit the buffer rather than interrupting with a merge dialog. Co-Authored-By: Claude Opus 4.8 (1M context) --- browse/js/preview-markdown.js | 44 +++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/browse/js/preview-markdown.js b/browse/js/preview-markdown.js index 839bff5..c4f135e 100644 --- a/browse/js/preview-markdown.js +++ b/browse/js/preview-markdown.js @@ -603,16 +603,22 @@ // even if we tweak whitespace in the YAML lines. var initialParsed = parseFrontMatter(text); var bodyText = initialParsed.body; - // On open, mirror the filename-derived identity into the front matter - // (the filename is the single source of truth; this keeps the values - // baked in for the converter). No-op for non-ZDDC filenames. The dirty + // On open, RECONCILE existing front-matter identity keys with the + // filename (the single source of truth) — but never ADD them. A blank + // or new file opens blank (we don't inject a title etc.); a file whose + // author already wrote a now-stale title/revision/… gets corrected. + // The converter derives identity from the filename regardless, so + // there's nothing to "bake in" for an empty front matter. The dirty // baseline stays the ON-DISK state, so a correction opens the buffer // dirty and a save persists it. var onDiskFM = stringifyFrontMatter(initialParsed.data); var fid = filenameIdentity(node.name); if (fid) { for (var ik in fid) { - if (Object.prototype.hasOwnProperty.call(fid, ik)) initialParsed.data[ik] = fid[ik]; + if (Object.prototype.hasOwnProperty.call(fid, ik) + && Object.prototype.hasOwnProperty.call(initialParsed.data, ik)) { + initialParsed.data[ik] = fid[ik]; + } } } var syncedFM = stringifyFrontMatter(initialParsed.data); @@ -891,7 +897,10 @@ if (!fid) return; var data = parseFrontMatter('---\n' + fmCM.getValue() + '\n---\n').data || {}; for (var k in fid) { - if (Object.prototype.hasOwnProperty.call(fid, k)) data[k] = fid[k]; + if (Object.prototype.hasOwnProperty.call(fid, k) + && Object.prototype.hasOwnProperty.call(data, k)) { + data[k] = fid[k]; + } } fmCM.setValue(stringifyFrontMatter(data)); var body = editor.getMarkdown(); @@ -909,11 +918,26 @@ async function renameToMatch(newName) { var up = window.app.modules.upload; if (!up || !up.renameNode || !newName) return; - // 1. Save first so body/FM edits survive the rename. A failed save - // (conflict, ACL) leaves the buffer dirty — abort the rename. - if (instance.dirty) { - await save(); - if (currentInstance !== instance || instance.dirty) return; + // 1. Persist the current buffer first so body edits survive the + // rename. Force the write (no If-Match) — the user deliberately + // initiated this rename, so we commit their version rather than + // interrupting with the conflict-resolution modal (which save() + // raises on a 412). The identity edit that triggered the rename + // is consumed by the new filename, so there's nothing to merge. + if (instance.dirty && canSave(node)) { + try { + var content = assembleContent(fmCM.getValue(), editor.getMarkdown()); + statusEl.textContent = 'Saving…'; + var res = await saveContent(node, content, { force: true }); + await markSaved(content, res); + if (currentInstance !== instance) return; + } catch (e) { + statusEl.textContent = ''; + if (window.zddc && window.zddc.toast) { + window.zddc.toast('Save failed: ' + (e.message || e), 'error'); + } + return; + } } // 2. Rename on disk. try {