create-walle 0.9.21 → 0.9.22
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.md +5 -5
- package/package.json +2 -2
- package/template/claude-task-manager/api-prompts.js +13 -0
- package/template/claude-task-manager/api-reviews.js +5 -2
- package/template/claude-task-manager/db.js +348 -15
- package/template/claude-task-manager/docs/app-update-refresh-protocol.md +69 -0
- package/template/claude-task-manager/docs/image-paste-ux.md +3 -0
- package/template/claude-task-manager/docs/ipad-web-preview.md +88 -0
- package/template/claude-task-manager/git-utils.js +146 -17
- package/template/claude-task-manager/lib/auth-rate-limit.js +23 -3
- package/template/claude-task-manager/lib/auth-rules.js +3 -0
- package/template/claude-task-manager/lib/document-review.js +33 -2
- package/template/claude-task-manager/lib/microsoft-dev-tunnel-setup.js +83 -0
- package/template/claude-task-manager/lib/mobile-auth-api.js +14 -0
- package/template/claude-task-manager/lib/restart-guard.js +68 -0
- package/template/claude-task-manager/lib/session-standup.js +36 -13
- package/template/claude-task-manager/lib/session-stream.js +11 -4
- package/template/claude-task-manager/lib/transport-security.js +50 -0
- package/template/claude-task-manager/lib/walle-transcript.js +16 -0
- package/template/claude-task-manager/lib/worktree-active-sync.js +6 -3
- package/template/claude-task-manager/public/css/reviews.css +10 -0
- package/template/claude-task-manager/public/css/setup.css +13 -0
- package/template/claude-task-manager/public/css/walle.css +145 -0
- package/template/claude-task-manager/public/index.html +539 -44
- package/template/claude-task-manager/public/ipad.html +363 -0
- package/template/claude-task-manager/public/js/document-review-links.js +196 -0
- package/template/claude-task-manager/public/js/message-renderer.js +14 -3
- package/template/claude-task-manager/public/js/reviews.js +30 -6
- package/template/claude-task-manager/public/js/setup.js +42 -2
- package/template/claude-task-manager/public/js/stream-view.js +20 -1
- package/template/claude-task-manager/public/js/walle.js +314 -18
- package/template/claude-task-manager/public/m/app.css +789 -11
- package/template/claude-task-manager/public/m/app.js +1070 -67
- package/template/claude-task-manager/public/m/claim.html +9 -2
- package/template/claude-task-manager/public/m/index.html +17 -10
- package/template/claude-task-manager/public/m/sw.js +1 -1
- package/template/claude-task-manager/server.js +365 -95
- package/template/claude-task-manager/session-integrity.js +4 -0
- package/template/docs/designs/2026-05-17-portkey-gateway-provider-ux.md +86 -35
- package/template/package.json +1 -1
- package/template/wall-e/api-walle.js +19 -1
- package/template/wall-e/brain.js +152 -6
- package/template/wall-e/chat.js +85 -0
- package/template/wall-e/coding-orchestrator.js +106 -12
- package/template/wall-e/http/model-admin.js +131 -0
- package/template/wall-e/lib/service-health.js +194 -0
- package/template/wall-e/llm/anthropic.js +7 -0
- package/template/wall-e/llm/client.js +46 -12
- package/template/wall-e/llm/openai.js +17 -2
- package/template/wall-e/llm/portkey-sync.js +201 -0
- package/template/wall-e/server.js +13 -0
- package/template/website/index.html +10 -10
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>CTM iPad Preview</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
color-scheme: dark;
|
|
10
|
+
--bg: #111827;
|
|
11
|
+
--panel: #182235;
|
|
12
|
+
--panel-strong: #202c43;
|
|
13
|
+
--line: #34435f;
|
|
14
|
+
--text: #e5edf9;
|
|
15
|
+
--muted: #94a3b8;
|
|
16
|
+
--blue: #7aa2ff;
|
|
17
|
+
--green: #81c784;
|
|
18
|
+
--shadow: rgba(0, 0, 0, 0.36);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
* { box-sizing: border-box; }
|
|
22
|
+
|
|
23
|
+
html,
|
|
24
|
+
body {
|
|
25
|
+
min-height: 100%;
|
|
26
|
+
margin: 0;
|
|
27
|
+
background: var(--bg);
|
|
28
|
+
color: var(--text);
|
|
29
|
+
font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
|
30
|
+
letter-spacing: 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
body {
|
|
34
|
+
display: grid;
|
|
35
|
+
grid-template-rows: auto 1fr;
|
|
36
|
+
overflow: hidden;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
button,
|
|
40
|
+
input,
|
|
41
|
+
select {
|
|
42
|
+
font: inherit;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
button,
|
|
46
|
+
input {
|
|
47
|
+
min-height: 40px;
|
|
48
|
+
border: 1px solid var(--line);
|
|
49
|
+
border-radius: 8px;
|
|
50
|
+
background: #121a2a;
|
|
51
|
+
color: var(--text);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
button {
|
|
55
|
+
padding: 0 12px;
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
button:hover,
|
|
60
|
+
button[aria-pressed="true"] {
|
|
61
|
+
border-color: color-mix(in srgb, var(--blue) 76%, var(--line));
|
|
62
|
+
background: color-mix(in srgb, var(--blue) 20%, #121a2a);
|
|
63
|
+
color: #f8fbff;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
button:focus-visible,
|
|
67
|
+
input:focus-visible {
|
|
68
|
+
outline: 2px solid var(--blue);
|
|
69
|
+
outline-offset: 2px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.topbar {
|
|
73
|
+
display: grid;
|
|
74
|
+
grid-template-columns: minmax(220px, 1fr) auto;
|
|
75
|
+
gap: 16px;
|
|
76
|
+
align-items: center;
|
|
77
|
+
padding: 14px 18px;
|
|
78
|
+
border-bottom: 1px solid var(--line);
|
|
79
|
+
background: color-mix(in srgb, var(--panel) 94%, transparent);
|
|
80
|
+
box-shadow: 0 14px 40px var(--shadow);
|
|
81
|
+
z-index: 2;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.title-block h1 {
|
|
85
|
+
margin: 0;
|
|
86
|
+
font-size: 18px;
|
|
87
|
+
line-height: 1.2;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.title-block p {
|
|
91
|
+
margin: 4px 0 0;
|
|
92
|
+
color: var(--muted);
|
|
93
|
+
font-size: 13px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.controls {
|
|
97
|
+
display: flex;
|
|
98
|
+
flex-wrap: wrap;
|
|
99
|
+
justify-content: flex-end;
|
|
100
|
+
align-items: center;
|
|
101
|
+
gap: 8px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.device-group {
|
|
105
|
+
display: flex;
|
|
106
|
+
gap: 4px;
|
|
107
|
+
padding: 3px;
|
|
108
|
+
border: 1px solid var(--line);
|
|
109
|
+
border-radius: 10px;
|
|
110
|
+
background: #0f1726;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.device-group button {
|
|
114
|
+
min-height: 34px;
|
|
115
|
+
border-color: transparent;
|
|
116
|
+
white-space: nowrap;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.source-form {
|
|
120
|
+
display: flex;
|
|
121
|
+
align-items: center;
|
|
122
|
+
gap: 8px;
|
|
123
|
+
min-width: min(520px, 100%);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.source-form input {
|
|
127
|
+
width: min(360px, 38vw);
|
|
128
|
+
padding: 0 11px;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.stage {
|
|
132
|
+
position: relative;
|
|
133
|
+
min-height: 0;
|
|
134
|
+
display: grid;
|
|
135
|
+
place-items: center;
|
|
136
|
+
padding: 18px;
|
|
137
|
+
overflow: auto;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.preview-meta {
|
|
141
|
+
position: absolute;
|
|
142
|
+
left: 18px;
|
|
143
|
+
bottom: 14px;
|
|
144
|
+
display: flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
gap: 10px;
|
|
147
|
+
color: var(--muted);
|
|
148
|
+
font-size: 12px;
|
|
149
|
+
pointer-events: none;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.status-dot {
|
|
153
|
+
width: 8px;
|
|
154
|
+
height: 8px;
|
|
155
|
+
border-radius: 999px;
|
|
156
|
+
background: var(--green);
|
|
157
|
+
box-shadow: 0 0 0 4px color-mix(in srgb, var(--green) 16%, transparent);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.device-slot {
|
|
161
|
+
position: relative;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.device {
|
|
165
|
+
transform-origin: top left;
|
|
166
|
+
border-radius: 34px;
|
|
167
|
+
background:
|
|
168
|
+
linear-gradient(145deg, #3b465f, #111827 52%, #050812);
|
|
169
|
+
padding: 16px;
|
|
170
|
+
box-shadow:
|
|
171
|
+
0 28px 80px rgba(0, 0, 0, 0.42),
|
|
172
|
+
inset 0 0 0 1px rgba(255, 255, 255, 0.08),
|
|
173
|
+
inset 0 0 0 7px rgba(255, 255, 255, 0.04);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.screen-shell {
|
|
177
|
+
position: relative;
|
|
178
|
+
overflow: hidden;
|
|
179
|
+
border-radius: 22px;
|
|
180
|
+
background: #0b1020;
|
|
181
|
+
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.10);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
iframe {
|
|
185
|
+
display: block;
|
|
186
|
+
border: 0;
|
|
187
|
+
background: #101419;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.home-indicator {
|
|
191
|
+
position: absolute;
|
|
192
|
+
left: 50%;
|
|
193
|
+
bottom: 7px;
|
|
194
|
+
width: 110px;
|
|
195
|
+
height: 4px;
|
|
196
|
+
transform: translateX(-50%);
|
|
197
|
+
border-radius: 999px;
|
|
198
|
+
background: rgba(255, 255, 255, 0.34);
|
|
199
|
+
pointer-events: none;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.camera {
|
|
203
|
+
position: absolute;
|
|
204
|
+
top: 8px;
|
|
205
|
+
left: 50%;
|
|
206
|
+
width: 42px;
|
|
207
|
+
height: 5px;
|
|
208
|
+
transform: translateX(-50%);
|
|
209
|
+
border-radius: 999px;
|
|
210
|
+
background: rgba(255, 255, 255, 0.22);
|
|
211
|
+
pointer-events: none;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
@media (max-width: 980px) {
|
|
215
|
+
.topbar {
|
|
216
|
+
grid-template-columns: 1fr;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.controls {
|
|
220
|
+
justify-content: flex-start;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.source-form,
|
|
224
|
+
.source-form input {
|
|
225
|
+
width: 100%;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
</style>
|
|
229
|
+
</head>
|
|
230
|
+
<body>
|
|
231
|
+
<header class="topbar">
|
|
232
|
+
<div class="title-block">
|
|
233
|
+
<h1 id="preview-title">CTM iPad Preview</h1>
|
|
234
|
+
<p>Same-origin preview of the mobile CTM app. Use this for quick iPad layout checks before testing on a real iPad.</p>
|
|
235
|
+
</div>
|
|
236
|
+
<div class="controls" aria-label="Preview controls">
|
|
237
|
+
<div class="device-group" role="group" aria-label="Device size">
|
|
238
|
+
<button type="button" data-device="ipad-test-portrait">Test Portrait</button>
|
|
239
|
+
<button type="button" data-device="ipad-test-landscape">Test Landscape</button>
|
|
240
|
+
<button type="button" data-device="ipad-11-portrait">11 Portrait</button>
|
|
241
|
+
<button type="button" data-device="ipad-11-landscape">11 Landscape</button>
|
|
242
|
+
</div>
|
|
243
|
+
<form id="source-form" class="source-form">
|
|
244
|
+
<label for="preview-src">Source</label>
|
|
245
|
+
<input id="preview-src" type="text" inputmode="url" autocomplete="off" spellcheck="false" value="/m/">
|
|
246
|
+
<button id="preview-load" type="submit">Load</button>
|
|
247
|
+
<button id="preview-reload" type="button">Reload</button>
|
|
248
|
+
<button id="preview-open" type="button">Open</button>
|
|
249
|
+
</form>
|
|
250
|
+
</div>
|
|
251
|
+
</header>
|
|
252
|
+
|
|
253
|
+
<main id="stage" class="stage">
|
|
254
|
+
<div id="device-slot" class="device-slot">
|
|
255
|
+
<section id="device" class="device" aria-label="iPad preview device">
|
|
256
|
+
<div class="camera" aria-hidden="true"></div>
|
|
257
|
+
<div id="screen-shell" class="screen-shell">
|
|
258
|
+
<iframe id="preview-frame" title="CTM mobile app iPad preview" src="/m/"></iframe>
|
|
259
|
+
</div>
|
|
260
|
+
<div class="home-indicator" aria-hidden="true"></div>
|
|
261
|
+
</section>
|
|
262
|
+
</div>
|
|
263
|
+
<div class="preview-meta" aria-live="polite">
|
|
264
|
+
<span class="status-dot" aria-hidden="true"></span>
|
|
265
|
+
<span id="preview-size">768 x 1024</span>
|
|
266
|
+
<span id="preview-scale">100%</span>
|
|
267
|
+
</div>
|
|
268
|
+
</main>
|
|
269
|
+
|
|
270
|
+
<script>
|
|
271
|
+
(function initIpadPreview() {
|
|
272
|
+
var DEVICES = {
|
|
273
|
+
'ipad-test-portrait': { label: 'Test Portrait', width: 768, height: 1024 },
|
|
274
|
+
'ipad-test-landscape': { label: 'Test Landscape', width: 1024, height: 768 },
|
|
275
|
+
'ipad-11-portrait': { label: '11 Portrait', width: 834, height: 1194 },
|
|
276
|
+
'ipad-11-landscape': { label: '11 Landscape', width: 1194, height: 834 },
|
|
277
|
+
};
|
|
278
|
+
var params = new URLSearchParams(window.location.search || '');
|
|
279
|
+
var stage = document.getElementById('stage');
|
|
280
|
+
var slot = document.getElementById('device-slot');
|
|
281
|
+
var device = document.getElementById('device');
|
|
282
|
+
var screen = document.getElementById('screen-shell');
|
|
283
|
+
var frame = document.getElementById('preview-frame');
|
|
284
|
+
var srcInput = document.getElementById('preview-src');
|
|
285
|
+
var sizeLabel = document.getElementById('preview-size');
|
|
286
|
+
var scaleLabel = document.getElementById('preview-scale');
|
|
287
|
+
var currentDevice = DEVICES[params.get('device')] ? params.get('device') : 'ipad-test-portrait';
|
|
288
|
+
|
|
289
|
+
function normalizeSrc(value) {
|
|
290
|
+
var raw = String(value || '').trim() || '/m/';
|
|
291
|
+
if (/^https?:\/\//i.test(raw)) return raw;
|
|
292
|
+
if (raw.charAt(0) !== '/') raw = '/' + raw;
|
|
293
|
+
return raw;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function setPressed(key) {
|
|
297
|
+
document.querySelectorAll('[data-device]').forEach(function(btn) {
|
|
298
|
+
btn.setAttribute('aria-pressed', btn.getAttribute('data-device') === key ? 'true' : 'false');
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function updateUrl() {
|
|
303
|
+
var next = new URL(window.location.href);
|
|
304
|
+
next.searchParams.set('device', currentDevice);
|
|
305
|
+
next.searchParams.set('src', srcInput.value);
|
|
306
|
+
window.history.replaceState(null, '', next);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function fitDevice() {
|
|
310
|
+
var spec = DEVICES[currentDevice] || DEVICES['ipad-test-portrait'];
|
|
311
|
+
var outerW = spec.width + 32;
|
|
312
|
+
var outerH = spec.height + 32;
|
|
313
|
+
screen.style.width = spec.width + 'px';
|
|
314
|
+
screen.style.height = spec.height + 'px';
|
|
315
|
+
frame.style.width = spec.width + 'px';
|
|
316
|
+
frame.style.height = spec.height + 'px';
|
|
317
|
+
device.style.width = outerW + 'px';
|
|
318
|
+
device.style.height = outerH + 'px';
|
|
319
|
+
var availableW = Math.max(320, stage.clientWidth - 42);
|
|
320
|
+
var availableH = Math.max(320, stage.clientHeight - 42);
|
|
321
|
+
var scale = Math.min(1, availableW / outerW, availableH / outerH);
|
|
322
|
+
device.style.transform = 'scale(' + scale.toFixed(4) + ')';
|
|
323
|
+
slot.style.width = Math.ceil(outerW * scale) + 'px';
|
|
324
|
+
slot.style.height = Math.ceil(outerH * scale) + 'px';
|
|
325
|
+
sizeLabel.textContent = spec.width + ' x ' + spec.height;
|
|
326
|
+
scaleLabel.textContent = Math.round(scale * 100) + '%';
|
|
327
|
+
setPressed(currentDevice);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function loadFrame(value) {
|
|
331
|
+
var src = normalizeSrc(value);
|
|
332
|
+
srcInput.value = src;
|
|
333
|
+
frame.setAttribute('src', src);
|
|
334
|
+
updateUrl();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
document.querySelectorAll('[data-device]').forEach(function(btn) {
|
|
338
|
+
btn.addEventListener('click', function() {
|
|
339
|
+
currentDevice = btn.getAttribute('data-device') || currentDevice;
|
|
340
|
+
fitDevice();
|
|
341
|
+
updateUrl();
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
document.getElementById('source-form').addEventListener('submit', function(event) {
|
|
346
|
+
event.preventDefault();
|
|
347
|
+
loadFrame(srcInput.value);
|
|
348
|
+
});
|
|
349
|
+
document.getElementById('preview-reload').addEventListener('click', function() {
|
|
350
|
+
try { frame.contentWindow.location.reload(); } catch (_) { frame.setAttribute('src', frame.getAttribute('src') || '/m/'); }
|
|
351
|
+
});
|
|
352
|
+
document.getElementById('preview-open').addEventListener('click', function() {
|
|
353
|
+
window.open(frame.getAttribute('src') || '/m/', '_blank', 'noopener,noreferrer');
|
|
354
|
+
});
|
|
355
|
+
window.addEventListener('resize', fitDevice);
|
|
356
|
+
|
|
357
|
+
srcInput.value = normalizeSrc(params.get('src') || '/m/');
|
|
358
|
+
frame.setAttribute('src', srcInput.value);
|
|
359
|
+
fitDevice();
|
|
360
|
+
})();
|
|
361
|
+
</script>
|
|
362
|
+
</body>
|
|
363
|
+
</html>
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const DOC_EXT_RE = /\.(?:md|markdown|mdown|txt)$/i;
|
|
5
|
+
const DOC_TOKEN_RE = /(^|[\s([{<"'`])([^\s<>"'`]*\.(?:md|markdown|mdown|txt)(?::\d+)?)(?=$|[\s)\]}>.,;!?])/gi;
|
|
6
|
+
const SKIP_SELECTOR = 'a,code,pre,kbd,samp,textarea,input,select,button,script,style';
|
|
7
|
+
|
|
8
|
+
function _state() {
|
|
9
|
+
return (typeof window !== 'undefined' && (window._ctmState || window.state)) || {};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function _recentSessions() {
|
|
13
|
+
try {
|
|
14
|
+
return Array.isArray(window.allRecentSessions) ? window.allRecentSessions : [];
|
|
15
|
+
} catch {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function splitReference(raw, fallbackLine) {
|
|
21
|
+
let value = String(raw || '').trim();
|
|
22
|
+
while (/[.,;!?]$/.test(value)) value = value.slice(0, -1);
|
|
23
|
+
const match = value.match(/^(.*):(\d+)$/);
|
|
24
|
+
const line = match ? Math.max(1, Number(match[2]) || 1) : Math.max(1, Number(fallbackLine) || 1);
|
|
25
|
+
const path = match ? match[1] : value;
|
|
26
|
+
return { raw: value, path, line, label: value };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function _looksLikeDocumentReference(raw) {
|
|
30
|
+
const ref = splitReference(raw);
|
|
31
|
+
if (!ref.path || !DOC_EXT_RE.test(ref.path)) return false;
|
|
32
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(ref.path)) return false;
|
|
33
|
+
if (/^data:/i.test(ref.path)) return false;
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function findReferences(text) {
|
|
38
|
+
const source = String(text || '');
|
|
39
|
+
const refs = [];
|
|
40
|
+
DOC_TOKEN_RE.lastIndex = 0;
|
|
41
|
+
let match;
|
|
42
|
+
while ((match = DOC_TOKEN_RE.exec(source))) {
|
|
43
|
+
const prefix = match[1] || '';
|
|
44
|
+
const token = match[2] || '';
|
|
45
|
+
if (!_looksLikeDocumentReference(token)) continue;
|
|
46
|
+
const start = match.index + prefix.length;
|
|
47
|
+
const end = start + token.length;
|
|
48
|
+
refs.push({ ...splitReference(token), start, end });
|
|
49
|
+
}
|
|
50
|
+
return refs;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function _rowMatchesSession(row, sessionId) {
|
|
54
|
+
if (!row || !sessionId) return false;
|
|
55
|
+
const ids = [
|
|
56
|
+
row.sessionId, row.session_id, row.id,
|
|
57
|
+
row.provisionalId, row.provisional_id,
|
|
58
|
+
row.agentSessionId, row.agent_session_id,
|
|
59
|
+
row.claudeSessionId, row.claude_session_id,
|
|
60
|
+
];
|
|
61
|
+
if (Array.isArray(row.agentSessionIds)) ids.push(...row.agentSessionIds);
|
|
62
|
+
if (Array.isArray(row.agent_session_ids)) ids.push(...row.agent_session_ids);
|
|
63
|
+
return ids.map(String).includes(String(sessionId));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function contextForSession(sessionId) {
|
|
67
|
+
const state = _state();
|
|
68
|
+
const id = sessionId || state.reviewingSessionId || state.activeTab || '';
|
|
69
|
+
const live = id && state.sessions && typeof state.sessions.get === 'function'
|
|
70
|
+
? state.sessions.get(id)
|
|
71
|
+
: null;
|
|
72
|
+
const meta = live && live.meta || {};
|
|
73
|
+
const recent = _recentSessions().find(row => _rowMatchesSession(row, id)) || null;
|
|
74
|
+
const cwd = meta.cwd || live?.cwd || meta.project || live?.project || recent?.cwd || recent?.project || '';
|
|
75
|
+
return { sessionId: id || '', cwd: cwd || '' };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function defaultContext() {
|
|
79
|
+
return contextForSession();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function hashForReference(raw, opts) {
|
|
83
|
+
const ref = splitReference(raw, opts && opts.line);
|
|
84
|
+
const params = new URLSearchParams();
|
|
85
|
+
params.set('type', 'doc');
|
|
86
|
+
params.set('path', ref.path);
|
|
87
|
+
params.set('line', String(ref.line));
|
|
88
|
+
if (opts && opts.cwd) params.set('cwd', opts.cwd);
|
|
89
|
+
if (opts && opts.sessionId) params.set('session', opts.sessionId);
|
|
90
|
+
return '#review&' + params.toString();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function openReference(raw, opts) {
|
|
94
|
+
const context = Object.assign({}, opts || {});
|
|
95
|
+
const ref = splitReference(raw, context.line);
|
|
96
|
+
if (typeof window.CR !== 'undefined' && typeof window.CR.openDocumentReference === 'function') {
|
|
97
|
+
window.CR.openDocumentReference(ref.label, context);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
window.location.hash = hashForReference(ref.label, context);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function _makeAnchor(ref, context) {
|
|
104
|
+
const a = document.createElement('a');
|
|
105
|
+
a.className = 'ctm-doc-review-link';
|
|
106
|
+
a.href = hashForReference(ref.label, context);
|
|
107
|
+
a.textContent = ref.label;
|
|
108
|
+
a.title = 'Open document in Review';
|
|
109
|
+
a.dataset.ctmDocPath = ref.path;
|
|
110
|
+
a.dataset.ctmDocLine = String(ref.line);
|
|
111
|
+
if (context && context.cwd) a.dataset.ctmDocCwd = context.cwd;
|
|
112
|
+
if (context && context.sessionId) a.dataset.ctmDocSessionId = context.sessionId;
|
|
113
|
+
a.addEventListener('click', (event) => {
|
|
114
|
+
event.preventDefault();
|
|
115
|
+
openReference(ref.label, context);
|
|
116
|
+
});
|
|
117
|
+
return a;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function _replaceTextNode(node, context) {
|
|
121
|
+
const text = node.nodeValue || '';
|
|
122
|
+
const refs = findReferences(text);
|
|
123
|
+
if (!refs.length) return;
|
|
124
|
+
const frag = document.createDocumentFragment();
|
|
125
|
+
let offset = 0;
|
|
126
|
+
for (const ref of refs) {
|
|
127
|
+
if (ref.start > offset) frag.appendChild(document.createTextNode(text.slice(offset, ref.start)));
|
|
128
|
+
frag.appendChild(_makeAnchor(ref, context));
|
|
129
|
+
offset = ref.end;
|
|
130
|
+
}
|
|
131
|
+
if (offset < text.length) frag.appendChild(document.createTextNode(text.slice(offset)));
|
|
132
|
+
node.parentNode.replaceChild(frag, node);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function linkifyElement(root, opts) {
|
|
136
|
+
if (!root || typeof document === 'undefined') return;
|
|
137
|
+
const context = Object.assign({}, opts || defaultContext());
|
|
138
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
|
139
|
+
acceptNode(node) {
|
|
140
|
+
const parent = node && node.parentElement;
|
|
141
|
+
if (!parent || !String(node.nodeValue || '').trim()) return NodeFilter.FILTER_REJECT;
|
|
142
|
+
if (parent.closest && parent.closest(SKIP_SELECTOR)) return NodeFilter.FILTER_REJECT;
|
|
143
|
+
return findReferences(node.nodeValue || '').length
|
|
144
|
+
? NodeFilter.FILTER_ACCEPT
|
|
145
|
+
: NodeFilter.FILTER_REJECT;
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
const nodes = [];
|
|
149
|
+
while (walker.nextNode()) nodes.push(walker.currentNode);
|
|
150
|
+
for (const node of nodes) _replaceTextNode(node, context);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function _bufferLineText(term, y) {
|
|
154
|
+
try {
|
|
155
|
+
const line = term && term.buffer && term.buffer.active && term.buffer.active.getLine(y - 1);
|
|
156
|
+
return line ? line.translateToString(true) : '';
|
|
157
|
+
} catch {
|
|
158
|
+
return '';
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function registerTerminalLinkProvider(term, sessionId) {
|
|
163
|
+
if (!term || typeof term.registerLinkProvider !== 'function') return null;
|
|
164
|
+
return term.registerLinkProvider({
|
|
165
|
+
provideLinks(y, callback) {
|
|
166
|
+
const text = _bufferLineText(term, y);
|
|
167
|
+
const context = contextForSession(sessionId);
|
|
168
|
+
const links = findReferences(text).map(ref => ({
|
|
169
|
+
range: {
|
|
170
|
+
start: { x: ref.start + 1, y },
|
|
171
|
+
end: { x: ref.end, y },
|
|
172
|
+
},
|
|
173
|
+
text: ref.label,
|
|
174
|
+
activate() { openReference(ref.label, context); },
|
|
175
|
+
hover() {},
|
|
176
|
+
leave() {},
|
|
177
|
+
}));
|
|
178
|
+
callback(links.length ? links : undefined);
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const api = {
|
|
184
|
+
findReferences,
|
|
185
|
+
splitReference,
|
|
186
|
+
contextForSession,
|
|
187
|
+
defaultContext,
|
|
188
|
+
hashForReference,
|
|
189
|
+
openReference,
|
|
190
|
+
linkifyElement,
|
|
191
|
+
registerTerminalLinkProvider,
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
if (typeof window !== 'undefined') window.CTMDocLinks = api;
|
|
195
|
+
if (typeof module !== 'undefined' && module.exports) module.exports = api;
|
|
196
|
+
})();
|
|
@@ -1741,9 +1741,21 @@
|
|
|
1741
1741
|
return _nodeWithin(root, sel.anchorNode) || _nodeWithin(root, sel.focusNode);
|
|
1742
1742
|
};
|
|
1743
1743
|
|
|
1744
|
+
MR.isPromptTurnPromptTextTarget = function (ev, headerEl) {
|
|
1745
|
+
if (!ev || !headerEl || !ev.target || !ev.target.closest) return false;
|
|
1746
|
+
const textEl = ev.target.closest('.prompt-turn-prompt .msg-text');
|
|
1747
|
+
return !!(textEl && headerEl.contains(textEl));
|
|
1748
|
+
};
|
|
1749
|
+
|
|
1744
1750
|
MR.shouldKeepPromptTextSelection = function (ev, headerEl) {
|
|
1751
|
+
if (!MR.isPromptTurnPromptTextTarget(ev, headerEl)) return false;
|
|
1752
|
+
return MR.hasTextSelectionInside(headerEl);
|
|
1753
|
+
};
|
|
1754
|
+
|
|
1755
|
+
MR.shouldIgnorePromptTurnHeaderToggle = function (ev, headerEl) {
|
|
1745
1756
|
if (!ev || !headerEl || !ev.target || !ev.target.closest) return false;
|
|
1746
|
-
if (
|
|
1757
|
+
if (ev.target.closest('a,button,input,textarea,select')) return true;
|
|
1758
|
+
if (MR.isPromptTurnPromptTextTarget(ev, headerEl)) return true;
|
|
1747
1759
|
return MR.hasTextSelectionInside(headerEl);
|
|
1748
1760
|
};
|
|
1749
1761
|
|
|
@@ -1815,8 +1827,7 @@
|
|
|
1815
1827
|
function _wirePromptTurnHeader(turnEl, header) {
|
|
1816
1828
|
if (!turnEl || !header) return;
|
|
1817
1829
|
const toggle = (ev) => {
|
|
1818
|
-
if (
|
|
1819
|
-
if (MR.shouldKeepPromptTextSelection(ev, header)) return;
|
|
1830
|
+
if (MR.shouldIgnorePromptTurnHeaderToggle(ev, header)) return;
|
|
1820
1831
|
MR.setPromptTurnExpanded(turnEl, !turnEl.classList.contains('expanded'));
|
|
1821
1832
|
};
|
|
1822
1833
|
header.addEventListener('click', toggle);
|
|
@@ -336,11 +336,12 @@ CR.openReview = async function(sessionId, projectPath) {
|
|
|
336
336
|
};
|
|
337
337
|
|
|
338
338
|
CR.openDocumentReview = async function(filePath, line, opts) {
|
|
339
|
+
opts = opts || {};
|
|
339
340
|
const seq = ++crState._openSeq;
|
|
340
341
|
const targetLine = Math.max(1, Number(line) || 1);
|
|
341
342
|
crState._view = 'review';
|
|
342
343
|
crState.reviewType = 'doc';
|
|
343
|
-
crState.sessionId = opts
|
|
344
|
+
crState.sessionId = opts.sessionId || null;
|
|
344
345
|
crState.projectPath = null;
|
|
345
346
|
crState.baseRef = '';
|
|
346
347
|
crState.branch = '';
|
|
@@ -360,9 +361,13 @@ CR.openDocumentReview = async function(filePath, line, opts) {
|
|
|
360
361
|
if (typeof window.renderTabs === 'function') window.renderTabs();
|
|
361
362
|
}
|
|
362
363
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
364
|
+
const routeParams = new URLSearchParams();
|
|
365
|
+
routeParams.set('type', 'doc');
|
|
366
|
+
routeParams.set('path', filePath || '');
|
|
367
|
+
routeParams.set('line', String(targetLine));
|
|
368
|
+
if (opts.cwd) routeParams.set('cwd', opts.cwd);
|
|
369
|
+
if (crState.sessionId) routeParams.set('session', crState.sessionId);
|
|
370
|
+
history.replaceState(null, '', location.pathname + location.search + '#review&' + routeParams.toString());
|
|
366
371
|
|
|
367
372
|
renderDocHeader();
|
|
368
373
|
renderLoading();
|
|
@@ -371,6 +376,7 @@ CR.openDocumentReview = async function(filePath, line, opts) {
|
|
|
371
376
|
const data = await api('/reviews/document-review', 'POST', {
|
|
372
377
|
path: filePath,
|
|
373
378
|
line: targetLine,
|
|
379
|
+
cwd: opts.cwd || undefined,
|
|
374
380
|
session_id: crState.sessionId || undefined,
|
|
375
381
|
});
|
|
376
382
|
if (seq !== crState._openSeq || crState.reviewType !== 'doc') return;
|
|
@@ -381,6 +387,14 @@ CR.openDocumentReview = async function(filePath, line, opts) {
|
|
|
381
387
|
crState.comments = data.review?.comments || [];
|
|
382
388
|
const targetBlock = findDocBlockForLine(crState.document?.line || targetLine);
|
|
383
389
|
crState.activeBlock = targetBlock?.id || null;
|
|
390
|
+
if (crState.document?.path) {
|
|
391
|
+
const canonicalParams = new URLSearchParams();
|
|
392
|
+
canonicalParams.set('type', 'doc');
|
|
393
|
+
canonicalParams.set('path', crState.document.path);
|
|
394
|
+
canonicalParams.set('line', String(crState.document.line || targetLine));
|
|
395
|
+
if (crState.sessionId) canonicalParams.set('session', crState.sessionId);
|
|
396
|
+
history.replaceState(null, '', location.pathname + location.search + '#review&' + canonicalParams.toString());
|
|
397
|
+
}
|
|
384
398
|
renderDocHeader();
|
|
385
399
|
renderDocTree();
|
|
386
400
|
renderDocReview();
|
|
@@ -392,6 +406,13 @@ CR.openDocumentReview = async function(filePath, line, opts) {
|
|
|
392
406
|
}
|
|
393
407
|
};
|
|
394
408
|
|
|
409
|
+
CR.openDocumentReference = async function(rawReference, opts) {
|
|
410
|
+
const parsed = window.CTMDocLinks && typeof window.CTMDocLinks.splitReference === 'function'
|
|
411
|
+
? window.CTMDocLinks.splitReference(rawReference, opts && opts.line)
|
|
412
|
+
: { path: String(rawReference || ''), line: opts && opts.line || 1 };
|
|
413
|
+
return CR.openDocumentReview(parsed.path, parsed.line, opts || {});
|
|
414
|
+
};
|
|
415
|
+
|
|
395
416
|
async function loadDiff() {
|
|
396
417
|
const seq = ++crState._diffSeq;
|
|
397
418
|
renderLoading();
|
|
@@ -1670,7 +1691,7 @@ CR.handleFilesChanged = function(msg) {
|
|
|
1670
1691
|
updateBadge(total);
|
|
1671
1692
|
|
|
1672
1693
|
// Refresh project list if it's currently visible (not in a diff review)
|
|
1673
|
-
if (!crState.reviewId) {
|
|
1694
|
+
if (!crState.reviewId && crState._view === 'projects') {
|
|
1674
1695
|
const panel = document.getElementById('codereview-panel');
|
|
1675
1696
|
if (panel && panel.classList.contains('active')) {
|
|
1676
1697
|
CR.showProjectList();
|
|
@@ -1934,7 +1955,10 @@ window.crState = crState;
|
|
|
1934
1955
|
if (window._ctmState?.pendingDocumentReview) {
|
|
1935
1956
|
const pending = window._ctmState.pendingDocumentReview;
|
|
1936
1957
|
delete window._ctmState.pendingDocumentReview;
|
|
1937
|
-
setTimeout(() => CR.openDocumentReview(pending.path, pending.line || 1
|
|
1958
|
+
setTimeout(() => CR.openDocumentReview(pending.path, pending.line || 1, {
|
|
1959
|
+
cwd: pending.cwd || '',
|
|
1960
|
+
sessionId: pending.sessionId || '',
|
|
1961
|
+
}), 0);
|
|
1938
1962
|
}
|
|
1939
1963
|
|
|
1940
1964
|
})();
|