feat(cms): optimistic locking against stale-tab overwrites
CI Pipeline with Test Management / 🧪 Run Tests & Generate Reports (push) Waiting to run
CI Pipeline with Test Management / 🐳 Docker Integration Tests (push) Blocked by required conditions
CI Pipeline with Test Management / 🏗️ Build Docker Image (push) Blocked by required conditions
CI Pipeline with Test Management / 📊 Generate Test Summary (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🧪 Run Tests & Generate Reports (push) Waiting to run
Test Reporting & Gherkin Analysis / 📊 Analyze Test Coverage (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🔄 Sync with Linear (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / ⚡ Performance Monitoring (push) Blocked by required conditions

A Content Editor tab left open across a deploy (or a save from another tab)
held the pre-deploy content; one Save would silently overwrite the newer
file. The editor page now embeds a SHA-256 fingerprint of the file content
at load time, /save requires it back in X-Content-Hash and compares against
the current file: mismatch (or a missing header) answers 409 with an
explanatory message and writes nothing. The client offers a reload on 409.

Integration test covers: matching hash saves, stale hash rejected with the
file untouched, missing hash rejected, retry with the fresh hash succeeds.

Closes MITHOME-61
This commit is contained in:
Do Siki
2026-08-18 20:57:08 +02:00
parent bdf9aac4d0
commit 3818859cc5
6 changed files with 157 additions and 4 deletions
+2 -1
View File
@@ -4,7 +4,7 @@
const isStaging = () => process.env.CMS_DEPLOY_ENV === 'staging';
// FILE_LABELS is injected to avoid a circular dependency with the main file.
const HTML = (activeFile, jsonData, message, csrfToken, fileLabels, clientJs) => `<!DOCTYPE html>
const HTML = (activeFile, jsonData, message, csrfToken, fileLabels, clientJs, contentHash) => `<!DOCTYPE html>
<html lang="hu">
<head>
<meta charset="UTF-8">
@@ -120,6 +120,7 @@ ${message ? `<div class="toast ${message.type === 'ok' ? 'ok' : 'err'}">${messag
const DATA = JSON.parse(document.getElementById('page-data').textContent);
const FILE = "${activeFile}";
const CSRF_TOKEN = "${csrfToken}";
const CONTENT_HASH = "${contentHash}";
${clientJs}
</script>