Fix mirror restore deletion semantics - #20

Merged
vrtmrz merged 2 commits into
mainfrom
fix-mirror-delete-semantics
Jul 15, 2026
Merged

Fix mirror restore deletion semantics#20
vrtmrz merged 2 commits into
mainfrom
fix-mirror-delete-semantics

Conversation

@vrtmrz

@vrtmrzvrtmrz commented Jul 15, 2026

Copy link
Copy Markdown
Owner

User-facing behaviour

  • Only new restores only files that are absent locally or newer in the selected backup.
  • All restores every selected file without deleting local files.
  • All and delete extra restores every selected file, then deletes local files represented by the selected deletion records.
  • A restore that will delete local files uses the title Restore and Delete Confirmation and the explicit action Restore and delete; Cancel remains the default action.
  • Selecting an older restore point now follows the state recorded at that point. A file deleted later is restored from an earlier revision, while a deletion record selected at that point removes the corresponding local file.
  • If an archive cannot be read, an entry is missing, or a Vault write fails, the restore reports the failure and does not proceed with the planned deletions.
  • The legacy folder restore skips deletion records and restores the available file revisions.

Implementation summary

  • map each restore mode to its intended overwrite and deletion behaviour
  • derive deletion decisions from the selected historical revision
  • require every requested archive entry to be restored before deleting confirmed local files
  • propagate archive and Vault write failures
  • distinguish destructive restore confirmations in their title and primary action
  • keep restore planning and execution under separate wake-lock leases, without holding a lease during confirmation

Root cause

The restore view mapped the UI modes incorrectly, and restore execution used the current top-level missing state rather than the state of the selected historical revision. Extraction failures could also be reported as success, allowing deletion to proceed without a complete restore. The legacy folder workflow attempted to extract contentless deletion records.

Behaviour and responsibility boundary

‘All and delete extra’ now restores every selected archive entry successfully before it removes the confirmed deletion candidates. The confirmation dialogue presents a point-in-time restore plan; it does not lock the Vault or revalidate candidates changed while the dialogue is open. If the Vault may have changed during review, the operation must be cancelled and prepared again.

Validation

  • npm run build
  • npm run check:e2e:obsidian
  • npm run lint
  • npm run test:ui — 11 passed
  • deno task test — 68 passed
  • npm run test:e2e:obsidian:legacy-folder-restore
  • npm run test:e2e:obsidian:restore-confirmation
  • npm run test:e2e:obsidian:wake-lock
  • npm run test:e2e:obsidian:mirror-delete-semantics

The mirror E2E covers historical deletion and recreation decisions, extraction failures, write failures, wake-lock lifetimes, and a 24-file deletion plan. The legacy E2E restores an ordinary revision from a real ZIP while skipping a deletion record.

@vrtmrz
vrtmrz marked this pull request as ready for review July 15, 2026 05:50
@vrtmrz
vrtmrz merged commit 6096890 into mainJul 15, 2026
1 check passed
@vrtmrz
vrtmrz deleted the fix-mirror-delete-semantics branch July 15, 2026 05:52
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@vrtmrz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Fix mirror restore deletion semantics - #20

Merged
vrtmrz merged 2 commits into
mainfrom
fix-mirror-delete-semantics
Jul 15, 2026
Merged

Fix mirror restore deletion semantics#20
vrtmrz merged 2 commits into
mainfrom
fix-mirror-delete-semantics

Conversation

@vrtmrz

@vrtmrzvrtmrz commented Jul 15, 2026

Copy link
Copy Markdown
Owner

User-facing behaviour

  • Only new restores only files that are absent locally or newer in the selected backup.
  • All restores every selected file without deleting local files.
  • All and delete extra restores every selected file, then deletes local files represented by the selected deletion records.
  • A restore that will delete local files uses the title Restore and Delete Confirmation and the explicit action Restore and delete; Cancel remains the default action.
  • Selecting an older restore point now follows the state recorded at that point. A file deleted later is restored from an earlier revision, while a deletion record selected at that point removes the corresponding local file.
  • If an archive cannot be read, an entry is missing, or a Vault write fails, the restore reports the failure and does not proceed with the planned deletions.
  • The legacy folder restore skips deletion records and restores the available file revisions.

