reviw 0.16.3 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.cjs +382 -60
- package/package.json +1 -1
package/cli.cjs
CHANGED
|
@@ -159,7 +159,8 @@ marked.use({
|
|
|
159
159
|
if (videoExtensions.test(href)) {
|
|
160
160
|
// For videos, render as video element with controls and thumbnail preview
|
|
161
161
|
var displayText = text || href.split('/').pop();
|
|
162
|
-
|
|
162
|
+
var dataAlt = text ? ' data-alt="' + escapeHtmlForXss(text) + '"' : "";
|
|
163
|
+
return '<video src="' + escapeHtmlForXss(href) + '" controls preload="metadata" class="video-preview"' + titleAttr + dataAlt + '>' +
|
|
163
164
|
'<a href="' + escapeHtmlForXss(href) + '">📹' + escapeHtmlForXss(displayText) + '</a></video>';
|
|
164
165
|
}
|
|
165
166
|
return '<img src="' + escapeHtmlForXss(href) + '"' + altAttr + titleAttr + '>';
|
|
@@ -946,7 +947,7 @@ function diffHtmlTemplate(diffData, history = []) {
|
|
|
946
947
|
}
|
|
947
948
|
.theme-toggle:hover { background: var(--border); }
|
|
948
949
|
|
|
949
|
-
.wrap { padding: 16px 20px
|
|
950
|
+
.wrap { padding: 16px 20px 16px; max-width: 1200px; margin: 0 auto; }
|
|
950
951
|
.diff-container {
|
|
951
952
|
background: var(--panel);
|
|
952
953
|
border: 1px solid var(--border);
|
|
@@ -1140,19 +1141,8 @@ function diffHtmlTemplate(diffData, history = []) {
|
|
|
1140
1141
|
.comment-list li:hover { color: var(--accent); }
|
|
1141
1142
|
.comment-list .hint { color: var(--muted); font-size: 11px; margin-top: 8px; }
|
|
1142
1143
|
.comment-list.collapsed { opacity: 0; pointer-events: none; transform: translateY(8px); }
|
|
1143
|
-
.
|
|
1144
|
-
|
|
1145
|
-
right: 16px;
|
|
1146
|
-
bottom: 16px;
|
|
1147
|
-
padding: 8px 12px;
|
|
1148
|
-
border-radius: 6px;
|
|
1149
|
-
border: 1px solid var(--border);
|
|
1150
|
-
background: var(--panel-alpha);
|
|
1151
|
-
color: var(--text);
|
|
1152
|
-
cursor: pointer;
|
|
1153
|
-
font-size: 12px;
|
|
1154
|
-
}
|
|
1155
|
-
.pill { display: inline-flex; align-items: center; gap: 6px; padding: 4px 8px; border-radius: 999px; background: var(--selected-bg); border: 1px solid var(--border); font-size: 12px; }
|
|
1144
|
+
.pill { display: inline-flex; align-items: center; gap: 6px; padding: 4px 8px; border-radius: 999px; background: var(--selected-bg); border: 1px solid var(--border); font-size: 12px; cursor: pointer; transition: background 150ms ease, border-color 150ms ease; }
|
|
1145
|
+
.pill:hover { background: var(--hover-bg); border-color: var(--accent); }
|
|
1156
1146
|
.pill strong { font-weight: 700; }
|
|
1157
1147
|
|
|
1158
1148
|
.modal-overlay {
|
|
@@ -1213,6 +1203,15 @@ function diffHtmlTemplate(diffData, history = []) {
|
|
|
1213
1203
|
}
|
|
1214
1204
|
.modal-actions button:hover { background: var(--border); }
|
|
1215
1205
|
.modal-actions button.primary { background: var(--accent); color: var(--text-inverse); border-color: var(--accent); }
|
|
1206
|
+
.image-attach-area { margin: 12px 0; }
|
|
1207
|
+
.image-attach-area label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 6px; }
|
|
1208
|
+
.image-attach-area.image-attach-small { margin: 8px 0; }
|
|
1209
|
+
.image-attach-area.image-attach-small label { font-size: 11px; }
|
|
1210
|
+
.image-preview-list { display: flex; flex-wrap: wrap; gap: 8px; min-height: 24px; }
|
|
1211
|
+
.image-preview-item { position: relative; }
|
|
1212
|
+
.image-preview-item img { max-width: 80px; max-height: 60px; border-radius: 4px; border: 1px solid var(--border); object-fit: cover; }
|
|
1213
|
+
.image-preview-item .remove-image { position: absolute; top: -6px; right: -6px; width: 18px; height: 18px; border-radius: 50%; background: var(--error, #ef4444); color: #fff; border: none; cursor: pointer; font-size: 12px; line-height: 1; display: flex; align-items: center; justify-content: center; }
|
|
1214
|
+
.image-preview-item .remove-image:hover { background: #dc2626; }
|
|
1216
1215
|
|
|
1217
1216
|
.modal-checkboxes { margin: 12px 0; }
|
|
1218
1217
|
.modal-checkboxes label {
|
|
@@ -1458,7 +1457,7 @@ function diffHtmlTemplate(diffData, history = []) {
|
|
|
1458
1457
|
<div class="meta">
|
|
1459
1458
|
<h1>${projectRoot ? `<span class="title-path">${projectRoot}</span>` : ""}<span class="title-file">${relativePath}</span></h1>
|
|
1460
1459
|
<span class="badge">${fileCount} file${fileCount !== 1 ? "s" : ""} changed</span>
|
|
1461
|
-
<
|
|
1460
|
+
<button class="pill" id="pill-comments" title="Toggle comment panel">Comments <strong id="comment-count">0</strong></button>
|
|
1462
1461
|
</div>
|
|
1463
1462
|
<div class="actions">
|
|
1464
1463
|
<button class="history-toggle" id="history-toggle" title="Review History">☰</button>
|
|
@@ -1492,17 +1491,20 @@ function diffHtmlTemplate(diffData, history = []) {
|
|
|
1492
1491
|
</header>
|
|
1493
1492
|
<div id="cell-preview" style="font-size:11px; color: var(--muted); margin-bottom:8px; white-space: pre-wrap; max-height: 60px; overflow: hidden;"></div>
|
|
1494
1493
|
<textarea id="comment-input" placeholder="Enter your comment"></textarea>
|
|
1494
|
+
<div class="image-attach-area image-attach-small" id="comment-image-area">
|
|
1495
|
+
<label>📎 Image (⌘V, max 1)</label>
|
|
1496
|
+
<div class="image-preview-list" id="comment-image-preview"></div>
|
|
1497
|
+
</div>
|
|
1495
1498
|
<div class="actions">
|
|
1496
1499
|
<button class="primary" id="save-comment">Save</button>
|
|
1497
1500
|
</div>
|
|
1498
1501
|
</div>
|
|
1499
1502
|
|
|
1500
|
-
<aside class="comment-list">
|
|
1503
|
+
<aside class="comment-list collapsed">
|
|
1501
1504
|
<h3>Comments</h3>
|
|
1502
1505
|
<ol id="comment-list"></ol>
|
|
1503
1506
|
<p class="hint">Click "Submit & Exit" to finish review.</p>
|
|
1504
1507
|
</aside>
|
|
1505
|
-
<button class="comment-toggle" id="comment-toggle">Comments (0)</button>
|
|
1506
1508
|
|
|
1507
1509
|
<div class="modal-overlay" id="submit-modal">
|
|
1508
1510
|
<div class="modal-dialog">
|
|
@@ -1510,6 +1512,10 @@ function diffHtmlTemplate(diffData, history = []) {
|
|
|
1510
1512
|
<p class="modal-summary" id="modal-summary"></p>
|
|
1511
1513
|
<label for="global-comment">Overall comment (optional)</label>
|
|
1512
1514
|
<textarea id="global-comment" placeholder="Add a summary or overall feedback..."></textarea>
|
|
1515
|
+
<div class="image-attach-area" id="submit-image-area">
|
|
1516
|
+
<label>📎 Attach images (⌘V to paste, max 5)</label>
|
|
1517
|
+
<div class="image-preview-list" id="submit-image-preview"></div>
|
|
1518
|
+
</div>
|
|
1513
1519
|
<div class="modal-checkboxes">
|
|
1514
1520
|
<label><input type="checkbox" id="prompt-subagents" checked /> 🤖 Delegate to sub-agents (implement, verify, report)</label>
|
|
1515
1521
|
<label><input type="checkbox" id="prompt-reviw" checked /> 👁️ Open in REVIW next time</label>
|
|
@@ -1716,7 +1722,7 @@ function diffHtmlTemplate(diffData, history = []) {
|
|
|
1716
1722
|
const commentList = document.getElementById('comment-list');
|
|
1717
1723
|
const commentCount = document.getElementById('comment-count');
|
|
1718
1724
|
const commentPanel = document.querySelector('.comment-list');
|
|
1719
|
-
const
|
|
1725
|
+
const pillComments = document.getElementById('pill-comments');
|
|
1720
1726
|
|
|
1721
1727
|
const comments = {};
|
|
1722
1728
|
let currentKey = null;
|
|
@@ -1726,6 +1732,81 @@ function diffHtmlTemplate(diffData, history = []) {
|
|
|
1726
1732
|
let dragEnd = null;
|
|
1727
1733
|
let selection = null;
|
|
1728
1734
|
|
|
1735
|
+
// Image attachment state
|
|
1736
|
+
const submitImages = []; // base64 images for submit modal (max 5)
|
|
1737
|
+
let currentCommentImage = null; // base64 image for current comment (max 1)
|
|
1738
|
+
|
|
1739
|
+
// Image attachment handlers
|
|
1740
|
+
const submitImagePreview = document.getElementById('submit-image-preview');
|
|
1741
|
+
const commentImagePreview = document.getElementById('comment-image-preview');
|
|
1742
|
+
|
|
1743
|
+
function addImageToPreview(container, images, maxCount, base64) {
|
|
1744
|
+
if (images.length >= maxCount) return;
|
|
1745
|
+
images.push(base64);
|
|
1746
|
+
renderImagePreviews(container, images);
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
function renderImagePreviews(container, images) {
|
|
1750
|
+
container.innerHTML = '';
|
|
1751
|
+
images.forEach((base64, idx) => {
|
|
1752
|
+
const item = document.createElement('div');
|
|
1753
|
+
item.className = 'image-preview-item';
|
|
1754
|
+
item.innerHTML = \`<img src="\${base64}" alt="attached image"><button class="remove-image" data-idx="\${idx}">×</button>\`;
|
|
1755
|
+
item.querySelector('.remove-image').addEventListener('click', () => {
|
|
1756
|
+
images.splice(idx, 1);
|
|
1757
|
+
renderImagePreviews(container, images);
|
|
1758
|
+
});
|
|
1759
|
+
container.appendChild(item);
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
function renderCommentImagePreview() {
|
|
1764
|
+
commentImagePreview.innerHTML = '';
|
|
1765
|
+
if (currentCommentImage) {
|
|
1766
|
+
const item = document.createElement('div');
|
|
1767
|
+
item.className = 'image-preview-item';
|
|
1768
|
+
item.innerHTML = \`<img src="\${currentCommentImage}" alt="attached image"><button class="remove-image">×</button>\`;
|
|
1769
|
+
item.querySelector('.remove-image').addEventListener('click', () => {
|
|
1770
|
+
currentCommentImage = null;
|
|
1771
|
+
renderCommentImagePreview();
|
|
1772
|
+
});
|
|
1773
|
+
commentImagePreview.appendChild(item);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
// Global paste handler
|
|
1778
|
+
document.addEventListener('paste', (e) => {
|
|
1779
|
+
const items = e.clipboardData?.items;
|
|
1780
|
+
if (!items) return;
|
|
1781
|
+
for (const item of items) {
|
|
1782
|
+
if (item.type.startsWith('image/')) {
|
|
1783
|
+
e.preventDefault();
|
|
1784
|
+
const file = item.getAsFile();
|
|
1785
|
+
const reader = new FileReader();
|
|
1786
|
+
reader.onload = () => {
|
|
1787
|
+
const base64 = reader.result;
|
|
1788
|
+
const activeEl = document.activeElement;
|
|
1789
|
+
// Prioritize comment card if its textarea has focus
|
|
1790
|
+
if (card.style.display !== 'none' && activeEl === commentInput) {
|
|
1791
|
+
if (!currentCommentImage) {
|
|
1792
|
+
currentCommentImage = base64;
|
|
1793
|
+
renderCommentImagePreview();
|
|
1794
|
+
}
|
|
1795
|
+
} else if (submitModal.classList.contains('visible')) {
|
|
1796
|
+
addImageToPreview(submitImagePreview, submitImages, 5, base64);
|
|
1797
|
+
} else if (card.style.display !== 'none') {
|
|
1798
|
+
if (!currentCommentImage) {
|
|
1799
|
+
currentCommentImage = base64;
|
|
1800
|
+
renderCommentImagePreview();
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
};
|
|
1804
|
+
reader.readAsDataURL(file);
|
|
1805
|
+
break;
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
});
|
|
1809
|
+
|
|
1729
1810
|
function makeKey(start, end) {
|
|
1730
1811
|
return start === end ? String(start) : (start + '-' + end);
|
|
1731
1812
|
}
|
|
@@ -1947,7 +2028,6 @@ function diffHtmlTemplate(diffData, history = []) {
|
|
|
1947
2028
|
commentList.innerHTML = '';
|
|
1948
2029
|
const items = Object.values(comments).sort((a, b) => (a.startRow ?? a.row) - (b.startRow ?? b.row));
|
|
1949
2030
|
commentCount.textContent = items.length;
|
|
1950
|
-
commentToggle.textContent = 'Comments (' + items.length + ')';
|
|
1951
2031
|
if (items.length === 0) panelOpen = false;
|
|
1952
2032
|
commentPanel.classList.toggle('collapsed', !panelOpen || items.length === 0);
|
|
1953
2033
|
if (!items.length) {
|
|
@@ -1968,7 +2048,7 @@ function diffHtmlTemplate(diffData, history = []) {
|
|
|
1968
2048
|
});
|
|
1969
2049
|
}
|
|
1970
2050
|
|
|
1971
|
-
|
|
2051
|
+
pillComments.addEventListener('click', () => {
|
|
1972
2052
|
panelOpen = !panelOpen;
|
|
1973
2053
|
if (panelOpen && Object.keys(comments).length === 0) panelOpen = false;
|
|
1974
2054
|
commentPanel.classList.toggle('collapsed', !panelOpen);
|
|
@@ -1980,7 +2060,7 @@ function diffHtmlTemplate(diffData, history = []) {
|
|
|
1980
2060
|
const range = keyToRange(currentKey);
|
|
1981
2061
|
if (!range) return;
|
|
1982
2062
|
const rowIdx = range.start;
|
|
1983
|
-
if (text) {
|
|
2063
|
+
if (text || currentCommentImage) {
|
|
1984
2064
|
if (range.start === range.end) {
|
|
1985
2065
|
comments[currentKey] = { row: rowIdx, text, content: DATA[rowIdx]?.content || '' };
|
|
1986
2066
|
} else {
|
|
@@ -1992,11 +2072,16 @@ function diffHtmlTemplate(diffData, history = []) {
|
|
|
1992
2072
|
content: DATA.slice(range.start, range.end + 1).map(r => r?.content || '').join('\\n')
|
|
1993
2073
|
};
|
|
1994
2074
|
}
|
|
2075
|
+
if (currentCommentImage) {
|
|
2076
|
+
comments[currentKey].image = currentCommentImage;
|
|
2077
|
+
}
|
|
1995
2078
|
setDotRange(range.start, range.end, true);
|
|
1996
2079
|
} else {
|
|
1997
2080
|
delete comments[currentKey];
|
|
1998
2081
|
setDotRange(range.start, range.end, false);
|
|
1999
2082
|
}
|
|
2083
|
+
currentCommentImage = null;
|
|
2084
|
+
renderCommentImagePreview();
|
|
2000
2085
|
refreshList();
|
|
2001
2086
|
closeCard();
|
|
2002
2087
|
saveToStorage();
|
|
@@ -2112,17 +2197,28 @@ function diffHtmlTemplate(diffData, history = []) {
|
|
|
2112
2197
|
function payload(reason) {
|
|
2113
2198
|
const data = { file: FILE_NAME, mode: MODE, submittedBy: reason, submittedAt: new Date().toISOString(), comments: Object.values(comments) };
|
|
2114
2199
|
if (globalComment.trim()) data.summary = globalComment.trim();
|
|
2200
|
+
if (submitImages.length > 0) data.summaryImages = submitImages;
|
|
2115
2201
|
const prompts = getSelectedPrompts();
|
|
2116
2202
|
if (prompts.length > 0) data.prompts = prompts;
|
|
2117
2203
|
return data;
|
|
2118
2204
|
}
|
|
2119
|
-
function sendAndExit(reason = 'button') {
|
|
2205
|
+
async function sendAndExit(reason = 'button') {
|
|
2120
2206
|
if (sent) return;
|
|
2121
2207
|
sent = true;
|
|
2122
2208
|
clearStorage();
|
|
2123
2209
|
const p = payload(reason);
|
|
2124
2210
|
saveToHistory(p);
|
|
2125
|
-
|
|
2211
|
+
try {
|
|
2212
|
+
// Use fetch with keepalive to handle large payloads (images)
|
|
2213
|
+
await fetch('/exit', {
|
|
2214
|
+
method: 'POST',
|
|
2215
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2216
|
+
body: JSON.stringify(p),
|
|
2217
|
+
// Note: keepalive has 64KB limit like sendBeacon, so we don't use it for large payloads
|
|
2218
|
+
});
|
|
2219
|
+
} catch (err) {
|
|
2220
|
+
console.error('Failed to send exit request:', err);
|
|
2221
|
+
}
|
|
2126
2222
|
}
|
|
2127
2223
|
function showSubmitModal() {
|
|
2128
2224
|
const count = Object.keys(comments).length;
|
|
@@ -2134,11 +2230,11 @@ function diffHtmlTemplate(diffData, history = []) {
|
|
|
2134
2230
|
function hideSubmitModal() { submitModal.classList.remove('visible'); }
|
|
2135
2231
|
document.getElementById('send-and-exit').addEventListener('click', showSubmitModal);
|
|
2136
2232
|
document.getElementById('modal-cancel').addEventListener('click', hideSubmitModal);
|
|
2137
|
-
function doSubmit() {
|
|
2233
|
+
async function doSubmit() {
|
|
2138
2234
|
globalComment = globalCommentInput.value;
|
|
2139
2235
|
savePromptPrefs();
|
|
2140
2236
|
hideSubmitModal();
|
|
2141
|
-
sendAndExit('button');
|
|
2237
|
+
await sendAndExit('button');
|
|
2142
2238
|
// Try to close window; if it fails (browser security), show completion message
|
|
2143
2239
|
setTimeout(() => {
|
|
2144
2240
|
window.close();
|
|
@@ -2343,7 +2439,7 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
2343
2439
|
}
|
|
2344
2440
|
.theme-toggle:hover { background: var(--hover-bg); transform: scale(1.05); }
|
|
2345
2441
|
|
|
2346
|
-
.wrap { padding: 12px 16px
|
|
2442
|
+
.wrap { padding: 12px 16px 12px; }
|
|
2347
2443
|
.toolbar {
|
|
2348
2444
|
display: flex;
|
|
2349
2445
|
gap: 12px;
|
|
@@ -2607,27 +2703,14 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
2607
2703
|
}
|
|
2608
2704
|
.comment-list li { margin-bottom: 6px; }
|
|
2609
2705
|
.comment-list .hint { color: var(--muted); font-size: 12px; }
|
|
2610
|
-
.pill { display: inline-flex; align-items: center; gap: 6px; padding: 4px 8px; border-radius: 999px; background: var(--selected-bg); border: 1px solid var(--border); font-size: 12px; color: var(--text); }
|
|
2706
|
+
.pill { display: inline-flex; align-items: center; gap: 6px; padding: 4px 8px; border-radius: 999px; background: var(--selected-bg); border: 1px solid var(--border); font-size: 12px; color: var(--text); cursor: pointer; transition: background 150ms ease, border-color 150ms ease; }
|
|
2707
|
+
.pill:hover { background: var(--hover-bg); border-color: var(--accent); }
|
|
2611
2708
|
.pill strong { color: var(--text); font-weight: 700; }
|
|
2612
2709
|
.comment-list.collapsed {
|
|
2613
2710
|
opacity: 0;
|
|
2614
2711
|
pointer-events: none;
|
|
2615
2712
|
transform: translateY(8px) scale(0.98);
|
|
2616
2713
|
}
|
|
2617
|
-
.comment-toggle {
|
|
2618
|
-
position: fixed;
|
|
2619
|
-
right: 14px;
|
|
2620
|
-
bottom: 14px;
|
|
2621
|
-
padding: 10px 12px;
|
|
2622
|
-
border-radius: 10px;
|
|
2623
|
-
border: 1px solid var(--border);
|
|
2624
|
-
background: var(--selected-bg);
|
|
2625
|
-
color: var(--text);
|
|
2626
|
-
cursor: pointer;
|
|
2627
|
-
box-shadow: 0 10px 24px var(--shadow-color);
|
|
2628
|
-
font-size: 13px;
|
|
2629
|
-
transition: background 200ms ease, border-color 200ms ease;
|
|
2630
|
-
}
|
|
2631
2714
|
.md-preview {
|
|
2632
2715
|
background: var(--input-bg);
|
|
2633
2716
|
border: 1px solid var(--border);
|
|
@@ -2641,13 +2724,14 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
2641
2724
|
gap: 16px;
|
|
2642
2725
|
align-items: stretch;
|
|
2643
2726
|
margin-top: 8px;
|
|
2644
|
-
height: calc(100vh -
|
|
2727
|
+
height: calc(100vh - 80px);
|
|
2645
2728
|
}
|
|
2646
2729
|
.md-left {
|
|
2647
2730
|
flex: 1;
|
|
2648
2731
|
min-width: 0;
|
|
2649
2732
|
overflow-y: auto;
|
|
2650
2733
|
overflow-x: hidden;
|
|
2734
|
+
overscroll-behavior: contain;
|
|
2651
2735
|
}
|
|
2652
2736
|
.md-left .md-preview {
|
|
2653
2737
|
max-height: none;
|
|
@@ -2657,6 +2741,7 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
2657
2741
|
min-width: 0;
|
|
2658
2742
|
overflow-y: auto;
|
|
2659
2743
|
overflow-x: auto;
|
|
2744
|
+
overscroll-behavior: contain;
|
|
2660
2745
|
}
|
|
2661
2746
|
.md-right .table-box {
|
|
2662
2747
|
max-width: none;
|
|
@@ -3354,6 +3439,15 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
3354
3439
|
}
|
|
3355
3440
|
.modal-actions button:hover { background: var(--hover-bg); }
|
|
3356
3441
|
.modal-actions button.primary { background: var(--accent); color: var(--text-inverse); border-color: var(--accent); }
|
|
3442
|
+
.image-attach-area { margin: 12px 0; }
|
|
3443
|
+
.image-attach-area label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 6px; }
|
|
3444
|
+
.image-attach-area.image-attach-small { margin: 8px 0; }
|
|
3445
|
+
.image-attach-area.image-attach-small label { font-size: 11px; }
|
|
3446
|
+
.image-preview-list { display: flex; flex-wrap: wrap; gap: 8px; min-height: 24px; }
|
|
3447
|
+
.image-preview-item { position: relative; }
|
|
3448
|
+
.image-preview-item img { max-width: 80px; max-height: 60px; border-radius: 4px; border: 1px solid var(--border); object-fit: cover; }
|
|
3449
|
+
.image-preview-item .remove-image { position: absolute; top: -6px; right: -6px; width: 18px; height: 18px; border-radius: 50%; background: var(--error, #ef4444); color: #fff; border: none; cursor: pointer; font-size: 12px; line-height: 1; display: flex; align-items: center; justify-content: center; }
|
|
3450
|
+
.image-preview-item .remove-image:hover { background: #dc2626; }
|
|
3357
3451
|
.modal-actions button.primary:hover { background: #7dd3fc; }
|
|
3358
3452
|
|
|
3359
3453
|
.modal-checkboxes { margin: 12px 0; }
|
|
@@ -3686,7 +3780,7 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
3686
3780
|
<div class="meta">
|
|
3687
3781
|
<h1><span class="title-path">${projectRoot}</span><span class="title-file">${relativePath}</span></h1>
|
|
3688
3782
|
<span class="badge">Click to comment / ESC to cancel</span>
|
|
3689
|
-
<
|
|
3783
|
+
<button class="pill" id="pill-comments" title="Toggle comment panel">Comments <strong id="comment-count">0</strong></button>
|
|
3690
3784
|
</div>
|
|
3691
3785
|
<div class="actions">
|
|
3692
3786
|
<button class="history-toggle" id="history-toggle" title="Review History">☰</button>
|
|
@@ -3772,17 +3866,20 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
3772
3866
|
</header>
|
|
3773
3867
|
<div id="cell-preview" style="font-size:12px; color: var(--muted); margin-bottom:8px;"></div>
|
|
3774
3868
|
<textarea id="comment-input" placeholder="Enter your comment or note"></textarea>
|
|
3869
|
+
<div class="image-attach-area image-attach-small" id="comment-image-area">
|
|
3870
|
+
<label>📎 Image (⌘V, max 1)</label>
|
|
3871
|
+
<div class="image-preview-list" id="comment-image-preview"></div>
|
|
3872
|
+
</div>
|
|
3775
3873
|
<div class="actions">
|
|
3776
3874
|
<button class="primary" id="save-comment">Save</button>
|
|
3777
3875
|
</div>
|
|
3778
3876
|
</div>
|
|
3779
3877
|
|
|
3780
|
-
<aside class="comment-list">
|
|
3878
|
+
<aside class="comment-list collapsed">
|
|
3781
3879
|
<h3>Comments</h3>
|
|
3782
3880
|
<ol id="comment-list"></ol>
|
|
3783
3881
|
<p class="hint">Close the tab or click "Submit & Exit" to send comments and stop the server.</p>
|
|
3784
3882
|
</aside>
|
|
3785
|
-
<button class="comment-toggle" id="comment-toggle">Comments (0)</button>
|
|
3786
3883
|
<div class="filter-menu" id="filter-menu">
|
|
3787
3884
|
<label class="menu-check"><input type="checkbox" id="freeze-col-check" /> Freeze up to this column</label>
|
|
3788
3885
|
<button data-action="not-empty">Rows where not empty</button>
|
|
@@ -3812,6 +3909,10 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
3812
3909
|
<p class="modal-summary" id="modal-summary"></p>
|
|
3813
3910
|
<label for="global-comment">Overall comment (optional)</label>
|
|
3814
3911
|
<textarea id="global-comment" placeholder="Add a summary or overall feedback..."></textarea>
|
|
3912
|
+
<div class="image-attach-area" id="submit-image-area">
|
|
3913
|
+
<label>📎 Attach images (⌘V to paste, max 5)</label>
|
|
3914
|
+
<div class="image-preview-list" id="submit-image-preview"></div>
|
|
3915
|
+
</div>
|
|
3815
3916
|
<div class="modal-checkboxes">
|
|
3816
3917
|
<label><input type="checkbox" id="prompt-subagents" checked /> 🤖 Delegate to sub-agents (implement, verify, report)</label>
|
|
3817
3918
|
<label><input type="checkbox" id="prompt-reviw" checked /> 👁️ Open in REVIW next time</label>
|
|
@@ -4074,7 +4175,7 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
4074
4175
|
const commentCount = document.getElementById('comment-count');
|
|
4075
4176
|
const fitBtn = document.getElementById('fit-width');
|
|
4076
4177
|
const commentPanel = document.querySelector('.comment-list');
|
|
4077
|
-
const
|
|
4178
|
+
const pillComments = document.getElementById('pill-comments');
|
|
4078
4179
|
const filterMenu = document.getElementById('filter-menu');
|
|
4079
4180
|
const rowMenu = document.getElementById('row-menu');
|
|
4080
4181
|
const freezeColCheck = document.getElementById('freeze-col-check');
|
|
@@ -4144,6 +4245,85 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
4144
4245
|
let dragEnd = null; // {row, col}
|
|
4145
4246
|
let selection = null; // {startRow, endRow, startCol, endCol}
|
|
4146
4247
|
|
|
4248
|
+
// Image attachment state
|
|
4249
|
+
const submitImages = []; // base64 images for submit modal (max 5)
|
|
4250
|
+
let currentCommentImage = null; // base64 image for current comment (max 1)
|
|
4251
|
+
|
|
4252
|
+
// Image attachment handlers
|
|
4253
|
+
const submitImagePreview = document.getElementById('submit-image-preview');
|
|
4254
|
+
const commentImagePreview = document.getElementById('comment-image-preview');
|
|
4255
|
+
|
|
4256
|
+
function addImageToPreview(container, images, maxCount, base64) {
|
|
4257
|
+
if (images.length >= maxCount) return;
|
|
4258
|
+
images.push(base64);
|
|
4259
|
+
renderImagePreviews(container, images);
|
|
4260
|
+
}
|
|
4261
|
+
|
|
4262
|
+
function renderImagePreviews(container, images) {
|
|
4263
|
+
container.innerHTML = '';
|
|
4264
|
+
images.forEach((base64, idx) => {
|
|
4265
|
+
const item = document.createElement('div');
|
|
4266
|
+
item.className = 'image-preview-item';
|
|
4267
|
+
item.innerHTML = \`<img src="\${base64}" alt="attached image"><button class="remove-image" data-idx="\${idx}">×</button>\`;
|
|
4268
|
+
item.querySelector('.remove-image').addEventListener('click', () => {
|
|
4269
|
+
images.splice(idx, 1);
|
|
4270
|
+
renderImagePreviews(container, images);
|
|
4271
|
+
});
|
|
4272
|
+
container.appendChild(item);
|
|
4273
|
+
});
|
|
4274
|
+
}
|
|
4275
|
+
|
|
4276
|
+
function renderCommentImagePreview() {
|
|
4277
|
+
commentImagePreview.innerHTML = '';
|
|
4278
|
+
if (currentCommentImage) {
|
|
4279
|
+
const item = document.createElement('div');
|
|
4280
|
+
item.className = 'image-preview-item';
|
|
4281
|
+
item.innerHTML = \`<img src="\${currentCommentImage}" alt="attached image"><button class="remove-image">×</button>\`;
|
|
4282
|
+
item.querySelector('.remove-image').addEventListener('click', () => {
|
|
4283
|
+
currentCommentImage = null;
|
|
4284
|
+
renderCommentImagePreview();
|
|
4285
|
+
});
|
|
4286
|
+
commentImagePreview.appendChild(item);
|
|
4287
|
+
}
|
|
4288
|
+
}
|
|
4289
|
+
|
|
4290
|
+
// Global paste handler for images
|
|
4291
|
+
document.addEventListener('paste', (e) => {
|
|
4292
|
+
const items = e.clipboardData?.items;
|
|
4293
|
+
if (!items) return;
|
|
4294
|
+
for (const item of items) {
|
|
4295
|
+
if (item.type.startsWith('image/')) {
|
|
4296
|
+
e.preventDefault();
|
|
4297
|
+
const file = item.getAsFile();
|
|
4298
|
+
const reader = new FileReader();
|
|
4299
|
+
reader.onload = () => {
|
|
4300
|
+
const base64 = reader.result;
|
|
4301
|
+
const modal = document.getElementById('submit-modal');
|
|
4302
|
+
const commentCard = document.getElementById('comment-card');
|
|
4303
|
+
const activeEl = document.activeElement;
|
|
4304
|
+
const commentInput = document.getElementById('comment-input');
|
|
4305
|
+
|
|
4306
|
+
// Prioritize comment card if its textarea has focus
|
|
4307
|
+
if (commentCard?.style.display !== 'none' && activeEl === commentInput) {
|
|
4308
|
+
if (!currentCommentImage) {
|
|
4309
|
+
currentCommentImage = base64;
|
|
4310
|
+
renderCommentImagePreview();
|
|
4311
|
+
}
|
|
4312
|
+
} else if (modal?.classList.contains('visible')) {
|
|
4313
|
+
addImageToPreview(submitImagePreview, submitImages, 5, base64);
|
|
4314
|
+
} else if (commentCard?.style.display !== 'none') {
|
|
4315
|
+
if (!currentCommentImage) {
|
|
4316
|
+
currentCommentImage = base64;
|
|
4317
|
+
renderCommentImagePreview();
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
};
|
|
4321
|
+
reader.readAsDataURL(file);
|
|
4322
|
+
break;
|
|
4323
|
+
}
|
|
4324
|
+
}
|
|
4325
|
+
});
|
|
4326
|
+
|
|
4147
4327
|
// --- localStorage Comment Persistence ---
|
|
4148
4328
|
const STORAGE_KEY = 'reviw:comments:' + FILE_NAME;
|
|
4149
4329
|
const STORAGE_TTL = 3 * 60 * 60 * 1000; // 3 hours in milliseconds
|
|
@@ -4566,7 +4746,6 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
4566
4746
|
return aRow === bRow ? aCol - bCol : aRow - bRow;
|
|
4567
4747
|
});
|
|
4568
4748
|
commentCount.textContent = items.length;
|
|
4569
|
-
commentToggle.textContent = 'Comments (' + items.length + ')';
|
|
4570
4749
|
if (items.length === 0) {
|
|
4571
4750
|
panelOpen = false;
|
|
4572
4751
|
}
|
|
@@ -4608,7 +4787,7 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
4608
4787
|
});
|
|
4609
4788
|
}
|
|
4610
4789
|
|
|
4611
|
-
|
|
4790
|
+
pillComments.addEventListener('click', () => {
|
|
4612
4791
|
panelOpen = !panelOpen;
|
|
4613
4792
|
if (panelOpen && Object.keys(comments).length === 0) {
|
|
4614
4793
|
panelOpen = false; // keep hidden if no comments
|
|
@@ -4745,8 +4924,11 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
4745
4924
|
if (isRangeKey(currentKey)) {
|
|
4746
4925
|
// Range (rectangular) comment
|
|
4747
4926
|
const { startRow, startCol, endRow, endCol } = parseRangeKey(currentKey);
|
|
4748
|
-
if (text) {
|
|
4927
|
+
if (text || currentCommentImage) {
|
|
4749
4928
|
comments[currentKey] = { startRow, startCol, endRow, endCol, text, isRange: true };
|
|
4929
|
+
if (currentCommentImage) {
|
|
4930
|
+
comments[currentKey].image = currentCommentImage;
|
|
4931
|
+
}
|
|
4750
4932
|
for (let r = startRow; r <= endRow; r++) {
|
|
4751
4933
|
for (let c = startCol; c <= endCol; c++) {
|
|
4752
4934
|
setDot(r, c, true);
|
|
@@ -4768,14 +4950,19 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
4768
4950
|
const [row, col] = currentKey.split('-').map(Number);
|
|
4769
4951
|
const td = tbody.querySelector('td[data-row="' + row + '"][data-col="' + col + '"]');
|
|
4770
4952
|
const value = td ? td.textContent : '';
|
|
4771
|
-
if (text) {
|
|
4953
|
+
if (text || currentCommentImage) {
|
|
4772
4954
|
comments[currentKey] = { row, col, text, value };
|
|
4955
|
+
if (currentCommentImage) {
|
|
4956
|
+
comments[currentKey].image = currentCommentImage;
|
|
4957
|
+
}
|
|
4773
4958
|
setDot(row, col, true);
|
|
4774
4959
|
} else {
|
|
4775
4960
|
delete comments[currentKey];
|
|
4776
4961
|
setDot(row, col, false);
|
|
4777
4962
|
}
|
|
4778
4963
|
}
|
|
4964
|
+
currentCommentImage = null;
|
|
4965
|
+
renderCommentImagePreview();
|
|
4779
4966
|
refreshList();
|
|
4780
4967
|
closeCard();
|
|
4781
4968
|
saveCommentsToStorage();
|
|
@@ -5059,6 +5246,10 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
5059
5246
|
if (c.isRange) {
|
|
5060
5247
|
transformed.lineEnd = (c.endRow || c.startRow) + 1;
|
|
5061
5248
|
}
|
|
5249
|
+
// Preserve image attachment
|
|
5250
|
+
if (c.image) {
|
|
5251
|
+
transformed.image = c.image;
|
|
5252
|
+
}
|
|
5062
5253
|
return transformed;
|
|
5063
5254
|
});
|
|
5064
5255
|
}
|
|
@@ -5079,6 +5270,7 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
5079
5270
|
if (globalComment.trim()) {
|
|
5080
5271
|
data.summary = globalComment.trim();
|
|
5081
5272
|
}
|
|
5273
|
+
if (submitImages.length > 0) data.summaryImages = submitImages;
|
|
5082
5274
|
const prompts = getSelectedPrompts();
|
|
5083
5275
|
if (prompts.length > 0) data.prompts = prompts;
|
|
5084
5276
|
// Include answered questions
|
|
@@ -5099,14 +5291,24 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
5099
5291
|
}
|
|
5100
5292
|
return data;
|
|
5101
5293
|
}
|
|
5102
|
-
function sendAndExit(reason = 'pagehide') {
|
|
5294
|
+
async function sendAndExit(reason = 'pagehide') {
|
|
5103
5295
|
if (sent) return;
|
|
5104
5296
|
sent = true;
|
|
5105
5297
|
clearCommentsFromStorage();
|
|
5106
5298
|
const p = payload(reason);
|
|
5107
5299
|
saveToHistory(p);
|
|
5108
|
-
|
|
5109
|
-
|
|
5300
|
+
try {
|
|
5301
|
+
// Use fetch with keepalive to handle large payloads (images)
|
|
5302
|
+
// keepalive allows the request to outlive the page
|
|
5303
|
+
await fetch('/exit', {
|
|
5304
|
+
method: 'POST',
|
|
5305
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5306
|
+
body: JSON.stringify(p),
|
|
5307
|
+
// Note: keepalive has 64KB limit like sendBeacon, so we don't use it for large payloads
|
|
5308
|
+
});
|
|
5309
|
+
} catch (err) {
|
|
5310
|
+
console.error('Failed to send exit request:', err);
|
|
5311
|
+
}
|
|
5110
5312
|
}
|
|
5111
5313
|
function showSubmitModal() {
|
|
5112
5314
|
const count = Object.keys(comments).length;
|
|
@@ -5122,11 +5324,11 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
5122
5324
|
}
|
|
5123
5325
|
document.getElementById('send-and-exit').addEventListener('click', showSubmitModal);
|
|
5124
5326
|
modalCancel.addEventListener('click', hideSubmitModal);
|
|
5125
|
-
function doSubmit() {
|
|
5327
|
+
async function doSubmit() {
|
|
5126
5328
|
globalComment = globalCommentInput.value;
|
|
5127
5329
|
savePromptPrefs();
|
|
5128
5330
|
hideSubmitModal();
|
|
5129
|
-
sendAndExit('button');
|
|
5331
|
+
await sendAndExit('button');
|
|
5130
5332
|
// Try to close window; if it fails (browser security), show completion message
|
|
5131
5333
|
setTimeout(() => {
|
|
5132
5334
|
window.close();
|
|
@@ -5918,8 +6120,46 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
5918
6120
|
.trim();
|
|
5919
6121
|
}
|
|
5920
6122
|
|
|
5921
|
-
// Helper: find matching source line for text
|
|
5922
|
-
|
|
6123
|
+
// Helper: find matching source line for text or element
|
|
6124
|
+
// If element is provided, also searches by media src attributes
|
|
6125
|
+
function findSourceLine(text, element = null) {
|
|
6126
|
+
// First, try to find by media src (images, videos) in the element
|
|
6127
|
+
if (element) {
|
|
6128
|
+
const mediaElements = element.querySelectorAll('img, video');
|
|
6129
|
+
for (const m of mediaElements) {
|
|
6130
|
+
const src = m.getAttribute('src');
|
|
6131
|
+
if (!src) continue;
|
|
6132
|
+
|
|
6133
|
+
const fileName = src.split('/').pop();
|
|
6134
|
+
const alt = m.getAttribute('alt') || m.getAttribute('data-alt') || m.getAttribute('title') || '';
|
|
6135
|
+
|
|
6136
|
+
// Search for lines containing this media file ( syntax)
|
|
6137
|
+
// Prioritize exact match with alt text
|
|
6138
|
+
let bestMatch = -1;
|
|
6139
|
+
for (let i = 0; i < DATA.length; i++) {
|
|
6140
|
+
const lineText = (DATA[i][0] || '');
|
|
6141
|
+
if (!lineText.includes(fileName)) continue;
|
|
6142
|
+
|
|
6143
|
+
// Check if it's an image/video markdown syntax
|
|
6144
|
+
const match = lineText.match(/!\\[([^\\]]*)\\]\\(([^)]+)\\)/);
|
|
6145
|
+
if (!match) continue;
|
|
6146
|
+
|
|
6147
|
+
const [, mdAlt, mdPath] = match;
|
|
6148
|
+
|
|
6149
|
+
// Exact path match
|
|
6150
|
+
if (mdPath.includes(fileName)) {
|
|
6151
|
+
// If alt text matches exactly, this is definitely the right one
|
|
6152
|
+
if (alt && mdAlt && mdAlt === alt) {
|
|
6153
|
+
return i + 1;
|
|
6154
|
+
}
|
|
6155
|
+
// Otherwise, remember as fallback (prefer first match)
|
|
6156
|
+
if (bestMatch === -1) bestMatch = i + 1;
|
|
6157
|
+
}
|
|
6158
|
+
}
|
|
6159
|
+
if (bestMatch !== -1) return bestMatch;
|
|
6160
|
+
}
|
|
6161
|
+
}
|
|
6162
|
+
|
|
5923
6163
|
if (!text) return -1;
|
|
5924
6164
|
const normalized = text.trim().replace(/\\s+/g, ' ').slice(0, 100);
|
|
5925
6165
|
if (!normalized) return -1;
|
|
@@ -6159,9 +6399,9 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
6159
6399
|
const target = e.target.closest('p, h1, h2, h3, h4, h5, h6, li, blockquote, td, th');
|
|
6160
6400
|
if (!target) return;
|
|
6161
6401
|
|
|
6162
|
-
// Use table-specific search for table cells
|
|
6402
|
+
// Use table-specific search for table cells, otherwise use element-aware search
|
|
6163
6403
|
const isTableCell = target.tagName === 'TD' || target.tagName === 'TH';
|
|
6164
|
-
const line = isTableCell ? findTableSourceLine(target.textContent) : findSourceLine(target.textContent);
|
|
6404
|
+
const line = isTableCell ? findTableSourceLine(target.textContent) : findSourceLine(target.textContent, target);
|
|
6165
6405
|
if (line <= 0) return;
|
|
6166
6406
|
|
|
6167
6407
|
e.preventDefault();
|
|
@@ -6496,6 +6736,79 @@ function removeLockFile(filePath) {
|
|
|
6496
6736
|
}
|
|
6497
6737
|
}
|
|
6498
6738
|
|
|
6739
|
+
// --- Image Saving Helper ---
|
|
6740
|
+
function saveBase64Image(base64Data, baseDir) {
|
|
6741
|
+
try {
|
|
6742
|
+
// Parse data URL: data:image/png;base64,iVBORw0K...
|
|
6743
|
+
const match = base64Data.match(/^data:image\/(\w+);base64,(.+)$/);
|
|
6744
|
+
if (!match) return null;
|
|
6745
|
+
|
|
6746
|
+
const ext = match[1] === 'jpeg' ? 'jpg' : match[1];
|
|
6747
|
+
const data = match[2];
|
|
6748
|
+
|
|
6749
|
+
// Create ./tmp/ directory if it doesn't exist
|
|
6750
|
+
const tmpDir = path.join(baseDir, 'tmp');
|
|
6751
|
+
if (!fs.existsSync(tmpDir)) {
|
|
6752
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
6753
|
+
}
|
|
6754
|
+
|
|
6755
|
+
// Generate unique filename using timestamp and random string
|
|
6756
|
+
const timestamp = Date.now();
|
|
6757
|
+
const random = crypto.randomBytes(4).toString('hex');
|
|
6758
|
+
const filename = `reviw-${timestamp}-${random}.${ext}`;
|
|
6759
|
+
const filepath = path.join(tmpDir, filename);
|
|
6760
|
+
|
|
6761
|
+
// Decode base64 and save to file
|
|
6762
|
+
const buffer = Buffer.from(data, 'base64');
|
|
6763
|
+
fs.writeFileSync(filepath, buffer);
|
|
6764
|
+
|
|
6765
|
+
// Return relative path (./tmp/filename)
|
|
6766
|
+
return `./tmp/${filename}`;
|
|
6767
|
+
} catch (err) {
|
|
6768
|
+
console.error('Failed to save image:', err);
|
|
6769
|
+
return null;
|
|
6770
|
+
}
|
|
6771
|
+
}
|
|
6772
|
+
|
|
6773
|
+
// Process payload images: save to disk and replace base64 with paths
|
|
6774
|
+
function processPayloadImages(payload, baseDir) {
|
|
6775
|
+
// Process summaryImages (images attached to summary)
|
|
6776
|
+
if (payload.summaryImages && Array.isArray(payload.summaryImages)) {
|
|
6777
|
+
const imagePaths = [];
|
|
6778
|
+
for (const base64 of payload.summaryImages) {
|
|
6779
|
+
const savedPath = saveBase64Image(base64, baseDir);
|
|
6780
|
+
if (savedPath) {
|
|
6781
|
+
imagePaths.push(savedPath);
|
|
6782
|
+
}
|
|
6783
|
+
}
|
|
6784
|
+
// Replace base64 array with file paths (keeps same key position)
|
|
6785
|
+
payload.summaryImages = imagePaths.length > 0 ? imagePaths : undefined;
|
|
6786
|
+
if (!payload.summaryImages) delete payload.summaryImages;
|
|
6787
|
+
}
|
|
6788
|
+
|
|
6789
|
+
// Process comment images
|
|
6790
|
+
if (payload.comments && Array.isArray(payload.comments)) {
|
|
6791
|
+
for (const comment of payload.comments) {
|
|
6792
|
+
if (comment.image) {
|
|
6793
|
+
const savedPath = saveBase64Image(comment.image, baseDir);
|
|
6794
|
+
if (savedPath) {
|
|
6795
|
+
comment.imagePath = savedPath;
|
|
6796
|
+
}
|
|
6797
|
+
delete comment.image; // Remove base64 data from comment
|
|
6798
|
+
}
|
|
6799
|
+
}
|
|
6800
|
+
}
|
|
6801
|
+
|
|
6802
|
+
// Add image reading instruction if any images are attached
|
|
6803
|
+
const hasCommentImages = payload.comments?.some(c => c.imagePath);
|
|
6804
|
+
const hasSummaryImages = payload.summaryImages?.length > 0;
|
|
6805
|
+
if (hasCommentImages || hasSummaryImages) {
|
|
6806
|
+
payload._imageReadingNote = "MANDATORY: You MUST read ALL images (imagePath and summaryImages) using the Read tool. Skipping image reading is PROHIBITED.";
|
|
6807
|
+
}
|
|
6808
|
+
|
|
6809
|
+
return payload;
|
|
6810
|
+
}
|
|
6811
|
+
|
|
6499
6812
|
function checkExistingServer(filePath) {
|
|
6500
6813
|
try {
|
|
6501
6814
|
const lockPath = getLockFilePath(filePath);
|
|
@@ -6871,6 +7184,10 @@ function createFileServer(filePath, fileIndex = 0) {
|
|
|
6871
7184
|
if (raw && raw.trim()) {
|
|
6872
7185
|
payload = JSON.parse(raw);
|
|
6873
7186
|
}
|
|
7187
|
+
// Process images: save to ./tmp/ and replace base64 with paths
|
|
7188
|
+
if (payload) {
|
|
7189
|
+
payload = processPayloadImages(payload, ctx.baseDir);
|
|
7190
|
+
}
|
|
6874
7191
|
// Save to file-based history (only if there are comments)
|
|
6875
7192
|
if (payload && (payload.comments?.length > 0 || payload.submitComment)) {
|
|
6876
7193
|
saveHistoryToFile(ctx.filePath, payload);
|
|
@@ -7137,6 +7454,11 @@ function createDiffServer(diffContent) {
|
|
|
7137
7454
|
if (raw && raw.trim()) {
|
|
7138
7455
|
payload = JSON.parse(raw);
|
|
7139
7456
|
}
|
|
7457
|
+
// Process images: save to ./tmp/ and replace base64 with paths
|
|
7458
|
+
// For diff mode, use current working directory
|
|
7459
|
+
if (payload) {
|
|
7460
|
+
payload = processPayloadImages(payload, process.cwd());
|
|
7461
|
+
}
|
|
7140
7462
|
// Save to file-based history (only if there are comments)
|
|
7141
7463
|
// For diff mode, use relativePath as identifier
|
|
7142
7464
|
if (payload && (payload.comments?.length > 0 || payload.submitComment)) {
|