reviw 0.16.1 → 0.16.3
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/README.ja.md +2 -2
- package/README.md +2 -2
- package/cli.cjs +897 -37
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -286,8 +286,8 @@ npx reviw .artifacts/<feature>/REPORT.md
|
|
|
286
286
|
Playwrightを使用したブラウザ自動化ツールキット。
|
|
287
287
|
|
|
288
288
|
**機能:**
|
|
289
|
-
-
|
|
290
|
-
-
|
|
289
|
+
- TypeScript Playwright Test (`@playwright/test`)
|
|
290
|
+
- webServerサポート付きPlaywright設定
|
|
291
291
|
- スクリーンショットと動画キャプチャ
|
|
292
292
|
- コンソールログとネットワークリクエスト監視
|
|
293
293
|
- 高度なデバッグ用CDP統合
|
package/README.md
CHANGED
|
@@ -318,8 +318,8 @@ npx reviw .artifacts/<feature>/REPORT.md
|
|
|
318
318
|
Browser automation toolkit using Playwright.
|
|
319
319
|
|
|
320
320
|
**Features:**
|
|
321
|
-
-
|
|
322
|
-
-
|
|
321
|
+
- TypeScript Playwright Test (`@playwright/test`)
|
|
322
|
+
- Playwright configuration with webServer support
|
|
323
323
|
- Screenshot and video capture
|
|
324
324
|
- Console log and network request monitoring
|
|
325
325
|
- CDP integration for advanced debugging
|
package/cli.cjs
CHANGED
|
@@ -825,9 +825,10 @@ function serializeForScript(value) {
|
|
|
825
825
|
.replace(/\$\{/g, "\\${");
|
|
826
826
|
}
|
|
827
827
|
|
|
828
|
-
function diffHtmlTemplate(diffData) {
|
|
828
|
+
function diffHtmlTemplate(diffData, history = []) {
|
|
829
829
|
const { rows, projectRoot, relativePath } = diffData;
|
|
830
830
|
const serialized = serializeForScript(rows);
|
|
831
|
+
const historyJson = serializeForScript(history);
|
|
831
832
|
const fileCount = rows.filter((r) => r.type === "file").length;
|
|
832
833
|
|
|
833
834
|
return `<!doctype html>
|
|
@@ -1235,6 +1236,221 @@ function diffHtmlTemplate(diffData) {
|
|
|
1235
1236
|
}
|
|
1236
1237
|
.no-diff h2 { font-size: 20px; margin: 0 0 8px; color: var(--text); }
|
|
1237
1238
|
.no-diff p { font-size: 14px; margin: 0; }
|
|
1239
|
+
|
|
1240
|
+
/* History Panel - Push layout */
|
|
1241
|
+
body { transition: margin-right 0.25s ease; }
|
|
1242
|
+
body.history-open { margin-right: 320px; }
|
|
1243
|
+
body.history-open header { right: 320px; }
|
|
1244
|
+
header { transition: right 0.25s ease; right: 0; }
|
|
1245
|
+
|
|
1246
|
+
.history-toggle {
|
|
1247
|
+
background: var(--selected-bg);
|
|
1248
|
+
color: var(--text);
|
|
1249
|
+
border: 1px solid var(--border);
|
|
1250
|
+
border-radius: 6px;
|
|
1251
|
+
padding: 6px 8px;
|
|
1252
|
+
font-size: 14px;
|
|
1253
|
+
cursor: pointer;
|
|
1254
|
+
width: 34px;
|
|
1255
|
+
height: 34px;
|
|
1256
|
+
display: flex;
|
|
1257
|
+
align-items: center;
|
|
1258
|
+
justify-content: center;
|
|
1259
|
+
}
|
|
1260
|
+
.history-toggle:hover { background: var(--border); }
|
|
1261
|
+
.history-panel {
|
|
1262
|
+
position: fixed;
|
|
1263
|
+
top: 0;
|
|
1264
|
+
right: 0;
|
|
1265
|
+
width: 320px;
|
|
1266
|
+
height: 100vh;
|
|
1267
|
+
background: var(--panel);
|
|
1268
|
+
border-left: 1px solid var(--border);
|
|
1269
|
+
z-index: 90;
|
|
1270
|
+
transform: translateX(100%);
|
|
1271
|
+
transition: transform 0.25s ease;
|
|
1272
|
+
display: flex;
|
|
1273
|
+
flex-direction: column;
|
|
1274
|
+
}
|
|
1275
|
+
.history-panel.open { transform: translateX(0); }
|
|
1276
|
+
.history-panel-header {
|
|
1277
|
+
padding: 16px;
|
|
1278
|
+
border-bottom: 1px solid var(--border);
|
|
1279
|
+
display: flex;
|
|
1280
|
+
justify-content: space-between;
|
|
1281
|
+
align-items: center;
|
|
1282
|
+
}
|
|
1283
|
+
.history-panel-header h3 { margin: 0; font-size: 14px; font-weight: 600; }
|
|
1284
|
+
.history-panel-close {
|
|
1285
|
+
background: transparent;
|
|
1286
|
+
border: none;
|
|
1287
|
+
color: var(--muted);
|
|
1288
|
+
cursor: pointer;
|
|
1289
|
+
font-size: 18px;
|
|
1290
|
+
padding: 4px;
|
|
1291
|
+
}
|
|
1292
|
+
.history-panel-close:hover { color: var(--text); }
|
|
1293
|
+
.history-panel-body {
|
|
1294
|
+
flex: 1;
|
|
1295
|
+
overflow-y: auto;
|
|
1296
|
+
padding: 12px;
|
|
1297
|
+
}
|
|
1298
|
+
.history-empty {
|
|
1299
|
+
color: var(--muted);
|
|
1300
|
+
font-size: 13px;
|
|
1301
|
+
text-align: center;
|
|
1302
|
+
padding: 40px 20px;
|
|
1303
|
+
}
|
|
1304
|
+
.history-date-group {
|
|
1305
|
+
margin-bottom: 16px;
|
|
1306
|
+
}
|
|
1307
|
+
.history-date {
|
|
1308
|
+
font-size: 11px;
|
|
1309
|
+
font-weight: 600;
|
|
1310
|
+
color: var(--muted);
|
|
1311
|
+
margin-bottom: 8px;
|
|
1312
|
+
text-transform: uppercase;
|
|
1313
|
+
}
|
|
1314
|
+
.history-item {
|
|
1315
|
+
background: var(--bg);
|
|
1316
|
+
border: 1px solid var(--border);
|
|
1317
|
+
border-radius: 6px;
|
|
1318
|
+
margin-bottom: 8px;
|
|
1319
|
+
overflow: hidden;
|
|
1320
|
+
}
|
|
1321
|
+
.history-item-header {
|
|
1322
|
+
display: flex;
|
|
1323
|
+
justify-content: space-between;
|
|
1324
|
+
align-items: center;
|
|
1325
|
+
padding: 8px 10px;
|
|
1326
|
+
background: var(--selected-bg);
|
|
1327
|
+
cursor: pointer;
|
|
1328
|
+
}
|
|
1329
|
+
.history-item-header:hover { background: var(--border); }
|
|
1330
|
+
.history-item-file {
|
|
1331
|
+
font-size: 12px;
|
|
1332
|
+
font-weight: 600;
|
|
1333
|
+
color: var(--text);
|
|
1334
|
+
white-space: nowrap;
|
|
1335
|
+
overflow: hidden;
|
|
1336
|
+
text-overflow: ellipsis;
|
|
1337
|
+
max-width: 180px;
|
|
1338
|
+
}
|
|
1339
|
+
.history-item-time {
|
|
1340
|
+
font-size: 10px;
|
|
1341
|
+
color: var(--muted);
|
|
1342
|
+
}
|
|
1343
|
+
.history-item-body {
|
|
1344
|
+
display: none;
|
|
1345
|
+
padding: 10px;
|
|
1346
|
+
font-size: 12px;
|
|
1347
|
+
border-top: 1px solid var(--border);
|
|
1348
|
+
}
|
|
1349
|
+
.history-item.expanded .history-item-body { display: block; }
|
|
1350
|
+
.history-summary {
|
|
1351
|
+
color: var(--text);
|
|
1352
|
+
margin-bottom: 8px;
|
|
1353
|
+
padding-bottom: 8px;
|
|
1354
|
+
border-bottom: 1px solid var(--border);
|
|
1355
|
+
}
|
|
1356
|
+
.history-summary-label {
|
|
1357
|
+
font-size: 10px;
|
|
1358
|
+
font-weight: 600;
|
|
1359
|
+
color: var(--muted);
|
|
1360
|
+
margin-bottom: 4px;
|
|
1361
|
+
}
|
|
1362
|
+
.history-summary-text {
|
|
1363
|
+
white-space: pre-wrap;
|
|
1364
|
+
line-height: 1.4;
|
|
1365
|
+
}
|
|
1366
|
+
.history-comments-label {
|
|
1367
|
+
font-size: 10px;
|
|
1368
|
+
font-weight: 600;
|
|
1369
|
+
color: var(--muted);
|
|
1370
|
+
margin-bottom: 6px;
|
|
1371
|
+
}
|
|
1372
|
+
.history-comment {
|
|
1373
|
+
padding: 6px 0;
|
|
1374
|
+
border-bottom: 1px solid var(--border);
|
|
1375
|
+
}
|
|
1376
|
+
.history-comment:last-child { border-bottom: none; }
|
|
1377
|
+
.history-comment-line {
|
|
1378
|
+
font-size: 10px;
|
|
1379
|
+
color: var(--accent);
|
|
1380
|
+
font-weight: 600;
|
|
1381
|
+
margin-bottom: 2px;
|
|
1382
|
+
}
|
|
1383
|
+
.history-comment-quote {
|
|
1384
|
+
background: rgba(0, 0, 0, 0.3);
|
|
1385
|
+
border-left: 2px solid var(--accent);
|
|
1386
|
+
padding: 4px 8px;
|
|
1387
|
+
margin: 4px 0;
|
|
1388
|
+
font-family: 'SF Mono', Monaco, Consolas, monospace;
|
|
1389
|
+
font-size: 11px;
|
|
1390
|
+
color: var(--muted);
|
|
1391
|
+
white-space: pre-wrap;
|
|
1392
|
+
word-break: break-all;
|
|
1393
|
+
max-height: 80px;
|
|
1394
|
+
overflow-y: auto;
|
|
1395
|
+
}
|
|
1396
|
+
.history-comment-text {
|
|
1397
|
+
color: var(--text);
|
|
1398
|
+
line-height: 1.4;
|
|
1399
|
+
white-space: pre-wrap;
|
|
1400
|
+
}
|
|
1401
|
+
.history-badge {
|
|
1402
|
+
display: inline-block;
|
|
1403
|
+
background: var(--accent);
|
|
1404
|
+
color: var(--text-inverse);
|
|
1405
|
+
font-size: 10px;
|
|
1406
|
+
padding: 2px 6px;
|
|
1407
|
+
border-radius: 10px;
|
|
1408
|
+
margin-left: 6px;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
/* Past comment indicator on lines */
|
|
1412
|
+
.diff-line[data-has-history]::before {
|
|
1413
|
+
content: '💬';
|
|
1414
|
+
position: absolute;
|
|
1415
|
+
left: 4px;
|
|
1416
|
+
font-size: 10px;
|
|
1417
|
+
opacity: 0.6;
|
|
1418
|
+
}
|
|
1419
|
+
.diff-line { position: relative; }
|
|
1420
|
+
.past-comment-overlay {
|
|
1421
|
+
background: var(--selected-bg);
|
|
1422
|
+
border-left: 3px solid var(--muted);
|
|
1423
|
+
margin: 0;
|
|
1424
|
+
padding: 8px 12px;
|
|
1425
|
+
font-size: 11px;
|
|
1426
|
+
color: var(--muted);
|
|
1427
|
+
display: none;
|
|
1428
|
+
}
|
|
1429
|
+
.past-comment-overlay.visible { display: block; }
|
|
1430
|
+
.past-comment-header {
|
|
1431
|
+
display: flex;
|
|
1432
|
+
justify-content: space-between;
|
|
1433
|
+
align-items: center;
|
|
1434
|
+
margin-bottom: 4px;
|
|
1435
|
+
}
|
|
1436
|
+
.past-comment-date {
|
|
1437
|
+
font-size: 10px;
|
|
1438
|
+
color: var(--muted);
|
|
1439
|
+
}
|
|
1440
|
+
.past-comment-toggle {
|
|
1441
|
+
background: transparent;
|
|
1442
|
+
border: none;
|
|
1443
|
+
color: var(--muted);
|
|
1444
|
+
cursor: pointer;
|
|
1445
|
+
font-size: 10px;
|
|
1446
|
+
padding: 2px 4px;
|
|
1447
|
+
}
|
|
1448
|
+
.past-comment-toggle:hover { color: var(--text); }
|
|
1449
|
+
.past-comment-text {
|
|
1450
|
+
color: var(--text);
|
|
1451
|
+
white-space: pre-wrap;
|
|
1452
|
+
line-height: 1.4;
|
|
1453
|
+
}
|
|
1238
1454
|
</style>
|
|
1239
1455
|
</head>
|
|
1240
1456
|
<body>
|
|
@@ -1245,11 +1461,23 @@ function diffHtmlTemplate(diffData) {
|
|
|
1245
1461
|
<span class="pill">Comments <strong id="comment-count">0</strong></span>
|
|
1246
1462
|
</div>
|
|
1247
1463
|
<div class="actions">
|
|
1464
|
+
<button class="history-toggle" id="history-toggle" title="Review History">☰</button>
|
|
1248
1465
|
<button class="theme-toggle" id="theme-toggle" title="Toggle theme"><span id="theme-icon">🌙</span></button>
|
|
1249
1466
|
<button id="send-and-exit">Submit & Exit</button>
|
|
1250
1467
|
</div>
|
|
1251
1468
|
</header>
|
|
1252
1469
|
|
|
1470
|
+
<!-- History Panel -->
|
|
1471
|
+
<aside class="history-panel" id="history-panel">
|
|
1472
|
+
<div class="history-panel-header">
|
|
1473
|
+
<h3>📜 Review History</h3>
|
|
1474
|
+
<button class="history-panel-close" id="history-panel-close">✕</button>
|
|
1475
|
+
</div>
|
|
1476
|
+
<div class="history-panel-body" id="history-panel-body">
|
|
1477
|
+
<div class="history-empty">No review history yet.</div>
|
|
1478
|
+
</div>
|
|
1479
|
+
</aside>
|
|
1480
|
+
|
|
1253
1481
|
<div class="wrap">
|
|
1254
1482
|
${rows.length === 0 ? '<div class="no-diff"><h2>No changes</h2><p>Working tree is clean</p></div>' : '<div class="diff-container" id="diff-container"></div>'}
|
|
1255
1483
|
</div>
|
|
@@ -1283,11 +1511,11 @@ function diffHtmlTemplate(diffData) {
|
|
|
1283
1511
|
<label for="global-comment">Overall comment (optional)</label>
|
|
1284
1512
|
<textarea id="global-comment" placeholder="Add a summary or overall feedback..."></textarea>
|
|
1285
1513
|
<div class="modal-checkboxes">
|
|
1286
|
-
<label><input type="checkbox" id="prompt-subagents" checked />
|
|
1287
|
-
<label><input type="checkbox" id="prompt-reviw" checked /> Open in REVIW next time
|
|
1288
|
-
<label><input type="checkbox" id="prompt-screenshots" checked /> Update all screenshots
|
|
1289
|
-
<label><input type="checkbox" id="prompt-user-feedback-todo" checked /> Add
|
|
1290
|
-
<label><input type="checkbox" id="prompt-deep-dive" checked />
|
|
1514
|
+
<label><input type="checkbox" id="prompt-subagents" checked /> 🤖 Delegate to sub-agents (implement, verify, report)</label>
|
|
1515
|
+
<label><input type="checkbox" id="prompt-reviw" checked /> 👁️ Open in REVIW next time</label>
|
|
1516
|
+
<label><input type="checkbox" id="prompt-screenshots" checked /> 📸 Update all screenshots/videos</label>
|
|
1517
|
+
<label><input type="checkbox" id="prompt-user-feedback-todo" checked /> ✅ Add feedback to Todo (require approval)</label>
|
|
1518
|
+
<label><input type="checkbox" id="prompt-deep-dive" checked /> 🔍 Probe requirements before implementing</label>
|
|
1291
1519
|
</div>
|
|
1292
1520
|
<div class="modal-actions">
|
|
1293
1521
|
<button id="modal-cancel">Cancel</button>
|
|
@@ -1298,8 +1526,9 @@ function diffHtmlTemplate(diffData) {
|
|
|
1298
1526
|
|
|
1299
1527
|
<script>
|
|
1300
1528
|
const DATA = ${serialized};
|
|
1301
|
-
const FILE_NAME = ${serializeForScript(
|
|
1529
|
+
const FILE_NAME = ${serializeForScript(relativePath)};
|
|
1302
1530
|
const MODE = 'diff';
|
|
1531
|
+
const HISTORY_DATA = ${historyJson};
|
|
1303
1532
|
|
|
1304
1533
|
// Theme
|
|
1305
1534
|
(function initTheme() {
|
|
@@ -1319,6 +1548,166 @@ function diffHtmlTemplate(diffData) {
|
|
|
1319
1548
|
});
|
|
1320
1549
|
})();
|
|
1321
1550
|
|
|
1551
|
+
// --- History Management ---
|
|
1552
|
+
// History is now server-side (file-based), HISTORY_DATA is provided by server
|
|
1553
|
+
|
|
1554
|
+
function loadHistory() {
|
|
1555
|
+
// Return server-provided history data
|
|
1556
|
+
return Array.isArray(HISTORY_DATA) ? HISTORY_DATA : [];
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
// saveToHistory is handled server-side via /exit endpoint
|
|
1560
|
+
function saveToHistory(payload) {
|
|
1561
|
+
// No-op on client - server saves history when receiving /exit
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
function formatDate(isoString) {
|
|
1565
|
+
const d = new Date(isoString);
|
|
1566
|
+
return d.toLocaleDateString('ja-JP', { year: 'numeric', month: '2-digit', day: '2-digit' });
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
function formatTime(isoString) {
|
|
1570
|
+
const d = new Date(isoString);
|
|
1571
|
+
return d.toLocaleTimeString('ja-JP', { hour: '2-digit', minute: '2-digit' });
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
function getBasename(filepath) {
|
|
1575
|
+
return filepath.split('/').pop() || filepath;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
function renderHistoryPanel() {
|
|
1579
|
+
const body = document.getElementById('history-panel-body');
|
|
1580
|
+
const history = loadHistory();
|
|
1581
|
+
if (history.length === 0) {
|
|
1582
|
+
body.innerHTML = '<div class="history-empty">No review history yet.</div>';
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
const grouped = {};
|
|
1587
|
+
history.forEach((item, idx) => {
|
|
1588
|
+
const date = formatDate(item.submittedAt);
|
|
1589
|
+
if (!grouped[date]) grouped[date] = [];
|
|
1590
|
+
grouped[date].push({ ...item, _idx: idx });
|
|
1591
|
+
});
|
|
1592
|
+
|
|
1593
|
+
let html = '';
|
|
1594
|
+
for (const date of Object.keys(grouped)) {
|
|
1595
|
+
html += \`<div class="history-date-group">
|
|
1596
|
+
<div class="history-date">\${date}</div>\`;
|
|
1597
|
+
for (const item of grouped[date]) {
|
|
1598
|
+
const commentCount = item.comments?.length || 0;
|
|
1599
|
+
html += \`<div class="history-item" data-idx="\${item._idx}">
|
|
1600
|
+
<div class="history-item-header">
|
|
1601
|
+
<span class="history-item-file">\${getBasename(item.file)}</span>
|
|
1602
|
+
<span class="history-item-time">\${formatTime(item.submittedAt)}<span class="history-badge">\${commentCount}</span></span>
|
|
1603
|
+
</div>
|
|
1604
|
+
<div class="history-item-body">\`;
|
|
1605
|
+
if (item.summary) {
|
|
1606
|
+
html += \`<div class="history-summary">
|
|
1607
|
+
<div class="history-summary-label">Summary</div>
|
|
1608
|
+
<div class="history-summary-text">\${escapeHtmlForHistory(item.summary)}</div>
|
|
1609
|
+
</div>\`;
|
|
1610
|
+
}
|
|
1611
|
+
if (commentCount > 0) {
|
|
1612
|
+
html += \`<div class="history-comments-label">Line Comments (\${commentCount})</div>\`;
|
|
1613
|
+
for (const c of item.comments) {
|
|
1614
|
+
const lineLabel = c.line ? \`L\${c.line}\${c.lineEnd ? '-' + c.lineEnd : ''}\` : (c.row != null ? \`L\${c.row}\` : '');
|
|
1615
|
+
const text = c.comment || c.text || '';
|
|
1616
|
+
// Support both direct content and context.content structures
|
|
1617
|
+
const content = c.content || c.context?.content || '';
|
|
1618
|
+
html += \`<div class="history-comment">
|
|
1619
|
+
<div class="history-comment-line">\${lineLabel}</div>\`;
|
|
1620
|
+
if (content) {
|
|
1621
|
+
html += \`<div class="history-comment-quote">\${escapeHtmlForHistory(content)}</div>\`;
|
|
1622
|
+
}
|
|
1623
|
+
html += \`<div class="history-comment-text">\${escapeHtmlForHistory(text)}</div>
|
|
1624
|
+
</div>\`;
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
html += \`</div></div>\`;
|
|
1628
|
+
}
|
|
1629
|
+
html += \`</div>\`;
|
|
1630
|
+
}
|
|
1631
|
+
body.innerHTML = html;
|
|
1632
|
+
|
|
1633
|
+
body.querySelectorAll('.history-item-header').forEach(header => {
|
|
1634
|
+
header.addEventListener('click', () => {
|
|
1635
|
+
header.parentElement.classList.toggle('expanded');
|
|
1636
|
+
});
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
function escapeHtmlForHistory(s) {
|
|
1641
|
+
if (!s) return '';
|
|
1642
|
+
return String(s).replace(/[&<>"]/g, c => ({'&':'&','<':'<','>':'>','"':'"'}[c] || c));
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
function getHistoryForFile(filename) {
|
|
1646
|
+
const history = loadHistory();
|
|
1647
|
+
return history.filter(h => h.file === filename || getBasename(h.file) === getBasename(filename));
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
function renderPastCommentsOnLines() {
|
|
1651
|
+
const fileHistory = getHistoryForFile(FILE_NAME);
|
|
1652
|
+
if (fileHistory.length === 0) return;
|
|
1653
|
+
|
|
1654
|
+
const lineComments = {};
|
|
1655
|
+
fileHistory.forEach(h => {
|
|
1656
|
+
if (!h.comments) return;
|
|
1657
|
+
h.comments.forEach(c => {
|
|
1658
|
+
const line = c.line || c.row;
|
|
1659
|
+
if (!line) return;
|
|
1660
|
+
if (!lineComments[line]) lineComments[line] = [];
|
|
1661
|
+
lineComments[line].push({
|
|
1662
|
+
date: formatDate(h.submittedAt),
|
|
1663
|
+
text: c.comment || c.text || ''
|
|
1664
|
+
});
|
|
1665
|
+
});
|
|
1666
|
+
});
|
|
1667
|
+
|
|
1668
|
+
Object.entries(lineComments).forEach(([line, comments]) => {
|
|
1669
|
+
const lineEl = document.querySelector('[data-row="' + line + '"]');
|
|
1670
|
+
if (lineEl && !lineEl.dataset.hasHistory) {
|
|
1671
|
+
lineEl.dataset.hasHistory = 'true';
|
|
1672
|
+
lineEl.title = comments.length + ' past comment(s) - click to view in History panel';
|
|
1673
|
+
}
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// History Panel Toggle
|
|
1678
|
+
(function initHistoryPanel() {
|
|
1679
|
+
const toggle = document.getElementById('history-toggle');
|
|
1680
|
+
const panel = document.getElementById('history-panel');
|
|
1681
|
+
const closeBtn = document.getElementById('history-panel-close');
|
|
1682
|
+
|
|
1683
|
+
function openPanel() {
|
|
1684
|
+
panel.classList.add('open');
|
|
1685
|
+
document.body.classList.add('history-open');
|
|
1686
|
+
renderHistoryPanel();
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
function closePanel() {
|
|
1690
|
+
panel.classList.remove('open');
|
|
1691
|
+
document.body.classList.remove('history-open');
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
toggle?.addEventListener('click', () => {
|
|
1695
|
+
if (panel.classList.contains('open')) {
|
|
1696
|
+
closePanel();
|
|
1697
|
+
} else {
|
|
1698
|
+
openPanel();
|
|
1699
|
+
}
|
|
1700
|
+
});
|
|
1701
|
+
|
|
1702
|
+
closeBtn?.addEventListener('click', closePanel);
|
|
1703
|
+
|
|
1704
|
+
document.addEventListener('keydown', (e) => {
|
|
1705
|
+
if (e.key === 'Escape' && panel.classList.contains('open')) {
|
|
1706
|
+
closePanel();
|
|
1707
|
+
}
|
|
1708
|
+
});
|
|
1709
|
+
})();
|
|
1710
|
+
|
|
1322
1711
|
const container = document.getElementById('diff-container');
|
|
1323
1712
|
const card = document.getElementById('comment-card');
|
|
1324
1713
|
const commentInput = document.getElementById('comment-input');
|
|
@@ -1628,7 +2017,18 @@ function diffHtmlTemplate(diffData) {
|
|
|
1628
2017
|
document.getElementById('clear-comment').addEventListener('click', clearCurrent);
|
|
1629
2018
|
document.getElementById('close-card').addEventListener('click', closeCard);
|
|
1630
2019
|
document.addEventListener('keydown', e => {
|
|
1631
|
-
if (e.key === 'Escape')
|
|
2020
|
+
if (e.key === 'Escape') {
|
|
2021
|
+
// Don't close card if any fullscreen overlay is open
|
|
2022
|
+
const imageOverlay = document.getElementById('image-fullscreen');
|
|
2023
|
+
const videoOverlay = document.getElementById('video-fullscreen');
|
|
2024
|
+
const mermaidOverlay = document.getElementById('mermaid-fullscreen');
|
|
2025
|
+
if (imageOverlay?.classList.contains('visible') ||
|
|
2026
|
+
videoOverlay?.classList.contains('visible') ||
|
|
2027
|
+
mermaidOverlay?.classList.contains('visible')) {
|
|
2028
|
+
return; // Let the fullscreen handlers handle ESC
|
|
2029
|
+
}
|
|
2030
|
+
closeCard();
|
|
2031
|
+
}
|
|
1632
2032
|
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') saveCurrent();
|
|
1633
2033
|
});
|
|
1634
2034
|
|
|
@@ -1657,13 +2057,13 @@ function diffHtmlTemplate(diffData) {
|
|
|
1657
2057
|
const modalSummary = document.getElementById('modal-summary');
|
|
1658
2058
|
const globalCommentInput = document.getElementById('global-comment');
|
|
1659
2059
|
|
|
1660
|
-
// Prompt checkboxes
|
|
2060
|
+
// Prompt checkboxes - text is the strong enforcement prompt for YAML output
|
|
1661
2061
|
const promptCheckboxes = [
|
|
1662
|
-
{ id: 'prompt-subagents', text: '
|
|
1663
|
-
{ id: 'prompt-reviw', text: '
|
|
1664
|
-
{ id: 'prompt-screenshots', text: '
|
|
1665
|
-
{ id: 'prompt-user-feedback-todo', text: "Add
|
|
1666
|
-
{ id: 'prompt-deep-dive', text: "Before
|
|
2062
|
+
{ id: 'prompt-subagents', text: 'MANDATORY: You MUST delegate ALL implementation, verification, and report creation to sub-agents. Direct execution on the main thread is PROHIBITED.' },
|
|
2063
|
+
{ id: 'prompt-reviw', text: 'REQUIRED: Before reporting completion, you MUST open the result in REVIW for user review. Skipping this step is NOT allowed.' },
|
|
2064
|
+
{ id: 'prompt-screenshots', text: 'MANDATORY: You MUST update ALL screenshots and videos as evidence. Reports without visual proof are REJECTED.' },
|
|
2065
|
+
{ id: 'prompt-user-feedback-todo', text: "STRICT RULE: Add ALL user feedback to the Todo list. You are FORBIDDEN from marking any item complete without explicit user approval." },
|
|
2066
|
+
{ id: 'prompt-deep-dive', text: "REQUIRED: Before ANY implementation, you MUST deeply probe the user's requirements using AskUserQuestion and EnterPlanMode. Starting implementation without thorough requirement analysis is PROHIBITED." }
|
|
1667
2067
|
];
|
|
1668
2068
|
const PROMPT_STORAGE_KEY = 'reviw-prompt-prefs';
|
|
1669
2069
|
|
|
@@ -1710,7 +2110,7 @@ function diffHtmlTemplate(diffData) {
|
|
|
1710
2110
|
}
|
|
1711
2111
|
|
|
1712
2112
|
function payload(reason) {
|
|
1713
|
-
const data = { file: FILE_NAME, mode: MODE, reason,
|
|
2113
|
+
const data = { file: FILE_NAME, mode: MODE, submittedBy: reason, submittedAt: new Date().toISOString(), comments: Object.values(comments) };
|
|
1714
2114
|
if (globalComment.trim()) data.summary = globalComment.trim();
|
|
1715
2115
|
const prompts = getSelectedPrompts();
|
|
1716
2116
|
if (prompts.length > 0) data.prompts = prompts;
|
|
@@ -1720,7 +2120,9 @@ function diffHtmlTemplate(diffData) {
|
|
|
1720
2120
|
if (sent) return;
|
|
1721
2121
|
sent = true;
|
|
1722
2122
|
clearStorage();
|
|
1723
|
-
|
|
2123
|
+
const p = payload(reason);
|
|
2124
|
+
saveToHistory(p);
|
|
2125
|
+
navigator.sendBeacon('/exit', new Blob([JSON.stringify(p)], { type: 'application/json' }));
|
|
1724
2126
|
}
|
|
1725
2127
|
function showSubmitModal() {
|
|
1726
2128
|
const count = Object.keys(comments).length;
|
|
@@ -1783,6 +2185,7 @@ function diffHtmlTemplate(diffData) {
|
|
|
1783
2185
|
|
|
1784
2186
|
renderDiff();
|
|
1785
2187
|
refreshList();
|
|
2188
|
+
renderPastCommentsOnLines();
|
|
1786
2189
|
|
|
1787
2190
|
// Recovery
|
|
1788
2191
|
(function checkRecovery() {
|
|
@@ -1802,11 +2205,12 @@ function diffHtmlTemplate(diffData) {
|
|
|
1802
2205
|
}
|
|
1803
2206
|
|
|
1804
2207
|
// --- HTML template ---------------------------------------------------------
|
|
1805
|
-
function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHtml, reviwQuestions = []) {
|
|
2208
|
+
function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHtml, reviwQuestions = [], history = []) {
|
|
1806
2209
|
const serialized = serializeForScript(dataRows);
|
|
1807
2210
|
const modeJson = serializeForScript(mode);
|
|
1808
2211
|
const titleJson = serializeForScript(relativePath); // Use relativePath as file identifier
|
|
1809
2212
|
const questionsJson = serializeForScript(reviwQuestions || []);
|
|
2213
|
+
const historyJson = serializeForScript(history);
|
|
1810
2214
|
const hasPreview = !!previewHtml;
|
|
1811
2215
|
return `<!doctype html>
|
|
1812
2216
|
<html lang="ja">
|
|
@@ -1979,7 +2383,7 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
1979
2383
|
position: sticky;
|
|
1980
2384
|
top: 0;
|
|
1981
2385
|
z-index: 3;
|
|
1982
|
-
background: var(--panel-solid);
|
|
2386
|
+
background: var(--panel-solid) !important;
|
|
1983
2387
|
color: var(--muted);
|
|
1984
2388
|
font-size: 12px;
|
|
1985
2389
|
text-align: center;
|
|
@@ -1989,6 +2393,9 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
1989
2393
|
white-space: nowrap;
|
|
1990
2394
|
transition: background 200ms ease;
|
|
1991
2395
|
}
|
|
2396
|
+
thead th:not(.selected) {
|
|
2397
|
+
background: var(--panel-solid) !important;
|
|
2398
|
+
}
|
|
1992
2399
|
thead th:first-child,
|
|
1993
2400
|
tbody th {
|
|
1994
2401
|
width: 28px;
|
|
@@ -2093,7 +2500,9 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
2093
2500
|
tr:nth-child(even) td:not(.selected):not(.has-comment) { background: var(--row-even); }
|
|
2094
2501
|
td:hover:not(.selected) { background: var(--hover-bg); box-shadow: inset 0 0 0 1px rgba(96,165,250,0.25); }
|
|
2095
2502
|
td.has-comment { background: rgba(34,197,94,0.12); box-shadow: inset 0 0 0 1px rgba(34,197,94,0.35); }
|
|
2096
|
-
td.selected,
|
|
2503
|
+
td.selected, tbody th.selected { background: rgba(99,102,241,0.22) !important; box-shadow: inset 0 0 0 1px rgba(99,102,241,0.45); }
|
|
2504
|
+
thead th.selected { background: #c7d2fe !important; box-shadow: inset 0 0 0 1px rgba(99,102,241,0.45); }
|
|
2505
|
+
[data-theme="dark"] thead th.selected { background: #3730a3 !important; }
|
|
2097
2506
|
body.dragging { user-select: none; cursor: crosshair; }
|
|
2098
2507
|
body.dragging td, body.dragging tbody th { cursor: crosshair; }
|
|
2099
2508
|
tbody th { cursor: pointer; }
|
|
@@ -2255,6 +2664,19 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
2255
2664
|
max-height: none;
|
|
2256
2665
|
overflow: visible;
|
|
2257
2666
|
}
|
|
2667
|
+
/* Ensure thead is opaque in md-right to prevent content showing through */
|
|
2668
|
+
.md-right thead th {
|
|
2669
|
+
background: var(--panel-solid) !important;
|
|
2670
|
+
}
|
|
2671
|
+
.md-right thead th.selected {
|
|
2672
|
+
background: #c7d2fe !important;
|
|
2673
|
+
}
|
|
2674
|
+
[data-theme="dark"] .md-right thead th {
|
|
2675
|
+
background: var(--panel-solid) !important;
|
|
2676
|
+
}
|
|
2677
|
+
[data-theme="dark"] .md-right thead th.selected {
|
|
2678
|
+
background: #3730a3 !important;
|
|
2679
|
+
}
|
|
2258
2680
|
.md-preview h1, .md-preview h2, .md-preview h3, .md-preview h4 {
|
|
2259
2681
|
margin: 0.4em 0 0.2em;
|
|
2260
2682
|
}
|
|
@@ -3090,6 +3512,169 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
3090
3512
|
font-family: monospace;
|
|
3091
3513
|
}
|
|
3092
3514
|
.mermaid-error-toast.visible { display: block; }
|
|
3515
|
+
|
|
3516
|
+
/* History Panel - Push layout */
|
|
3517
|
+
body { transition: margin-right 0.25s ease; }
|
|
3518
|
+
body.history-open { margin-right: 320px; }
|
|
3519
|
+
body.history-open header { right: 320px; }
|
|
3520
|
+
header { transition: right 0.25s ease; right: 0; }
|
|
3521
|
+
|
|
3522
|
+
.history-toggle {
|
|
3523
|
+
background: var(--selected-bg);
|
|
3524
|
+
color: var(--text);
|
|
3525
|
+
border: 1px solid var(--border);
|
|
3526
|
+
border-radius: 6px;
|
|
3527
|
+
padding: 6px 8px;
|
|
3528
|
+
font-size: 14px;
|
|
3529
|
+
cursor: pointer;
|
|
3530
|
+
width: 34px;
|
|
3531
|
+
height: 34px;
|
|
3532
|
+
display: flex;
|
|
3533
|
+
align-items: center;
|
|
3534
|
+
justify-content: center;
|
|
3535
|
+
}
|
|
3536
|
+
.history-toggle:hover { background: var(--border); }
|
|
3537
|
+
.history-panel {
|
|
3538
|
+
position: fixed;
|
|
3539
|
+
top: 0;
|
|
3540
|
+
right: 0;
|
|
3541
|
+
width: 320px;
|
|
3542
|
+
height: 100vh;
|
|
3543
|
+
background: var(--panel-solid);
|
|
3544
|
+
border-left: 1px solid var(--border);
|
|
3545
|
+
z-index: 90;
|
|
3546
|
+
transform: translateX(100%);
|
|
3547
|
+
transition: transform 0.25s ease;
|
|
3548
|
+
display: flex;
|
|
3549
|
+
flex-direction: column;
|
|
3550
|
+
}
|
|
3551
|
+
.history-panel.open { transform: translateX(0); }
|
|
3552
|
+
.history-panel-header {
|
|
3553
|
+
padding: 16px;
|
|
3554
|
+
border-bottom: 1px solid var(--border);
|
|
3555
|
+
display: flex;
|
|
3556
|
+
justify-content: space-between;
|
|
3557
|
+
align-items: center;
|
|
3558
|
+
}
|
|
3559
|
+
.history-panel-header h3 { margin: 0; font-size: 14px; font-weight: 600; }
|
|
3560
|
+
.history-panel-close {
|
|
3561
|
+
background: transparent;
|
|
3562
|
+
border: none;
|
|
3563
|
+
color: var(--muted);
|
|
3564
|
+
cursor: pointer;
|
|
3565
|
+
font-size: 18px;
|
|
3566
|
+
padding: 4px;
|
|
3567
|
+
}
|
|
3568
|
+
.history-panel-close:hover { color: var(--text); }
|
|
3569
|
+
.history-panel-body {
|
|
3570
|
+
flex: 1;
|
|
3571
|
+
overflow-y: auto;
|
|
3572
|
+
padding: 12px;
|
|
3573
|
+
}
|
|
3574
|
+
.history-empty {
|
|
3575
|
+
color: var(--muted);
|
|
3576
|
+
font-size: 13px;
|
|
3577
|
+
text-align: center;
|
|
3578
|
+
padding: 40px 20px;
|
|
3579
|
+
}
|
|
3580
|
+
.history-date-group { margin-bottom: 16px; }
|
|
3581
|
+
.history-date {
|
|
3582
|
+
font-size: 11px;
|
|
3583
|
+
font-weight: 600;
|
|
3584
|
+
color: var(--muted);
|
|
3585
|
+
margin-bottom: 8px;
|
|
3586
|
+
text-transform: uppercase;
|
|
3587
|
+
}
|
|
3588
|
+
.history-item {
|
|
3589
|
+
background: var(--bg);
|
|
3590
|
+
border: 1px solid var(--border);
|
|
3591
|
+
border-radius: 6px;
|
|
3592
|
+
margin-bottom: 8px;
|
|
3593
|
+
overflow: hidden;
|
|
3594
|
+
}
|
|
3595
|
+
.history-item-header {
|
|
3596
|
+
display: flex;
|
|
3597
|
+
justify-content: space-between;
|
|
3598
|
+
align-items: center;
|
|
3599
|
+
padding: 8px 10px;
|
|
3600
|
+
background: var(--selected-bg);
|
|
3601
|
+
cursor: pointer;
|
|
3602
|
+
}
|
|
3603
|
+
.history-item-header:hover { background: var(--hover-bg); }
|
|
3604
|
+
.history-item-file {
|
|
3605
|
+
font-size: 12px;
|
|
3606
|
+
font-weight: 600;
|
|
3607
|
+
color: var(--text);
|
|
3608
|
+
white-space: nowrap;
|
|
3609
|
+
overflow: hidden;
|
|
3610
|
+
text-overflow: ellipsis;
|
|
3611
|
+
max-width: 180px;
|
|
3612
|
+
}
|
|
3613
|
+
.history-item-time { font-size: 10px; color: var(--muted); }
|
|
3614
|
+
.history-item-body {
|
|
3615
|
+
display: none;
|
|
3616
|
+
padding: 10px;
|
|
3617
|
+
font-size: 12px;
|
|
3618
|
+
border-top: 1px solid var(--border);
|
|
3619
|
+
}
|
|
3620
|
+
.history-item.expanded .history-item-body { display: block; }
|
|
3621
|
+
.history-summary {
|
|
3622
|
+
color: var(--text);
|
|
3623
|
+
margin-bottom: 8px;
|
|
3624
|
+
padding-bottom: 8px;
|
|
3625
|
+
border-bottom: 1px solid var(--border);
|
|
3626
|
+
}
|
|
3627
|
+
.history-summary-label {
|
|
3628
|
+
font-size: 10px;
|
|
3629
|
+
font-weight: 600;
|
|
3630
|
+
color: var(--muted);
|
|
3631
|
+
margin-bottom: 4px;
|
|
3632
|
+
}
|
|
3633
|
+
.history-summary-text { white-space: pre-wrap; line-height: 1.4; }
|
|
3634
|
+
.history-comments-label {
|
|
3635
|
+
font-size: 10px;
|
|
3636
|
+
font-weight: 600;
|
|
3637
|
+
color: var(--muted);
|
|
3638
|
+
margin-bottom: 6px;
|
|
3639
|
+
}
|
|
3640
|
+
.history-comment {
|
|
3641
|
+
padding: 6px 0;
|
|
3642
|
+
border-bottom: 1px solid var(--border);
|
|
3643
|
+
}
|
|
3644
|
+
.history-comment:last-child { border-bottom: none; }
|
|
3645
|
+
.history-comment-line {
|
|
3646
|
+
font-size: 10px;
|
|
3647
|
+
color: var(--accent);
|
|
3648
|
+
font-weight: 600;
|
|
3649
|
+
margin-bottom: 2px;
|
|
3650
|
+
}
|
|
3651
|
+
.history-comment-quote {
|
|
3652
|
+
background: rgba(0, 0, 0, 0.3);
|
|
3653
|
+
border-left: 2px solid var(--accent);
|
|
3654
|
+
padding: 4px 8px;
|
|
3655
|
+
margin: 4px 0;
|
|
3656
|
+
font-family: 'SF Mono', Monaco, Consolas, monospace;
|
|
3657
|
+
font-size: 11px;
|
|
3658
|
+
color: var(--muted);
|
|
3659
|
+
white-space: pre-wrap;
|
|
3660
|
+
word-break: break-all;
|
|
3661
|
+
max-height: 80px;
|
|
3662
|
+
overflow-y: auto;
|
|
3663
|
+
}
|
|
3664
|
+
.history-comment-text {
|
|
3665
|
+
color: var(--text);
|
|
3666
|
+
line-height: 1.4;
|
|
3667
|
+
white-space: pre-wrap;
|
|
3668
|
+
}
|
|
3669
|
+
.history-badge {
|
|
3670
|
+
display: inline-block;
|
|
3671
|
+
background: var(--accent);
|
|
3672
|
+
color: var(--text-inverse);
|
|
3673
|
+
font-size: 10px;
|
|
3674
|
+
padding: 2px 6px;
|
|
3675
|
+
border-radius: 10px;
|
|
3676
|
+
margin-left: 6px;
|
|
3677
|
+
}
|
|
3093
3678
|
</style>
|
|
3094
3679
|
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
|
|
3095
3680
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/styles/github-dark.min.css" id="hljs-theme-dark">
|
|
@@ -3104,6 +3689,7 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
3104
3689
|
<span class="pill">Comments <strong id="comment-count">0</strong></span>
|
|
3105
3690
|
</div>
|
|
3106
3691
|
<div class="actions">
|
|
3692
|
+
<button class="history-toggle" id="history-toggle" title="Review History">☰</button>
|
|
3107
3693
|
<button class="theme-toggle" id="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
|
|
3108
3694
|
<span id="theme-icon">🌙</span>
|
|
3109
3695
|
</button>
|
|
@@ -3111,6 +3697,17 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
3111
3697
|
</div>
|
|
3112
3698
|
</header>
|
|
3113
3699
|
|
|
3700
|
+
<!-- History Panel -->
|
|
3701
|
+
<aside class="history-panel" id="history-panel">
|
|
3702
|
+
<div class="history-panel-header">
|
|
3703
|
+
<h3>📜 Review History</h3>
|
|
3704
|
+
<button class="history-panel-close" id="history-panel-close">✕</button>
|
|
3705
|
+
</div>
|
|
3706
|
+
<div class="history-panel-body" id="history-panel-body">
|
|
3707
|
+
<div class="history-empty">No review history yet.</div>
|
|
3708
|
+
</div>
|
|
3709
|
+
</aside>
|
|
3710
|
+
|
|
3114
3711
|
<div class="wrap">
|
|
3115
3712
|
${
|
|
3116
3713
|
hasPreview && mode === "markdown"
|
|
@@ -3216,11 +3813,11 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
3216
3813
|
<label for="global-comment">Overall comment (optional)</label>
|
|
3217
3814
|
<textarea id="global-comment" placeholder="Add a summary or overall feedback..."></textarea>
|
|
3218
3815
|
<div class="modal-checkboxes">
|
|
3219
|
-
<label><input type="checkbox" id="prompt-subagents" checked />
|
|
3220
|
-
<label><input type="checkbox" id="prompt-reviw" checked /> Open in REVIW next time
|
|
3221
|
-
<label><input type="checkbox" id="prompt-screenshots" checked /> Update all screenshots
|
|
3222
|
-
<label><input type="checkbox" id="prompt-user-feedback-todo" checked /> Add
|
|
3223
|
-
<label><input type="checkbox" id="prompt-deep-dive" checked />
|
|
3816
|
+
<label><input type="checkbox" id="prompt-subagents" checked /> 🤖 Delegate to sub-agents (implement, verify, report)</label>
|
|
3817
|
+
<label><input type="checkbox" id="prompt-reviw" checked /> 👁️ Open in REVIW next time</label>
|
|
3818
|
+
<label><input type="checkbox" id="prompt-screenshots" checked /> 📸 Update all screenshots/videos</label>
|
|
3819
|
+
<label><input type="checkbox" id="prompt-user-feedback-todo" checked /> ✅ Add feedback to Todo (require approval)</label>
|
|
3820
|
+
<label><input type="checkbox" id="prompt-deep-dive" checked /> 🔍 Probe requirements before implementing</label>
|
|
3224
3821
|
</div>
|
|
3225
3822
|
<div class="modal-actions">
|
|
3226
3823
|
<button id="modal-cancel">Cancel</button>
|
|
@@ -3285,6 +3882,7 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
3285
3882
|
const FILE_NAME = ${titleJson};
|
|
3286
3883
|
const MODE = ${modeJson};
|
|
3287
3884
|
const REVIW_QUESTIONS = ${questionsJson};
|
|
3885
|
+
const HISTORY_DATA = ${historyJson};
|
|
3288
3886
|
|
|
3289
3887
|
// --- Theme Management ---
|
|
3290
3888
|
(function initTheme() {
|
|
@@ -3337,6 +3935,134 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
3337
3935
|
themeToggle.addEventListener('click', toggleTheme);
|
|
3338
3936
|
})();
|
|
3339
3937
|
|
|
3938
|
+
// --- History Management ---
|
|
3939
|
+
// History is now server-side (file-based), HISTORY_DATA is provided by server
|
|
3940
|
+
|
|
3941
|
+
function loadHistory() {
|
|
3942
|
+
// Return server-provided history data
|
|
3943
|
+
return Array.isArray(HISTORY_DATA) ? HISTORY_DATA : [];
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3946
|
+
// saveToHistory is handled server-side via /exit endpoint
|
|
3947
|
+
function saveToHistory(payload) {
|
|
3948
|
+
// No-op on client - server saves history when receiving /exit
|
|
3949
|
+
}
|
|
3950
|
+
|
|
3951
|
+
function formatDate(isoString) {
|
|
3952
|
+
const d = new Date(isoString);
|
|
3953
|
+
return d.toLocaleDateString('ja-JP', { year: 'numeric', month: '2-digit', day: '2-digit' });
|
|
3954
|
+
}
|
|
3955
|
+
|
|
3956
|
+
function formatTime(isoString) {
|
|
3957
|
+
const d = new Date(isoString);
|
|
3958
|
+
return d.toLocaleTimeString('ja-JP', { hour: '2-digit', minute: '2-digit' });
|
|
3959
|
+
}
|
|
3960
|
+
|
|
3961
|
+
function getBasename(filepath) {
|
|
3962
|
+
return filepath.split('/').pop() || filepath;
|
|
3963
|
+
}
|
|
3964
|
+
|
|
3965
|
+
function escapeHtmlForHistory(s) {
|
|
3966
|
+
if (!s) return '';
|
|
3967
|
+
return String(s).replace(/[&<>"]/g, c => ({'&':'&','<':'<','>':'>','"':'"'}[c] || c));
|
|
3968
|
+
}
|
|
3969
|
+
|
|
3970
|
+
function renderHistoryPanel() {
|
|
3971
|
+
const body = document.getElementById('history-panel-body');
|
|
3972
|
+
const history = loadHistory();
|
|
3973
|
+
if (history.length === 0) {
|
|
3974
|
+
body.innerHTML = '<div class="history-empty">No review history yet.</div>';
|
|
3975
|
+
return;
|
|
3976
|
+
}
|
|
3977
|
+
|
|
3978
|
+
const grouped = {};
|
|
3979
|
+
history.forEach((item, idx) => {
|
|
3980
|
+
const date = formatDate(item.submittedAt);
|
|
3981
|
+
if (!grouped[date]) grouped[date] = [];
|
|
3982
|
+
grouped[date].push({ ...item, _idx: idx });
|
|
3983
|
+
});
|
|
3984
|
+
|
|
3985
|
+
let html = '';
|
|
3986
|
+
for (const date of Object.keys(grouped)) {
|
|
3987
|
+
html += \`<div class="history-date-group">
|
|
3988
|
+
<div class="history-date">\${date}</div>\`;
|
|
3989
|
+
for (const item of grouped[date]) {
|
|
3990
|
+
const commentCount = item.comments?.length || 0;
|
|
3991
|
+
html += \`<div class="history-item" data-idx="\${item._idx}">
|
|
3992
|
+
<div class="history-item-header">
|
|
3993
|
+
<span class="history-item-file">\${escapeHtmlForHistory(getBasename(item.file))}</span>
|
|
3994
|
+
<span class="history-item-time">\${formatTime(item.submittedAt)}<span class="history-badge">\${commentCount}</span></span>
|
|
3995
|
+
</div>
|
|
3996
|
+
<div class="history-item-body">\`;
|
|
3997
|
+
if (item.summary) {
|
|
3998
|
+
html += \`<div class="history-summary">
|
|
3999
|
+
<div class="history-summary-label">Summary</div>
|
|
4000
|
+
<div class="history-summary-text">\${escapeHtmlForHistory(item.summary)}</div>
|
|
4001
|
+
</div>\`;
|
|
4002
|
+
}
|
|
4003
|
+
if (commentCount > 0) {
|
|
4004
|
+
html += \`<div class="history-comments-label">Line Comments (\${commentCount})</div>\`;
|
|
4005
|
+
for (const c of item.comments) {
|
|
4006
|
+
const lineLabel = c.line ? \`L\${c.line}\${c.lineEnd ? '-' + c.lineEnd : ''}\` : (c.row != null ? \`L\${c.row}\` : '');
|
|
4007
|
+
const text = c.comment || c.text || '';
|
|
4008
|
+
// Support both direct content and context.content structures
|
|
4009
|
+
const content = c.content || c.context?.content || c.value || '';
|
|
4010
|
+
html += \`<div class="history-comment">
|
|
4011
|
+
<div class="history-comment-line">\${lineLabel}</div>\`;
|
|
4012
|
+
if (content) {
|
|
4013
|
+
html += \`<div class="history-comment-quote">\${escapeHtmlForHistory(content)}</div>\`;
|
|
4014
|
+
}
|
|
4015
|
+
html += \`<div class="history-comment-text">\${escapeHtmlForHistory(text)}</div>
|
|
4016
|
+
</div>\`;
|
|
4017
|
+
}
|
|
4018
|
+
}
|
|
4019
|
+
html += \`</div></div>\`;
|
|
4020
|
+
}
|
|
4021
|
+
html += \`</div>\`;
|
|
4022
|
+
}
|
|
4023
|
+
body.innerHTML = html;
|
|
4024
|
+
|
|
4025
|
+
body.querySelectorAll('.history-item-header').forEach(header => {
|
|
4026
|
+
header.addEventListener('click', () => {
|
|
4027
|
+
header.parentElement.classList.toggle('expanded');
|
|
4028
|
+
});
|
|
4029
|
+
});
|
|
4030
|
+
}
|
|
4031
|
+
|
|
4032
|
+
// History Panel Toggle
|
|
4033
|
+
(function initHistoryPanel() {
|
|
4034
|
+
const toggle = document.getElementById('history-toggle');
|
|
4035
|
+
const panel = document.getElementById('history-panel');
|
|
4036
|
+
const closeBtn = document.getElementById('history-panel-close');
|
|
4037
|
+
|
|
4038
|
+
function openPanel() {
|
|
4039
|
+
panel.classList.add('open');
|
|
4040
|
+
document.body.classList.add('history-open');
|
|
4041
|
+
renderHistoryPanel();
|
|
4042
|
+
}
|
|
4043
|
+
|
|
4044
|
+
function closePanel() {
|
|
4045
|
+
panel.classList.remove('open');
|
|
4046
|
+
document.body.classList.remove('history-open');
|
|
4047
|
+
}
|
|
4048
|
+
|
|
4049
|
+
toggle?.addEventListener('click', () => {
|
|
4050
|
+
if (panel.classList.contains('open')) {
|
|
4051
|
+
closePanel();
|
|
4052
|
+
} else {
|
|
4053
|
+
openPanel();
|
|
4054
|
+
}
|
|
4055
|
+
});
|
|
4056
|
+
|
|
4057
|
+
closeBtn?.addEventListener('click', closePanel);
|
|
4058
|
+
|
|
4059
|
+
document.addEventListener('keydown', (e) => {
|
|
4060
|
+
if (e.key === 'Escape' && panel.classList.contains('open')) {
|
|
4061
|
+
closePanel();
|
|
4062
|
+
}
|
|
4063
|
+
});
|
|
4064
|
+
})();
|
|
4065
|
+
|
|
3340
4066
|
const tbody = document.getElementById('tbody');
|
|
3341
4067
|
const table = document.getElementById('csv-table');
|
|
3342
4068
|
const colgroup = document.getElementById('colgroup');
|
|
@@ -4083,7 +4809,18 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
4083
4809
|
document.getElementById('clear-comment').addEventListener('click', clearCurrent);
|
|
4084
4810
|
document.getElementById('close-card').addEventListener('click', closeCard);
|
|
4085
4811
|
document.addEventListener('keydown', (e) => {
|
|
4086
|
-
if (e.key === 'Escape')
|
|
4812
|
+
if (e.key === 'Escape') {
|
|
4813
|
+
// Don't close card if any fullscreen overlay is open
|
|
4814
|
+
const imageOverlay = document.getElementById('image-fullscreen');
|
|
4815
|
+
const videoOverlay = document.getElementById('video-fullscreen');
|
|
4816
|
+
const mermaidOverlay = document.getElementById('mermaid-fullscreen');
|
|
4817
|
+
if (imageOverlay?.classList.contains('visible') ||
|
|
4818
|
+
videoOverlay?.classList.contains('visible') ||
|
|
4819
|
+
mermaidOverlay?.classList.contains('visible')) {
|
|
4820
|
+
return; // Let the fullscreen handlers handle ESC
|
|
4821
|
+
}
|
|
4822
|
+
closeCard();
|
|
4823
|
+
}
|
|
4087
4824
|
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') saveCurrent();
|
|
4088
4825
|
});
|
|
4089
4826
|
|
|
@@ -4217,13 +4954,13 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
4217
4954
|
const modalCancel = document.getElementById('modal-cancel');
|
|
4218
4955
|
const modalSubmit = document.getElementById('modal-submit');
|
|
4219
4956
|
|
|
4220
|
-
// Prompt checkboxes
|
|
4957
|
+
// Prompt checkboxes - text is the strong enforcement prompt for YAML output
|
|
4221
4958
|
const promptCheckboxes = [
|
|
4222
|
-
{ id: 'prompt-subagents', text: '
|
|
4223
|
-
{ id: 'prompt-reviw', text: '
|
|
4224
|
-
{ id: 'prompt-screenshots', text: '
|
|
4225
|
-
{ id: 'prompt-user-feedback-todo', text: "Add
|
|
4226
|
-
{ id: 'prompt-deep-dive', text: "Before
|
|
4959
|
+
{ id: 'prompt-subagents', text: 'MANDATORY: You MUST delegate ALL implementation, verification, and report creation to sub-agents. Direct execution on the main thread is PROHIBITED.' },
|
|
4960
|
+
{ id: 'prompt-reviw', text: 'REQUIRED: Before reporting completion, you MUST open the result in REVIW for user review. Skipping this step is NOT allowed.' },
|
|
4961
|
+
{ id: 'prompt-screenshots', text: 'MANDATORY: You MUST update ALL screenshots and videos as evidence. Reports without visual proof are REJECTED.' },
|
|
4962
|
+
{ id: 'prompt-user-feedback-todo', text: "STRICT RULE: Add ALL user feedback to the Todo list. You are FORBIDDEN from marking any item complete without explicit user approval." },
|
|
4963
|
+
{ id: 'prompt-deep-dive', text: "REQUIRED: Before ANY implementation, you MUST deeply probe the user's requirements using AskUserQuestion and EnterPlanMode. Starting implementation without thorough requirement analysis is PROHIBITED." }
|
|
4227
4964
|
];
|
|
4228
4965
|
const PROMPT_STORAGE_KEY = 'reviw-prompt-prefs';
|
|
4229
4966
|
|
|
@@ -4269,13 +5006,75 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
4269
5006
|
return prompts;
|
|
4270
5007
|
}
|
|
4271
5008
|
|
|
5009
|
+
// Find nearest heading for a given line number (markdown context)
|
|
5010
|
+
function findNearestHeading(lineNum) {
|
|
5011
|
+
let nearestHeading = null;
|
|
5012
|
+
for (let i = lineNum - 1; i >= 0; i--) {
|
|
5013
|
+
const line = DATA[i] ? DATA[i][0] : '';
|
|
5014
|
+
const match = line.match(/^(#{1,6})\\s+(.+)/);
|
|
5015
|
+
if (match) {
|
|
5016
|
+
nearestHeading = match[2].trim();
|
|
5017
|
+
break;
|
|
5018
|
+
}
|
|
5019
|
+
}
|
|
5020
|
+
return nearestHeading;
|
|
5021
|
+
}
|
|
5022
|
+
|
|
5023
|
+
// Check if line is inside a table
|
|
5024
|
+
function getTableContext(lineNum) {
|
|
5025
|
+
const line = DATA[lineNum] ? DATA[lineNum][0] : '';
|
|
5026
|
+
if (!line.includes('|')) return null;
|
|
5027
|
+
// Find table header (look backwards for header row)
|
|
5028
|
+
for (let i = lineNum; i >= 0; i--) {
|
|
5029
|
+
const l = DATA[i] ? DATA[i][0] : '';
|
|
5030
|
+
if (!l.includes('|')) break;
|
|
5031
|
+
// Check if next line is separator (---|---)
|
|
5032
|
+
const nextLine = DATA[i + 1] ? DATA[i + 1][0] : '';
|
|
5033
|
+
if (nextLine && nextLine.match(/^\\|?[\\s-:|]+\\|/)) {
|
|
5034
|
+
// This is the header row
|
|
5035
|
+
return l.replace(/^\\|\\s*/, '').replace(/\\s*\\|$/, '').split('|').map(h => h.trim()).slice(0, 3).join(' | ') + (l.split('|').length > 4 ? ' ...' : '');
|
|
5036
|
+
}
|
|
5037
|
+
}
|
|
5038
|
+
return null;
|
|
5039
|
+
}
|
|
5040
|
+
|
|
5041
|
+
// Transform comments for markdown mode
|
|
5042
|
+
function transformMarkdownComments(rawComments) {
|
|
5043
|
+
return rawComments.map(c => {
|
|
5044
|
+
const lineNum = c.row || c.startRow || 0;
|
|
5045
|
+
const section = findNearestHeading(lineNum);
|
|
5046
|
+
const tableHeader = getTableContext(lineNum);
|
|
5047
|
+
const content = c.content || c.value || '';
|
|
5048
|
+
const truncatedContent = content.length > 60 ? content.substring(0, 60) + '...' : content;
|
|
5049
|
+
|
|
5050
|
+
const transformed = {
|
|
5051
|
+
line: lineNum + 1,
|
|
5052
|
+
context: {}
|
|
5053
|
+
};
|
|
5054
|
+
if (section) transformed.context.section = section;
|
|
5055
|
+
if (tableHeader) transformed.context.table = tableHeader;
|
|
5056
|
+
if (truncatedContent) transformed.context.content = truncatedContent;
|
|
5057
|
+
transformed.comment = c.text;
|
|
5058
|
+
|
|
5059
|
+
if (c.isRange) {
|
|
5060
|
+
transformed.lineEnd = (c.endRow || c.startRow) + 1;
|
|
5061
|
+
}
|
|
5062
|
+
return transformed;
|
|
5063
|
+
});
|
|
5064
|
+
}
|
|
5065
|
+
|
|
4272
5066
|
function payload(reason) {
|
|
5067
|
+
const rawComments = Object.values(comments);
|
|
5068
|
+
const transformedComments = MODE === 'markdown'
|
|
5069
|
+
? transformMarkdownComments(rawComments)
|
|
5070
|
+
: rawComments;
|
|
5071
|
+
|
|
4273
5072
|
const data = {
|
|
4274
5073
|
file: FILE_NAME,
|
|
4275
5074
|
mode: MODE,
|
|
4276
|
-
reason,
|
|
4277
|
-
|
|
4278
|
-
comments:
|
|
5075
|
+
submittedBy: reason,
|
|
5076
|
+
submittedAt: new Date().toISOString(),
|
|
5077
|
+
comments: transformedComments
|
|
4279
5078
|
};
|
|
4280
5079
|
if (globalComment.trim()) {
|
|
4281
5080
|
data.summary = globalComment.trim();
|
|
@@ -4304,7 +5103,9 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
4304
5103
|
if (sent) return;
|
|
4305
5104
|
sent = true;
|
|
4306
5105
|
clearCommentsFromStorage();
|
|
4307
|
-
const
|
|
5106
|
+
const p = payload(reason);
|
|
5107
|
+
saveToHistory(p);
|
|
5108
|
+
const blob = new Blob([JSON.stringify(p)], { type: 'application/json' });
|
|
4308
5109
|
navigator.sendBeacon('/exit', blob);
|
|
4309
5110
|
}
|
|
4310
5111
|
function showSubmitModal() {
|
|
@@ -5622,11 +6423,12 @@ function htmlTemplate(dataRows, cols, projectRoot, relativePath, mode, previewHt
|
|
|
5622
6423
|
|
|
5623
6424
|
function buildHtml(filePath) {
|
|
5624
6425
|
const data = loadData(filePath);
|
|
6426
|
+
const history = loadHistoryFromFile(filePath);
|
|
5625
6427
|
if (data.mode === "diff") {
|
|
5626
|
-
return diffHtmlTemplate(data);
|
|
6428
|
+
return diffHtmlTemplate(data, history);
|
|
5627
6429
|
}
|
|
5628
6430
|
const { rows, cols, projectRoot, relativePath, mode, preview, reviwQuestions } = data;
|
|
5629
|
-
return htmlTemplate(rows, cols, projectRoot, relativePath, mode, preview, reviwQuestions);
|
|
6431
|
+
return htmlTemplate(rows, cols, projectRoot, relativePath, mode, preview, reviwQuestions, history);
|
|
5630
6432
|
}
|
|
5631
6433
|
|
|
5632
6434
|
// --- HTTP Server -----------------------------------------------------------
|
|
@@ -5746,6 +6548,54 @@ function checkExistingServer(filePath) {
|
|
|
5746
6548
|
}
|
|
5747
6549
|
}
|
|
5748
6550
|
|
|
6551
|
+
// --- History File Management ---
|
|
6552
|
+
const HISTORY_DIR = path.join(os.homedir(), '.reviw', 'history');
|
|
6553
|
+
const HISTORY_MAX = 50;
|
|
6554
|
+
|
|
6555
|
+
function getHistoryFilePath(filePath) {
|
|
6556
|
+
// Use SHA256 hash of absolute path (same as lock files)
|
|
6557
|
+
const hash = crypto.createHash('sha256').update(path.resolve(filePath)).digest('hex').slice(0, 16);
|
|
6558
|
+
return path.join(HISTORY_DIR, hash + '.json');
|
|
6559
|
+
}
|
|
6560
|
+
|
|
6561
|
+
function ensureHistoryDir() {
|
|
6562
|
+
try {
|
|
6563
|
+
if (!fs.existsSync(HISTORY_DIR)) {
|
|
6564
|
+
fs.mkdirSync(HISTORY_DIR, { recursive: true, mode: 0o700 });
|
|
6565
|
+
}
|
|
6566
|
+
} catch (err) {
|
|
6567
|
+
// Ignore errors
|
|
6568
|
+
}
|
|
6569
|
+
}
|
|
6570
|
+
|
|
6571
|
+
function loadHistoryFromFile(filePath) {
|
|
6572
|
+
try {
|
|
6573
|
+
const historyPath = getHistoryFilePath(filePath);
|
|
6574
|
+
if (!fs.existsSync(historyPath)) {
|
|
6575
|
+
return [];
|
|
6576
|
+
}
|
|
6577
|
+
const data = JSON.parse(fs.readFileSync(historyPath, 'utf8'));
|
|
6578
|
+
return Array.isArray(data) ? data : [];
|
|
6579
|
+
} catch (err) {
|
|
6580
|
+
return [];
|
|
6581
|
+
}
|
|
6582
|
+
}
|
|
6583
|
+
|
|
6584
|
+
function saveHistoryToFile(filePath, historyEntry) {
|
|
6585
|
+
try {
|
|
6586
|
+
ensureHistoryDir();
|
|
6587
|
+
const historyPath = getHistoryFilePath(filePath);
|
|
6588
|
+
let history = loadHistoryFromFile(filePath);
|
|
6589
|
+
history.unshift(historyEntry);
|
|
6590
|
+
if (history.length > HISTORY_MAX) {
|
|
6591
|
+
history = history.slice(0, HISTORY_MAX);
|
|
6592
|
+
}
|
|
6593
|
+
fs.writeFileSync(historyPath, JSON.stringify(history, null, 2), { mode: 0o600 });
|
|
6594
|
+
} catch (err) {
|
|
6595
|
+
// Ignore errors - history is optional
|
|
6596
|
+
}
|
|
6597
|
+
}
|
|
6598
|
+
|
|
5749
6599
|
// Try to activate an existing browser tab with the given URL (macOS only)
|
|
5750
6600
|
// Returns true if a tab was activated, false otherwise
|
|
5751
6601
|
function tryActivateExistingTab(url) {
|
|
@@ -6021,6 +6871,10 @@ function createFileServer(filePath, fileIndex = 0) {
|
|
|
6021
6871
|
if (raw && raw.trim()) {
|
|
6022
6872
|
payload = JSON.parse(raw);
|
|
6023
6873
|
}
|
|
6874
|
+
// Save to file-based history (only if there are comments)
|
|
6875
|
+
if (payload && (payload.comments?.length > 0 || payload.submitComment)) {
|
|
6876
|
+
saveHistoryToFile(ctx.filePath, payload);
|
|
6877
|
+
}
|
|
6024
6878
|
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
6025
6879
|
res.end("bye");
|
|
6026
6880
|
// Notify all tabs to close before shutting down
|
|
@@ -6283,6 +7137,12 @@ function createDiffServer(diffContent) {
|
|
|
6283
7137
|
if (raw && raw.trim()) {
|
|
6284
7138
|
payload = JSON.parse(raw);
|
|
6285
7139
|
}
|
|
7140
|
+
// Save to file-based history (only if there are comments)
|
|
7141
|
+
// For diff mode, use relativePath as identifier
|
|
7142
|
+
if (payload && (payload.comments?.length > 0 || payload.submitComment)) {
|
|
7143
|
+
const filePath = ctx.diffData?.relativePath || 'stdin-diff';
|
|
7144
|
+
saveHistoryToFile(filePath, payload);
|
|
7145
|
+
}
|
|
6286
7146
|
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
6287
7147
|
res.end("bye");
|
|
6288
7148
|
// Notify all tabs to close before shutting down
|