modified on local at 2026-07-12 23:13:04
| 1 | 1 | <div class="page-head"> |
| 2 | 2 | <span class="page-eyebrow"><span class="dot"></span> Diff</span> |
| 3 | 3 | <h1 class="page-title">$title</h1> |
| 4 | 4 | <p class="page-subtitle">$subtitle</p> |
| 5 | 5 | </div> |
| 6 | 6 | |
| 7 | 7 | <div class="dash-grid" style="grid-template-columns:repeat(4,1fr);margin-bottom:16px"> |
| 8 | 8 | <div class="module glow-emerald"><div class="module-head"><div class="left"><div class="module-title">Added</div></div></div><div class="module-body kpi kpi-emerald"><div class="num">+$ct_add</div><div class="lbl">lines</div></div></div> |
| 9 | 9 | <div class="module glow-rose"><div class="module-head"><div class="left"><div class="module-title">Removed</div></div></div><div class="module-body kpi kpi-rose"><div class="num">-$ct_del</div><div class="lbl">lines</div></div></div> |
| 10 | 10 | <div class="module glow-teal"><div class="module-head"><div class="left"><div class="module-title">Context</div></div></div><div class="module-body kpi kpi-teal"><div class="num">$ct_context</div><div class="lbl">unchanged</div></div></div> |
| 11 | 11 | <div class="module glow-sky"><div class="module-head"><div class="left"><div class="module-title">Blobs</div></div></div><div class="module-body kpi kpi-sky"><div class="num" style="font-size:14px;letter-spacing:0"><span class="dim">from</span> $prev_sha<br><span class="dim">to</span> $curr_sha</div></div></div> |
| 12 | 12 | </div> |
| 13 | 13 | |
| 14 | 14 | [if:$restore_url] |
| 15 | 15 | <div style="display:flex;justify-content:flex-end;gap:8px;margin:-4px 0 12px"> |
| 16 | 16 | <button type="button" id="dsCompileBtn" class="tr-chip" |
| 17 | 17 | data-id="$id" data-kind="$kind" |
| 18 | 18 | style="border-color:var(--accent-warn);color:var(--accent-warn-hi)"> |
| 19 | 19 | Test compile <span style="font-size:9.5px;letter-spacing:1px;color:var(--text-muted)">(perl / node / bash / py)</span> |
| 20 | 20 | </button> |
| 21 | 21 | <a href="$restore_url" class="tr-chip" style="border-color:var(--accent);color:var(--accent-hi)">Restore this content →</a> |
| 22 | 22 | </div> |
| 23 | 23 | |
| 24 | 24 | <div id="dsCompileResult" style="display:none;margin:0 0 16px;padding:12px 16px;border-radius:10px;font-family:var(--mono);font-size:12.5px;line-height:1.55"></div> |
| 25 | 25 | |
| 26 | 26 | <script> |
| 27 | 27 | (function () { |
| 28 | 28 | var btn = document.getElementById('dsCompileBtn'); |
| 29 | 29 | var out = document.getElementById('dsCompileResult'); |
| 30 | 30 | if (!btn) return; |
| 31 | 31 | btn.addEventListener('click', async function () { |
| 32 | 32 | if (btn.getAttribute('data-kind') !== 'file') { |
| 33 | 33 | alert('Compile check only supports file changes.'); |
| 34 | 34 | return; |
| 35 | 35 | } |
| 36 | 36 | var origText = btn.innerHTML; |
| 37 | 37 | btn.disabled = true; |
| 38 | 38 | btn.textContent = 'Checking...'; |
| 39 | 39 | out.style.display = 'block'; |
| 40 | 40 | out.style.background = 'var(--bg-2)'; |
| 41 | 41 | out.style.border = '1px solid var(--border)'; |
| 42 | 42 | out.style.color = 'var(--text-dim)'; |
| 43 | 43 | out.textContent = 'Running compile check...'; |
| 44 | 44 | |
| 45 | 45 | try { |
| 46 | 46 | var r = await fetch('/diff.cgi?op=compile&id=' + btn.getAttribute('data-id'), { cache: 'no-store' }); |
| 47 | 47 | var j = await r.json(); |
| 48 | 48 | var color = j.status === 'ok' ? 'rgba(52,211,153,' |
| 49 | 49 | : j.status === 'error' ? 'rgba(244,63,94,' |
| 50 | 50 | : 'rgba(245,158,11,'; |
| 51 | 51 | out.style.background = color + '0.08)'; |
| 52 | 52 | out.style.border = '1px solid ' + color + '0.35)'; |
| 53 | 53 | out.style.color = j.status === 'error' ? '#fda4af' |
| 54 | 54 | : j.status === 'ok' ? '#86efac' |
| 55 | 55 | : '#fcd34d'; |
| 56 | 56 | var head = document.createElement('div'); |
| 57 | 57 | head.style.marginBottom = '6px'; |
| 58 | 58 | head.style.fontWeight = '700'; |
| 59 | 59 | head.style.fontFamily = 'var(--sans)'; |
| 60 | 60 | head.textContent = (j.status === 'ok' ? '✓ Compiles cleanly' |
| 61 | 61 | : j.status === 'error' ? '✗ Syntax errors' |
| 62 | 62 | : '⚠ Unchecked') |
| 63 | 63 | + ' (' + (j.language || '?') + ')'; |
| 64 | 64 | out.innerHTML = ''; |
| 65 | 65 | out.appendChild(head); |
| 66 | 66 | var body = document.createElement('pre'); |
| 67 | 67 | body.style.margin = '0'; |
| 68 | 68 | body.style.whiteSpace = 'pre-wrap'; |
| 69 | 69 | body.style.wordWrap = 'break-word'; |
| 70 | 70 | body.style.fontSize = '12px'; |
| 71 | 71 | body.textContent = j.message || ''; |
| 72 | 72 | out.appendChild(body); |
| 73 | 73 | } catch (e) { |
| 74 | 74 | out.textContent = 'Compile check failed: ' + e.message; |
| 75 | 75 | } finally { |
| 76 | 76 | btn.disabled = false; |
| 77 | 77 | btn.innerHTML = origText; |
| 78 | 78 | } |
| 79 | 79 | }); |
| 80 | 80 | })(); |
| 81 | 81 | </script> |
| 82 | 82 | [/if] |
| 83 | 83 | |
| 84 | 84 | <div class="module glow-teal"> |
| 85 | 85 | <div class="module-head"><div class="left"><div class="module-title">Unified diff</div><span class="module-subtitle">Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.</span></div></div> |
| 86 | 86 | <div class="module-body tight"> |
| 87 | 87 | [if:$has_diff] |
| 88 | 88 | <style> |
| 89 | 89 | .diff-tbl { width:100%; border-collapse:collapse; font-family:var(--mono); font-size:12.5px; line-height:1.5; } |
| 90 | 90 | .diff-tbl td { padding:1px 8px; vertical-align:top; white-space:pre-wrap; word-wrap:break-word; } |
| 91 | 91 | .diff-tbl .ln { color:var(--text-muted); text-align:right; width:44px; user-select:none; border-right:1px solid var(--border); font-size:11px; } |
| 92 | 92 | .diff-tbl tr.op-add td.text { background:rgba(52,211,153,.10); color:#a7f3d0; } |
| 93 | 93 | .diff-tbl tr.op-add td.ln { background:rgba(52,211,153,.05); color:var(--accent-ok); } |
| 94 | 94 | .diff-tbl tr.op-add td.text::before { content:'+'; color:var(--accent-ok); padding-right:6px; } |
| 95 | 95 | .diff-tbl tr.op-del td.text { background:rgba(244,63,94,.10); color:var(--accent-danger-hi); } |
| 96 | 96 | .diff-tbl tr.op-del td.ln { background:rgba(244,63,94,.05); color:var(--accent-danger); } |
| 97 | 97 | .diff-tbl tr.op-del td.text::before { content:'-'; color:var(--accent-danger); padding-right:6px; } |
| 98 | 98 | .diff-tbl tr.op-ctx td { color:var(--text-dim); } |
| 99 | 99 | .diff-tbl tr.op-ctx td.text::before { content:' '; padding-right:6px; } |
| 100 | 100 | </style> |
| 101 | 101 | <div style="max-height:70vh;overflow:auto"> |
| 102 | 102 | <table class="diff-tbl"> |
| 103 | 103 | <tbody> |
| 104 | 104 | [loop:@lines]<tr class="op-$op"><td class="ln">$old_n</td><td class="ln">$new_n</td><td class="text">$text</td></tr>[/loop] |
| 105 | 105 | </tbody> |
| 106 | 106 | </table> |
| 107 | 107 | </div> |
| 108 | 108 | [/if] |
| 109 | 109 | [if:!$has_diff] |
| 110 | 110 | <div style="padding:36px 14px;text-align:center;color:var(--text-dim);font-size:13.5px"> |
| 111 | 111 | Nothing to diff. Blob may have been auto-purged, or this is the first snapshot. |
| 112 | 112 | </div> |
| 113 | 113 | [/if] |
| 114 | </div> | |
| 115 | </div> | |
| 116 | ||
| 117 | <div class="module glow-teal" style="margin-top:14px"> | |
| 118 | <div class="module-body tight" style="display:flex;gap:14px;flex-wrap:wrap;align-items:center;font-size:11.5px;color:var(--text-dim)"> | |
| 119 | <span style="color:var(--text)"><strong>Keyboard:</strong></span> | |
| 120 | <span><kbd class="ds-kbd">j</kbd> next diff</span> | |
| 121 | <span><kbd class="ds-kbd">k</kbd> previous diff</span> | |
| 122 | <span><kbd class="ds-kbd">g</kbd> top</span> | |
| 123 | <span><kbd class="ds-kbd">G</kbd> bottom</span> | |
| 124 | <span><kbd class="ds-kbd">r</kbd> restore</span> | |
| 125 | <span><kbd class="ds-kbd">c</kbd> compile-check</span> | |
| 126 | <span><kbd class="ds-kbd">?</kbd> help</span> | |
| 114 | 127 | </div> |
| 115 | 128 | </div> |
| 129 | <style> | |
| 130 | .ds-kbd { display:inline-block;padding:1px 6px;border:1px solid var(--border);border-bottom-width:2px;border-radius:4px;background:var(--bg-2);font-family:var(--mono);font-size:10.5px;color:var(--text);margin:0 1px } | |
| 131 | </style> | |
| 132 | <script> | |
| 133 | (function(){ | |
| 134 | var lines = Array.from(document.querySelectorAll('.diff-tbl tr.op-add, .diff-tbl tr.op-del')); | |
| 135 | if (!lines.length) return; | |
| 136 | var idx = -1; | |
| 137 | function scrollTo(row){ | |
| 138 | if (!row) return; | |
| 139 | lines.forEach(function(l){l.style.outline='';}); | |
| 140 | row.style.outline = '2px solid var(--accent-hi)'; | |
| 141 | row.style.outlineOffset = '-2px'; | |
| 142 | row.scrollIntoView({block:'center', behavior:'smooth'}); | |
| 143 | } | |
| 144 | document.addEventListener('keydown', function(e){ | |
| 145 | if (e.target && (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable)) return; | |
| 146 | if (e.metaKey || e.ctrlKey || e.altKey) return; | |
| 147 | switch(e.key) { | |
| 148 | case 'j': idx = Math.min(idx + 1, lines.length - 1); scrollTo(lines[idx]); e.preventDefault(); break; | |
| 149 | case 'k': idx = Math.max(idx - 1, 0); scrollTo(lines[idx]); e.preventDefault(); break; | |
| 150 | case 'g': window.scrollTo({top:0, behavior:'smooth'}); idx = -1; e.preventDefault(); break; | |
| 151 | case 'G': window.scrollTo({top:document.body.scrollHeight, behavior:'smooth'}); idx = lines.length - 1; e.preventDefault(); break; | |
| 152 | case 'r': var r = document.querySelector('a[href*="/restore.cgi"]'); if (r) location.href = r.href; break; | |
| 153 | case 'c': var c = document.querySelector('[onclick*="compile"], a[href*="op=compile"], button.ds-compile-btn'); if (c) c.click(); break; | |
| 154 | case '?': alert('DriftSense diff viewer\n\nj / k next / prev diff\ng / G top / bottom\nr restore this file\nc compile-check captured bytes\n? this help'); e.preventDefault(); break; | |
| 155 | } | |
| 156 | }); | |
| 157 | })(); | |
| 158 | </script> |