Implementation summary

  • map each restore mode to its intended overwrite and deletion behaviour
  • derive deletion decisions from the selected historical revision
  • require every requested archive entry to be restored before deleting confirmed local files
  • propagate archive and Vault write failures
  • distinguish destructive restore confirmations in their title and primary action
  • keep restore planning and execution under separate wake-lock leases, without holding a lease during confirmation

Root cause

The restore view mapped the UI modes incorrectly, and restore execution used the current top-level missing state rather than the state of the selected historical revision. Extraction failures could also be reported as success, allowing deletion to proceed without a complete restore. The legacy folder workflow attempted to extract contentless deletion records.

Behaviour and responsibility boundary

‘All and delete extra’ now restores every selected archive entry successfully before it removes the confirmed deletion candidates. The confirmation dialogue presents a point-in-time restore plan; it does not lock the Vault or revalidate candidates changed while the dialogue is open. If the Vault may have changed during review, the operation must be cancelled and prepared again.

Validation

  • npm run build
  • npm run check:e2e:obsidian
  • npm run lint
  • npm run test:ui — 11 passed
  • deno task test — 68 passed
  • npm run test:e2e:obsidian:legacy-folder-restore
  • npm run test:e2e:obsidian:restore-confirmation
  • npm run test:e2e:obsidian:wake-lock
  • npm run test:e2e:obsidian:mirror-delete-semantics

The mirror E2E covers historical deletion and recreation decisions, extraction failures, write failures, wake-lock lifetimes, and a 24-file deletion plan. The legacy E2E restores an ordinary revision from a real ZIP while skipping a deletion record.

@vrtmrz
vrtmrz marked this pull request as ready for review July 15, 2026 05:50
@vrtmrz
vrtmrz merged commit 6096890 into mainJul 15, 2026
1 check passed
@vrtmrz
vrtmrz deleted the fix-mirror-delete-semantics branch July 15, 2026 05:52
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@vrtmrz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix mirror restore deletion semantics - #20

Merged
vrtmrz merged 2 commits into
mainfrom
fix-mirror-delete-semantics
Jul 15, 2026
Merged

Fix mirror restore deletion semantics#20
vrtmrz merged 2 commits into
mainfrom
fix-mirror-delete-semantics

Conversation

@vrtmrz

@vrtmrzvrtmrz commented Jul 15, 2026

Copy link
Copy Markdown
Owner

User-facing behaviour

  • Only new restores only files that are absent locally or newer in the selected backup.
  • All restores every selected file without deleting local files.
  • All and delete extra restores every selected file, then deletes local files represented by the selected deletion records.
  • A restore that will delete local files uses the title Restore and Delete Confirmation and the explicit action Restore and delete; Cancel remains the default action.
  • Selecting an older restore point now follows the state recorded at that point. A file deleted later is restored from an earlier revision, while a deletion record selected at that point removes the corresponding local file.
  • If an archive cannot be read, an entry is missing, or a Vault write fails, the restore reports the failure and does not proceed with the planned deletions.
  • The legacy folder restore skips deletion records and restores the available file revisions.

Implementation summary

  • map each restore mode to its intended overwrite and deletion behaviour
  • derive deletion decisions from the selected historical revision
  • require every requested archive entry to be restored before deleting confirmed local files
  • propagate archive and Vault write failures
  • distinguish destructive restore confirmations in their title and primary action
  • keep restore planning and execution under separate wake-lock leases, without holding a lease during confirmation

Root cause

The restore view mapped the UI modes incorrectly, and restore execution used the current top-level missing state rather than the state of the selected historical revision. Extraction failures could also be reported as success, allowing deletion to proceed without a complete restore. The legacy folder workflow attempted to extract contentless deletion records.

Behaviour and responsibility boundary

‘All and delete extra’ now restores every selected archive entry successfully before it removes the confirmed deletion candidates. The confirmation dialogue presents a point-in-time restore plan; it does not lock the Vault or revalidate candidates changed while the dialogue is open. If the Vault may have changed during review, the operation must be cancelled and prepared again.

