@ai-setting/roy-plugin-task-show 1.2.0 → 2.0.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.
@@ -0,0 +1,210 @@
1
+ /* v2.0.0: file-tree sidebar hydrator.
2
+ *
3
+ * Loads the project file list from the data-files JSON attribute on the
4
+ * SSR-rendered `<ul id="file-tree">` and wires up:
5
+ * - chevron toggle (▶ / ▼) for directories
6
+ * - "expand all / collapse all" controls
7
+ * - click-to-highlight a file node (calls window.setActiveToolCallFile)
8
+ * - scrolling a node into view when window.__toolClick is fired
9
+ *
10
+ * Designed to be loaded AFTER app.js (so it can hook __toolClick) but
11
+ * BEFORE tool-call-detail.js (so that hydrator can call our
12
+ * setActiveToolCallFile when a Mermaid node is clicked).
13
+ *
14
+ * No dependencies — pure vanilla JS, no module loader, no JSX.
15
+ */
16
+
17
+ (function attachFileTreeHydrator() {
18
+ const SIDEBAR_SELECTOR = "#file-tree";
19
+
20
+ // -----------------------------------------------------------------------
21
+ // State
22
+ // -----------------------------------------------------------------------
23
+ /** @type {HTMLElement|null} */
24
+ let root = null;
25
+ /** @type {Set<string>} */
26
+ let affectedSet = new Set();
27
+ /** @type {string|null} */
28
+ let activePath = null;
29
+
30
+ // -----------------------------------------------------------------------
31
+ // Helpers
32
+ // -----------------------------------------------------------------------
33
+
34
+ /**
35
+ * Toggle visibility of a directory's children.
36
+ * @param {HTMLElement} li
37
+ */
38
+ function toggleDir(li) {
39
+ const isOpen = li.getAttribute("aria-expanded") === "true";
40
+ const next = !isOpen;
41
+ li.setAttribute("aria-expanded", String(next));
42
+ const chev = li.querySelector(".file-tree-chevron");
43
+ if (chev) chev.textContent = next ? "▼" : "▶";
44
+ // Update descendant visibility by toggling the `data-collapsed` flag on
45
+ // every sibling immediately under this directory.
46
+ const depth = Number(li.getAttribute("data-depth") || "0");
47
+ let sibling = li.nextElementSibling;
48
+ while (sibling) {
49
+ const sDepth = Number(sibling.getAttribute("data-depth") || "0");
50
+ if (sDepth <= depth) break;
51
+ if (!next) {
52
+ sibling.setAttribute("data-collapsed", "true");
53
+ } else {
54
+ sibling.removeAttribute("data-collapsed");
55
+ }
56
+ sibling = sibling.nextElementSibling;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Set the active (highlighted) file path. Called by the tool-call
62
+ * detail hydrator when the user clicks a Mermaid tool node.
63
+ * @param {string|null} path
64
+ */
65
+ function setActive(path) {
66
+ if (!root) return;
67
+ if (activePath) {
68
+ const prev = root.querySelector(`[data-path="${cssEscape(activePath)}"]`);
69
+ if (prev) prev.classList.remove("is-active");
70
+ }
71
+ activePath = path;
72
+ if (path) {
73
+ const next = root.querySelector(`[data-path="${cssEscape(path)}"]`);
74
+ if (next) {
75
+ next.classList.add("is-active");
76
+ // Expand parent directories so the active node is visible.
77
+ expandAncestors(next);
78
+ // Scroll into view (block: 'center') — but only if it's not
79
+ // already on-screen so we don't fight the user mid-scroll.
80
+ try {
81
+ next.scrollIntoView({ behavior: "smooth", block: "center" });
82
+ } catch {
83
+ try {
84
+ next.scrollIntoView();
85
+ } catch (_) {
86
+ /* noop */
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Walk up the DOM expanding every ancestor directory so the given
95
+ * node becomes visible. Cheap because the tree is at most ~10 levels
96
+ * deep in practice.
97
+ * @param {HTMLElement} leaf
98
+ */
99
+ function expandAncestors(leaf) {
100
+ let cur = leaf.previousElementSibling;
101
+ let targetDepth = Number(leaf.getAttribute("data-depth") || "0");
102
+ while (cur) {
103
+ const d = Number(cur.getAttribute("data-depth") || "0");
104
+ if (d < targetDepth) {
105
+ if (cur.getAttribute("aria-expanded") === "false") {
106
+ toggleDir(cur);
107
+ }
108
+ targetDepth = d; // keep walking
109
+ }
110
+ cur = cur.previousElementSibling;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * CSS.escape polyfill — older browsers / jsdom may lack it.
116
+ * @param {string} s
117
+ */
118
+ function cssEscape(s) {
119
+ if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
120
+ return CSS.escape(s);
121
+ }
122
+ return String(s).replace(/([^a-zA-Z0-9_-])/g, "\\$1");
123
+ }
124
+
125
+ // -----------------------------------------------------------------------
126
+ // Wiring
127
+ // -----------------------------------------------------------------------
128
+
129
+ /**
130
+ * Build the affected set from `[data-affected="true"]` nodes the SSR
131
+ * already flagged. Re-runs on each hydration so SSE-inserted nodes
132
+ * stay highlighted.
133
+ */
134
+ function rebuildAffectedSet() {
135
+ if (!root) return;
136
+ affectedSet = new Set();
137
+ root.querySelectorAll('[data-affected="true"]').forEach((el) => {
138
+ const p = el.getAttribute("data-path");
139
+ if (p) affectedSet.add(p);
140
+ });
141
+ }
142
+
143
+ /**
144
+ * Initial hydration: bind click handlers + collect the affected set.
145
+ * Idempotent — safe to call multiple times.
146
+ */
147
+ function hydrate() {
148
+ root = document.querySelector(SIDEBAR_SELECTOR);
149
+ if (!root) return;
150
+ rebuildAffectedSet();
151
+ root.addEventListener("click", (ev) => {
152
+ const target = ev.target;
153
+ // jsdom's eval'd scripts don't have `Element` in scope; use a
154
+ // duck-type check instead of `instanceof Element`.
155
+ if (!target || typeof target.getAttribute !== "function") return;
156
+ const li = target.closest && target.closest(".file-tree-node");
157
+ if (!li || typeof li.getAttribute !== "function") return;
158
+ const path = li.getAttribute("data-path") || null;
159
+ const type = li.getAttribute("data-type");
160
+ if (type === "directory") {
161
+ toggleDir(li);
162
+ // Suppress the subsequent active-path switch — directories
163
+ // aren't files, just containers.
164
+ return;
165
+ }
166
+ if (path) setActive(path);
167
+ });
168
+ // Auto-hydrate on DOMContentLoaded if the DOM is already ready.
169
+ if (document.readyState === "loading") {
170
+ document.addEventListener("DOMContentLoaded", hydrate, { once: true });
171
+ }
172
+ }
173
+ hydrate();
174
+
175
+ /**
176
+ * Expand every directory in the tree (used by the "expand all" button).
177
+ */
178
+ function expandAll() {
179
+ if (!root) return;
180
+ root.querySelectorAll('.file-tree-node[data-type="directory"]').forEach((li) => {
181
+ if (li.getAttribute("aria-expanded") === "false") toggleDir(li);
182
+ });
183
+ }
184
+
185
+ /**
186
+ * Collapse every directory (used by "collapse all").
187
+ */
188
+ function collapseAll() {
189
+ if (!root) return;
190
+ root.querySelectorAll('.file-tree-node[data-type="directory"]').forEach((li) => {
191
+ if (li.getAttribute("aria-expanded") === "true") toggleDir(li);
192
+ });
193
+ }
194
+
195
+ // Expose a tiny global API so other scripts (app.js, tool-call-detail.js)
196
+ // can drive the sidebar.
197
+ /** @type {any} */
198
+ const w = window;
199
+ w.__fileTree = {
200
+ setActive,
201
+ expandAll,
202
+ collapseAll,
203
+ /** Refresh affected-set from the current DOM (called by SSE handlers). */
204
+ refreshAffected: rebuildAffectedSet,
205
+ /** Return the currently-highlighted file path, or null. */
206
+ getActive() {
207
+ return activePath;
208
+ },
209
+ };
210
+ })();
package/public/style.css CHANGED
@@ -1154,3 +1154,505 @@ details summary {
1154
1154
  margin-bottom: 8px;
1155
1155
  }
1156
1156
  }
1157
+
1158
+ /* ============================================================================
1159
+ * v2.0.0: Monaco Editor + File Tree + Diff + Mermaid jump
1160
+ *
1161
+ * Sections (search for the header to navigate):
1162
+ * - .task-v2-layout — sidebar + main grid
1163
+ * - .file-tree-panel — left sidebar
1164
+ * - .file-tree — collapsible nested list
1165
+ * - .file-tree-node — individual rows
1166
+ * - .file-tree-chevron — directory expand/collapse
1167
+ * - .tool-call-detail — right-hand detail panel
1168
+ * - .monaco-editor — Monaco container
1169
+ * - .diff-body / .diff-line — LCS diff rendering
1170
+ * - .v2-hint — small inline hints
1171
+ * ========================================================================== */
1172
+
1173
+ /* ---------- v2.0.0 layout (sidebar + main) ---------- */
1174
+ .task-v2-layout {
1175
+ display: grid;
1176
+ grid-template-columns: minmax(220px, 280px) 1fr;
1177
+ gap: 16px;
1178
+ margin: 0 auto;
1179
+ max-width: 1400px;
1180
+ padding: 0 16px;
1181
+ }
1182
+ .task-v2-layout > .task-v2-main {
1183
+ min-width: 0; /* allow grid item to shrink */
1184
+ }
1185
+ .file-tree-panel {
1186
+ position: sticky;
1187
+ top: 12px;
1188
+ align-self: start;
1189
+ max-height: calc(100vh - 24px);
1190
+ overflow: auto;
1191
+ border: 1px solid var(--border, #e5e7eb);
1192
+ border-radius: 6px;
1193
+ padding: 12px 14px;
1194
+ background: var(--bg-panel, #fafafa);
1195
+ }
1196
+ .file-tree-panel h2 {
1197
+ font-size: 14px;
1198
+ margin: 0 0 6px;
1199
+ letter-spacing: 0.02em;
1200
+ }
1201
+ .file-tree-summary {
1202
+ font-size: 11px;
1203
+ color: var(--fg-muted, #6b7280);
1204
+ margin: 0 0 8px;
1205
+ }
1206
+ @media (max-width: 900px) {
1207
+ .task-v2-layout {
1208
+ grid-template-columns: 1fr;
1209
+ }
1210
+ .file-tree-panel {
1211
+ position: static;
1212
+ max-height: none;
1213
+ }
1214
+ }
1215
+
1216
+ /* ---------- file tree sidebar ---------- */
1217
+ .file-tree {
1218
+ list-style: none;
1219
+ margin: 0;
1220
+ padding: 0;
1221
+ font-family: ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Code", Menlo, monospace;
1222
+ font-size: 12px;
1223
+ line-height: 1.5;
1224
+ }
1225
+ .file-tree-node {
1226
+ display: flex;
1227
+ align-items: center;
1228
+ gap: 4px;
1229
+ padding: 1px 4px 1px calc(4px + var(--depth, 0) * 12px);
1230
+ border-radius: 3px;
1231
+ cursor: pointer;
1232
+ user-select: none;
1233
+ }
1234
+ .file-tree-node[data-collapsed="true"] {
1235
+ display: none;
1236
+ }
1237
+ .file-tree-node:hover {
1238
+ background: rgba(99, 102, 241, 0.08);
1239
+ }
1240
+ .file-tree-node.is-active {
1241
+ background: rgba(99, 102, 241, 0.18);
1242
+ outline: 1px solid rgba(99, 102, 241, 0.45);
1243
+ font-weight: 600;
1244
+ }
1245
+ .file-tree-node.is-affected .file-tree-name::after {
1246
+ content: " ●";
1247
+ color: rgba(220, 38, 38, 0.8);
1248
+ font-size: 10px;
1249
+ }
1250
+ .file-tree-chevron {
1251
+ display: inline-block;
1252
+ width: 12px;
1253
+ text-align: center;
1254
+ font-size: 9px;
1255
+ color: var(--fg-muted, #6b7280);
1256
+ flex: 0 0 12px;
1257
+ }
1258
+ .file-tree-spacer {
1259
+ display: inline-block;
1260
+ width: 12px;
1261
+ flex: 0 0 12px;
1262
+ }
1263
+ .file-tree-name {
1264
+ flex: 1 1 auto;
1265
+ white-space: nowrap;
1266
+ overflow: hidden;
1267
+ text-overflow: ellipsis;
1268
+ }
1269
+ .file-tree-empty {
1270
+ list-style: none;
1271
+ color: var(--fg-muted, #6b7280);
1272
+ font-style: italic;
1273
+ padding: 4px;
1274
+ }
1275
+
1276
+ /* ---------- tool-call detail panel ---------- */
1277
+ .tool-call-detail-panel {
1278
+ border-left: 4px solid rgba(99, 102, 241, 0.55);
1279
+ padding-left: 14px;
1280
+ margin-top: 18px;
1281
+ }
1282
+ .tool-call-detail-panel .v2-hint {
1283
+ font-size: 12px;
1284
+ font-weight: 400;
1285
+ color: var(--fg-muted, #6b7280);
1286
+ margin-left: 8px;
1287
+ }
1288
+ .tool-call-detail {
1289
+ border: 1px solid var(--border, #e5e7eb);
1290
+ border-radius: 6px;
1291
+ padding: 10px 12px;
1292
+ margin-top: 8px;
1293
+ background: #ffffff;
1294
+ }
1295
+ .tool-call-header {
1296
+ display: flex;
1297
+ align-items: center;
1298
+ gap: 10px;
1299
+ font-size: 13px;
1300
+ margin-bottom: 6px;
1301
+ }
1302
+ .tool-call-header .tool-name code {
1303
+ background: rgba(99, 102, 241, 0.08);
1304
+ padding: 1px 6px;
1305
+ border-radius: 3px;
1306
+ }
1307
+ .tool-call-header .tool-path {
1308
+ color: var(--fg-muted, #6b7280);
1309
+ font-family: ui-monospace, Menlo, monospace;
1310
+ font-size: 11px;
1311
+ margin-left: auto;
1312
+ overflow: hidden;
1313
+ text-overflow: ellipsis;
1314
+ white-space: nowrap;
1315
+ flex: 1 1 auto;
1316
+ }
1317
+ .tool-call-detail-empty {
1318
+ color: var(--fg-muted, #6b7280);
1319
+ font-style: italic;
1320
+ padding: 8px 0;
1321
+ }
1322
+
1323
+ /* ---------- Monaco container ---------- */
1324
+ .monaco-editor {
1325
+ width: 100%;
1326
+ height: 280px;
1327
+ border: 1px solid var(--border, #d1d5db);
1328
+ border-radius: 4px;
1329
+ margin: 6px 0 0;
1330
+ position: relative;
1331
+ background: #fafafa;
1332
+ overflow: hidden;
1333
+ }
1334
+ .monaco-editor .monaco-placeholder {
1335
+ display: flex;
1336
+ align-items: center;
1337
+ justify-content: center;
1338
+ height: 100%;
1339
+ color: var(--fg-muted, #6b7280);
1340
+ font-family: ui-monospace, Menlo, monospace;
1341
+ font-size: 12px;
1342
+ }
1343
+ .monaco-editor .monaco-error {
1344
+ color: rgba(220, 38, 38, 0.9);
1345
+ background: rgba(254, 226, 226, 0.5);
1346
+ }
1347
+ .monaco-editor .monaco-truncation-note {
1348
+ position: absolute;
1349
+ bottom: 4px;
1350
+ right: 8px;
1351
+ font-size: 10px;
1352
+ color: var(--fg-muted, #6b7280);
1353
+ background: rgba(255, 255, 255, 0.85);
1354
+ padding: 1px 6px;
1355
+ border-radius: 3px;
1356
+ }
1357
+
1358
+ /* ---------- LCS diff body ---------- */
1359
+ .diff-body {
1360
+ list-style: none;
1361
+ margin: 6px 0 0;
1362
+ padding: 6px 8px;
1363
+ border: 1px solid var(--border, #e5e7eb);
1364
+ border-radius: 4px;
1365
+ background: #fafafa;
1366
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
1367
+ font-size: 12px;
1368
+ line-height: 1.5;
1369
+ max-height: 320px;
1370
+ overflow: auto;
1371
+ }
1372
+ .diff-line {
1373
+ display: flex;
1374
+ gap: 8px;
1375
+ padding: 0 4px;
1376
+ white-space: pre;
1377
+ }
1378
+ .diff-line .diff-gutter {
1379
+ flex: 0 0 14px;
1380
+ text-align: center;
1381
+ font-weight: 600;
1382
+ }
1383
+ .diff-line.diff-line-added {
1384
+ background: rgba(220, 252, 231, 0.6);
1385
+ }
1386
+ .diff-line.diff-line-added .diff-gutter {
1387
+ color: #15803d;
1388
+ }
1389
+ .diff-line.diff-line-removed {
1390
+ background: rgba(254, 226, 226, 0.6);
1391
+ }
1392
+ .diff-line.diff-line-removed .diff-gutter {
1393
+ color: #b91c1c;
1394
+ }
1395
+ .diff-line.diff-line-context .diff-gutter {
1396
+ color: var(--fg-muted, #9ca3af);
1397
+ }
1398
+ .diff-line .diff-text {
1399
+ flex: 1 1 auto;
1400
+ white-space: pre;
1401
+ }
1402
+
1403
+ /* ---------- v2 small inline hint ---------- */
1404
+ .v2-hint {
1405
+ font-size: 11px;
1406
+ color: var(--fg-muted, #6b7280);
1407
+ }
1408
+
1409
+ /* ============================================================================
1410
+ * v2.0.0: Monaco Editor + File Tree + Diff + Mermaid jump
1411
+ *
1412
+ * Sections (search for the header to navigate):
1413
+ * - .task-v2-layout — sidebar + main grid
1414
+ * - .file-tree-panel — left sidebar
1415
+ * - .file-tree — collapsible nested list
1416
+ * - .file-tree-node — individual rows
1417
+ * - .file-tree-chevron — directory expand/collapse
1418
+ * - .tool-call-detail — right-hand detail panel
1419
+ * - .monaco-editor — Monaco container
1420
+ * - .diff-body / .diff-line — LCS diff rendering
1421
+ * - .v2-hint — small inline hints
1422
+ * ========================================================================== */
1423
+
1424
+ /* ---------- v2.0.0 layout (sidebar + main) ---------- */
1425
+ .task-v2-layout {
1426
+ display: grid;
1427
+ grid-template-columns: minmax(220px, 280px) 1fr;
1428
+ gap: 16px;
1429
+ margin: 0 auto;
1430
+ max-width: 1400px;
1431
+ padding: 0 16px;
1432
+ }
1433
+ .task-v2-layout > .task-v2-main {
1434
+ min-width: 0; /* allow grid item to shrink */
1435
+ }
1436
+ .file-tree-panel {
1437
+ position: sticky;
1438
+ top: 12px;
1439
+ align-self: start;
1440
+ max-height: calc(100vh - 24px);
1441
+ overflow: auto;
1442
+ border: 1px solid var(--border, #e5e7eb);
1443
+ border-radius: 6px;
1444
+ padding: 12px 14px;
1445
+ background: var(--bg-panel, #fafafa);
1446
+ }
1447
+ .file-tree-panel h2 {
1448
+ font-size: 14px;
1449
+ margin: 0 0 6px;
1450
+ letter-spacing: 0.02em;
1451
+ }
1452
+ .file-tree-summary {
1453
+ font-size: 11px;
1454
+ color: var(--fg-muted, #6b7280);
1455
+ margin: 0 0 8px;
1456
+ }
1457
+ @media (max-width: 900px) {
1458
+ .task-v2-layout {
1459
+ grid-template-columns: 1fr;
1460
+ }
1461
+ .file-tree-panel {
1462
+ position: static;
1463
+ max-height: none;
1464
+ }
1465
+ }
1466
+
1467
+ /* ---------- file tree sidebar ---------- */
1468
+ .file-tree {
1469
+ list-style: none;
1470
+ margin: 0;
1471
+ padding: 0;
1472
+ font-family: ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Code", Menlo, monospace;
1473
+ font-size: 12px;
1474
+ line-height: 1.5;
1475
+ }
1476
+ .file-tree-node {
1477
+ display: flex;
1478
+ align-items: center;
1479
+ gap: 4px;
1480
+ padding: 1px 4px 1px calc(4px + var(--depth, 0) * 12px);
1481
+ border-radius: 3px;
1482
+ cursor: pointer;
1483
+ user-select: none;
1484
+ }
1485
+ .file-tree-node[data-collapsed="true"] {
1486
+ display: none;
1487
+ }
1488
+ .file-tree-node:hover {
1489
+ background: rgba(99, 102, 241, 0.08);
1490
+ }
1491
+ .file-tree-node.is-active {
1492
+ background: rgba(99, 102, 241, 0.18);
1493
+ outline: 1px solid rgba(99, 102, 241, 0.45);
1494
+ font-weight: 600;
1495
+ }
1496
+ .file-tree-node.is-affected .file-tree-name::after {
1497
+ content: " ●";
1498
+ color: rgba(220, 38, 38, 0.8);
1499
+ font-size: 10px;
1500
+ }
1501
+ .file-tree-chevron {
1502
+ display: inline-block;
1503
+ width: 12px;
1504
+ text-align: center;
1505
+ font-size: 9px;
1506
+ color: var(--fg-muted, #6b7280);
1507
+ flex: 0 0 12px;
1508
+ }
1509
+ .file-tree-spacer {
1510
+ display: inline-block;
1511
+ width: 12px;
1512
+ flex: 0 0 12px;
1513
+ }
1514
+ .file-tree-name {
1515
+ flex: 1 1 auto;
1516
+ white-space: nowrap;
1517
+ overflow: hidden;
1518
+ text-overflow: ellipsis;
1519
+ }
1520
+ .file-tree-empty {
1521
+ list-style: none;
1522
+ color: var(--fg-muted, #6b7280);
1523
+ font-style: italic;
1524
+ padding: 4px;
1525
+ }
1526
+
1527
+ /* ---------- tool-call detail panel ---------- */
1528
+ .tool-call-detail-panel {
1529
+ border-left: 4px solid rgba(99, 102, 241, 0.55);
1530
+ padding-left: 14px;
1531
+ margin-top: 18px;
1532
+ }
1533
+ .tool-call-detail-panel .v2-hint {
1534
+ font-size: 12px;
1535
+ font-weight: 400;
1536
+ color: var(--fg-muted, #6b7280);
1537
+ margin-left: 8px;
1538
+ }
1539
+ .tool-call-detail {
1540
+ border: 1px solid var(--border, #e5e7eb);
1541
+ border-radius: 6px;
1542
+ padding: 10px 12px;
1543
+ margin-top: 8px;
1544
+ background: #ffffff;
1545
+ }
1546
+ .tool-call-header {
1547
+ display: flex;
1548
+ align-items: center;
1549
+ gap: 10px;
1550
+ font-size: 13px;
1551
+ margin-bottom: 6px;
1552
+ }
1553
+ .tool-call-header .tool-name code {
1554
+ background: rgba(99, 102, 241, 0.08);
1555
+ padding: 1px 6px;
1556
+ border-radius: 3px;
1557
+ }
1558
+ .tool-call-header .tool-path {
1559
+ color: var(--fg-muted, #6b7280);
1560
+ font-family: ui-monospace, Menlo, monospace;
1561
+ font-size: 11px;
1562
+ margin-left: auto;
1563
+ overflow: hidden;
1564
+ text-overflow: ellipsis;
1565
+ white-space: nowrap;
1566
+ flex: 1 1 auto;
1567
+ }
1568
+ .tool-call-detail-empty {
1569
+ color: var(--fg-muted, #6b7280);
1570
+ font-style: italic;
1571
+ padding: 8px 0;
1572
+ }
1573
+
1574
+ /* ---------- Monaco container ---------- */
1575
+ .monaco-editor {
1576
+ width: 100%;
1577
+ height: 280px;
1578
+ border: 1px solid var(--border, #d1d5db);
1579
+ border-radius: 4px;
1580
+ margin: 6px 0 0;
1581
+ position: relative;
1582
+ background: #fafafa;
1583
+ overflow: hidden;
1584
+ }
1585
+ .monaco-editor .monaco-placeholder {
1586
+ display: flex;
1587
+ align-items: center;
1588
+ justify-content: center;
1589
+ height: 100%;
1590
+ color: var(--fg-muted, #6b7280);
1591
+ font-family: ui-monospace, Menlo, monospace;
1592
+ font-size: 12px;
1593
+ }
1594
+ .monaco-editor .monaco-error {
1595
+ color: rgba(220, 38, 38, 0.9);
1596
+ background: rgba(254, 226, 226, 0.5);
1597
+ }
1598
+ .monaco-editor .monaco-truncation-note {
1599
+ position: absolute;
1600
+ bottom: 4px;
1601
+ right: 8px;
1602
+ font-size: 10px;
1603
+ color: var(--fg-muted, #6b7280);
1604
+ background: rgba(255, 255, 255, 0.85);
1605
+ padding: 1px 6px;
1606
+ border-radius: 3px;
1607
+ }
1608
+
1609
+ /* ---------- LCS diff body ---------- */
1610
+ .diff-body {
1611
+ list-style: none;
1612
+ margin: 6px 0 0;
1613
+ padding: 6px 8px;
1614
+ border: 1px solid var(--border, #e5e7eb);
1615
+ border-radius: 4px;
1616
+ background: #fafafa;
1617
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
1618
+ font-size: 12px;
1619
+ line-height: 1.5;
1620
+ max-height: 320px;
1621
+ overflow: auto;
1622
+ }
1623
+ .diff-line {
1624
+ display: flex;
1625
+ gap: 8px;
1626
+ padding: 0 4px;
1627
+ white-space: pre;
1628
+ }
1629
+ .diff-line .diff-gutter {
1630
+ flex: 0 0 14px;
1631
+ text-align: center;
1632
+ font-weight: 600;
1633
+ }
1634
+ .diff-line.diff-line-added {
1635
+ background: rgba(220, 252, 231, 0.6);
1636
+ }
1637
+ .diff-line.diff-line-added .diff-gutter {
1638
+ color: #15803d;
1639
+ }
1640
+ .diff-line.diff-line-removed {
1641
+ background: rgba(254, 226, 226, 0.6);
1642
+ }
1643
+ .diff-line.diff-line-removed .diff-gutter {
1644
+ color: #b91c1c;
1645
+ }
1646
+ .diff-line.diff-line-context .diff-gutter {
1647
+ color: var(--fg-muted, #9ca3af);
1648
+ }
1649
+ .diff-line .diff-text {
1650
+ flex: 1 1 auto;
1651
+ white-space: pre;
1652
+ }
1653
+
1654
+ /* ---------- v2 small inline hint ---------- */
1655
+ .v2-hint {
1656
+ font-size: 11px;
1657
+ color: var(--fg-muted, #6b7280);
1658
+ }