fix(browse): don't inject front matter on open; no conflict modal on rename

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) <noreply@anthropic.com>
This commit is contained in:
ZDDC 2026-06-08 08:54:59 -05:00
parent cfe379d4f9
commit 84f93ba56d

View file

@ -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 {