Validation

  • npm run build
  • npm run check:e2e:obsidian
  • npm run lint
  • npm run test:ui — 11 passed
  • deno task test — 68 passed
  • npm run test:e2e:obsidian:legacy-folder-restore
  • npm run test:e2e:obsidian:restore-confirmation
  • npm run test:e2e:obsidian:wake-lock
  • npm run test:e2e:obsidian:mirror-delete-semantics

The mirror E2E covers historical deletion and recreation decisions, extraction failures, write failures, wake-lock lifetimes, and a 24-file deletion plan. The legacy E2E restores an ordinary revision from a real ZIP while skipping a deletion record.

@vrtmrz
vrtmrz marked this pull request as ready for review July 15, 2026 05:50
@vrtmrz
vrtmrz merged commit 6096890 into mainJul 15, 2026
1 check passed
@vrtmrz
vrtmrz deleted the fix-mirror-delete-semantics branch July 15, 2026 05:52
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@vrtmrz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix mirror restore deletion semantics - #20

Merged
vrtmrz merged 2 commits into
mainfrom
fix-mirror-delete-semantics
Jul 15, 2026
Merged

Fix mirror restore deletion semantics#20
vrtmrz merged 2 commits into
mainfrom
fix-mirror-delete-semantics

Conversation

@vrtmrz

@vrtmrzvrtmrz commented Jul 15, 2026

Copy link
Copy Markdown
Owner

User-facing behaviour

  • Only new restores only files that are absent locally or newer in the selected backup.
  • All restores every selected file without deleting local files.
  • All and delete extra restores every selected file, then deletes local files represented by the selected deletion records.
  • A restore that will delete local files uses the title Restore and Delete Confirmation and the explicit action Restore and delete; Cancel remains the default action.
  • Selecting an older restore point now follows the state recorded at that point. A file deleted later is restored from an earlier revision, while a deletion record selected at that point removes the corresponding local file.
  • If an archive cannot be read, an entry is missing, or a Vault write fails, the restore reports the failure and does not proceed with the planned deletions.
  • The legacy folder restore skips deletion records and restores the available file revisions.

Implementation summary

  • map each restore mode to its intended overwrite and deletion behaviour
  • derive deletion decisions from the selected historical revision
  • require every requested archive entry to be restored before deleting confirmed local files
  • propagate archive and Vault write failures
  • distinguish destructive restore confirmations in their title and primary action
  • keep restore planning and execution under separate wake-lock leases, without holding a lease during confirmation

Root cause

The restore view mapped the UI modes incorrectly, and restore execution used the current top-level missing state rather than the state of the selected historical revision. Extraction failures could also be reported as success, allowing deletion to proceed without a complete restore. The legacy folder workflow attempted to extract contentless deletion records.

Behaviour and responsibility boundary

‘All and delete extra’ now restores every selected archive entry successfully before it removes the confirmed deletion candidates. The confirmation dialogue presents a point-in-time restore plan; it does not lock the Vault or revalidate candidates changed while the dialogue is open. If the Vault may have changed during review, the operation must be cancelled and prepared again.

Validation

  • npm run build
  • npm run check:e2e:obsidian
  • npm run lint
  • npm run test:ui — 11 passed
  • deno task test — 68 passed
  • npm run test:e2e:obsidian:legacy-folder-restore
  • npm run test:e2e:obsidian:restore-confirmation
  • npm run test:e2e:obsidian:wake-lock
  • npm run test:e2e:obsidian:mirror-delete-semantics

The mirror E2E covers historical deletion and recreation decisions, extraction failures, write failures, wake-lock lifetimes, and a 24-file deletion plan. The legacy E2E restores an ordinary revision from a real ZIP while skipping a deletion record.

@vrtmrz
vrtmrz marked this pull request as ready for review July 15, 2026 05:50
@vrtmrz
vrtmrz merged commit 6096890 into mainJul 15, 2026
1 check passed
@vrtmrz
vrtmrz deleted the fix-mirror-delete-semantics branch July 15, 2026 05:52
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@vrtmrz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Fix mirror restore deletion semantics - #20

Merged
vrtmrz merged 2 commits into
mainfrom
fix-mirror-delete-semantics
Jul 15, 2026
Merged

Fix mirror restore deletion semantics#20
vrtmrz merged 2 commits into
mainfrom
fix-mirror-delete-semantics

Conversation

@vrtmrz

@vrtmrzvrtmrz commented Jul 15, 2026

Copy link
Copy Markdown
Owner

User-facing behaviour

  • Only new restores only files that are absent locally or newer in the selected backup.
  • All restores every selected file without deleting local files.
  • All and delete extra restores every selected file, then deletes local files represented by the selected deletion records.
  • A restore that will delete local files uses the title Restore and Delete Confirmation and the explicit action Restore and delete; Cancel remains the default action.
  • Selecting an older restore point now follows the state recorded at that point. A file deleted later is restored from an earlier revision, while a deletion record selected at that point removes the corresponding local file.
  • If an archive cannot be read, an entry is missing, or a Vault write fails, the restore reports the failure and does not proceed with the planned deletions.
  • The legacy folder restore skips deletion records and restores the available file revisions.

Implementation summary

  • map each restore mode to its intended overwrite and deletion behaviour
  • derive deletion decisions from the selected historical revision
  • require every requested archive entry to be restored before deleting confirmed local files
  • propagate archive and Vault write failures
  • distinguish destructive restore confirmations in their title and primary action
  • keep restore planning and execution under separate wake-lock leases, without holding a lease during confirmation

Root cause

The restore view mapped the UI modes incorrectly, and restore execution used the current top-level missing state rather than the state of the selected historical revision. Extraction failures could also be reported as success, allowing deletion to proceed without a complete restore. The legacy folder workflow attempted to extract contentless deletion records.

Behaviour and responsibility boundary

‘All and delete extra’ now restores every selected archive entry successfully before it removes the confirmed deletion candidates. The confirmation dialogue presents a point-in-time restore plan; it does not lock the Vault or revalidate candidates changed while the dialogue is open. If the Vault may have changed during review, the operation must be cancelled and prepared again.

Validation

  • npm run build
  • npm run check:e2e:obsidian
  • npm run lint
  • npm run test:ui — 11 passed
  • deno task test — 68 passed
  • npm run test:e2e:obsidian:legacy-folder-restore
  • npm run test:e2e:obsidian:restore-confirmation
  • npm run test:e2e:obsidian:wake-lock
  • npm run test:e2e:obsidian:mirror-delete-semantics

The mirror E2E covers historical deletion and recreation decisions, extraction failures, write failures, wake-lock lifetimes, and a 24-file deletion plan. The legacy E2E restores an ordinary revision from a real ZIP while skipping a deletion record.

@vrtmrz
vrtmrz marked this pull request as ready for review July 15, 2026 05:50
@vrtmrz
vrtmrz merged commit 6096890 into mainJul 15, 2026
1 check passed
@vrtmrz
vrtmrz deleted the fix-mirror-delete-semantics branch July 15, 2026 05:52
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@vrtmrz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix mirror restore deletion semantics - #20

Merged
vrtmrz merged 2 commits into
mainfrom
fix-mirror-delete-semantics
Jul 15, 2026
Merged

Fix mirror restore deletion semantics#20
vrtmrz merged 2 commits into
mainfrom
fix-mirror-delete-semantics

Conversation

@vrtmrz

@vrtmrzvrtmrz commented Jul 15, 2026

Copy link
Copy Markdown
Owner

User-facing behaviour

  • Only new restores only files that are absent locally or newer in the selected backup.
  • All restores every selected file without deleting local files.
  • All and delete extra restores every selected file, then deletes local files represented by the selected deletion records.
  • A restore that will delete local files uses the title Restore and Delete Confirmation and the explicit action Restore and delete; Cancel remains the default action.
  • Selecting an older restore point now follows the state recorded at that point. A file deleted later is restored from an earlier revision, while a deletion record selected at that point removes the corresponding local file.
  • If an archive cannot be read, an entry is missing, or a Vault write fails, the restore reports the failure and does not proceed with the planned deletions.
  • The legacy folder restore skips deletion records and restores the available file revisions.

Implementation summary

  • map each restore mode to its intended overwrite and deletion behaviour
  • derive deletion decisions from the selected historical revision
  • require every requested archive entry to be restored before deleting confirmed local files
  • propagate archive and Vault write failures
  • distinguish destructive restore confirmations in their title and primary action
  • keep restore planning and execution under separate wake-lock leases, without holding a lease during confirmation

Root cause

The restore view mapped the UI modes incorrectly, and restore execution used the current top-level missing state rather than the state of the selected historical revision. Extraction failures could also be reported as success, allowing deletion to proceed without a complete restore. The legacy folder workflow attempted to extract contentless deletion records.

Behaviour and responsibility boundary

‘All and delete extra’ now restores every selected archive entry successfully before it removes the confirmed deletion candidates. The confirmation dialogue presents a point-in-time restore plan; it does not lock the Vault or revalidate candidates changed while the dialogue is open. If the Vault may have changed during review, the operation must be cancelled and prepared again.

Validation

  • npm run build
  • npm run check:e2e:obsidian
  • npm run lint
  • npm run test:ui — 11 passed
  • deno task test — 68 passed
  • npm run test:e2e:obsidian:legacy-folder-restore
  • npm run test:e2e:obsidian:restore-confirmation
  • npm run test:e2e:obsidian:wake-lock
  • npm run test:e2e:obsidian:mirror-delete-semantics

The mirror E2E covers historical deletion and recreation decisions, extraction failures, write failures, wake-lock lifetimes, and a 24-file deletion plan. The legacy E2E restores an ordinary revision from a real ZIP while skipping a deletion record.

@vrtmrz
vrtmrz marked this pull request as ready for review July 15, 2026 05:50
@vrtmrz
vrtmrz merged commit 6096890 into mainJul 15, 2026
1 check passed
@vrtmrz
vrtmrz deleted the fix-mirror-delete-semantics branch July 15, 2026 05:52
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@vrtmrz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix mirror restore deletion semantics - #20

Merged
vrtmrz merged 2 commits into
mainfrom
fix-mirror-delete-semantics
Jul 15, 2026
Merged

Fix mirror restore deletion semantics#20
vrtmrz merged 2 commits into
mainfrom
fix-mirror-delete-semantics

Conversation

@vrtmrz

@vrtmrzvrtmrz commented Jul 15, 2026

Copy link
Copy Markdown
Owner

User-facing behaviour

  • Only new restores only files that are absent locally or newer in the selected backup.
  • All restores every selected file without deleting local files.
  • All and delete extra restores every selected file, then deletes local files represented by the selected deletion records.
  • A restore that will delete local files uses the title Restore and Delete Confirmation and the explicit action Restore and delete; Cancel remains the default action.
  • Selecting an older restore point now follows the state recorded at that point. A file deleted later is restored from an earlier revision, while a deletion record selected at that point removes the corresponding local file.
  • If an archive cannot be read, an entry is missing, or a Vault write fails, the restore reports the failure and does not proceed with the planned deletions.
  • The legacy folder restore skips deletion records and restores the available file revisions.

Implementation summary

  • map each restore mode to its intended overwrite and deletion behaviour
  • derive deletion decisions from the selected historical revision
  • require every requested archive entry to be restored before deleting confirmed local files
  • propagate archive and Vault write failures
  • distinguish destructive restore confirmations in their title and primary action
  • keep restore planning and execution under separate wake-lock leases, without holding a lease during confirmation

Root cause

The restore view mapped the UI modes incorrectly, and restore execution used the current top-level missing state rather than the state of the selected historical revision. Extraction failures could also be reported as success, allowing deletion to proceed without a complete restore. The legacy folder workflow attempted to extract contentless deletion records.

Behaviour and responsibility boundary

‘All and delete extra’ now restores every selected archive entry successfully before it removes the confirmed deletion candidates. The confirmation dialogue presents a point-in-time restore plan; it does not lock the Vault or revalidate candidates changed while the dialogue is open. If the Vault may have changed during review, the operation must be cancelled and prepared again.

Validation

  • npm run build
  • npm run check:e2e:obsidian
  • npm run lint
  • npm run test:ui — 11 passed
  • deno task test — 68 passed
  • npm run test:e2e:obsidian:legacy-folder-restore
  • npm run test:e2e:obsidian:restore-confirmation
  • npm run test:e2e:obsidian:wake-lock
  • npm run test:e2e:obsidian:mirror-delete-semantics

The mirror E2E covers historical deletion and recreation decisions, extraction failures, write failures, wake-lock lifetimes, and a 24-file deletion plan. The legacy E2E restores an ordinary revision from a real ZIP while skipping a deletion record.

@vrtmrz
vrtmrz marked this pull request as ready for review July 15, 2026 05:50
@vrtmrz
vrtmrz merged commit 6096890 into mainJul 15, 2026
1 check passed
@vrtmrz
vrtmrz deleted the fix-mirror-delete-semantics branch July 15, 2026 05:52
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@vrtmrz
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Fix mirror restore deletion semantics - #20

Merged
vrtmrz merged 2 commits into
mainfrom
fix-mirror-delete-semantics
Jul 15, 2026
Merged

Fix mirror restore deletion semantics#20
vrtmrz merged 2 commits into
mainfrom
fix-mirror-delete-semantics

Conversation

@vrtmrz

@vrtmrzvrtmrz commented Jul 15, 2026

Copy link
Copy Markdown
Owner

User-facing behaviour

  • Only new restores only files that are absent locally or newer in the selected backup.
  • All restores every selected file without deleting local files.
  • All and delete extra restores every selected file, then deletes local files represented by the selected deletion records.
  • A restore that will delete local files uses the title Restore and Delete Confirmation and the explicit action Restore and delete; Cancel remains the default action.
  • Selecting an older restore point now follows the state recorded at that point. A file deleted later is restored from an earlier revision, while a deletion record selected at that point removes the corresponding local file.
  • If an archive cannot be read, an entry is missing, or a Vault write fails, the restore reports the failure and does not proceed with the planned deletions.
  • The legacy folder restore skips deletion records and restores the available file revisions.

Implementation summary

  • map each restore mode to its intended overwrite and deletion behaviour
  • derive deletion decisions from the selected historical revision
  • require every requested archive entry to be restored before deleting confirmed local files
  • propagate archive and Vault write failures
  • distinguish destructive restore confirmations in their title and primary action
  • keep restore planning and execution under separate wake-lock leases, without holding a lease during confirmation

Root cause

The restore view mapped the UI modes incorrectly, and restore execution used the current top-level missing state rather than the state of the selected historical revision. Extraction failures could also be reported as success, allowing deletion to proceed without a complete restore. The legacy folder workflow attempted to extract contentless deletion records.

Behaviour and responsibility boundary

‘All and delete extra’ now restores every selected archive entry successfully before it removes the confirmed deletion candidates. The confirmation dialogue presents a point-in-time restore plan; it does not lock the Vault or revalidate candidates changed while the dialogue is open. If the Vault may have changed during review, the operation must be cancelled and prepared again.

Validation

  • npm run build
  • npm run check:e2e:obsidian
  • npm run lint
  • npm run test:ui — 11 passed
  • deno task test — 68 passed
  • npm run test:e2e:obsidian:legacy-folder-restore
  • npm run test:e2e:obsidian:restore-confirmation
  • npm run test:e2e:obsidian:wake-lock
  • npm run test:e2e:obsidian:mirror-delete-semantics

The mirror E2E covers historical deletion and recreation decisions, extraction failures, write failures, wake-lock lifetimes, and a 24-file deletion plan. The legacy E2E restores an ordinary revision from a real ZIP while skipping a deletion record.

@vrtmrz
vrtmrz marked this pull request as ready for review July 15, 2026 05:50
@vrtmrz
vrtmrz merged commit 6096890 into mainJul 15, 2026
1 check passed
@vrtmrz
vrtmrz deleted the fix-mirror-delete-semantics branch July 15, 2026 05:52
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@vrtmrz