dev3000 0.0.170 → 0.0.173
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/dist/cdp-monitor.d.ts +3 -1
- package/dist/cdp-monitor.d.ts.map +1 -1
- package/dist/cdp-monitor.js +44 -9
- package/dist/cdp-monitor.js.map +1 -1
- package/dist/cli.js +196 -81
- package/dist/cli.js.map +1 -1
- package/dist/commands/crawl.d.ts.map +1 -1
- package/dist/commands/crawl.js +1 -1
- package/dist/commands/crawl.js.map +1 -1
- package/dist/dev-environment.d.ts +10 -1
- package/dist/dev-environment.d.ts.map +1 -1
- package/dist/dev-environment.js +72 -32
- package/dist/dev-environment.js.map +1 -1
- package/dist/portless.d.ts +15 -0
- package/dist/portless.d.ts.map +1 -0
- package/dist/portless.js +180 -0
- package/dist/portless.js.map +1 -0
- package/dist/screencast-manager.d.ts +3 -2
- package/dist/screencast-manager.d.ts.map +1 -1
- package/dist/screencast-manager.js +14 -5
- package/dist/screencast-manager.js.map +1 -1
- package/dist/skills/d3k/SKILL.md +40 -0
- package/dist/src/loading.html +2 -2
- package/dist/src/tui-interface-impl.tsx +24 -4
- package/dist/tui-interface-impl.d.ts +2 -0
- package/dist/tui-interface-impl.d.ts.map +1 -1
- package/dist/tui-interface-impl.js +18 -3
- package/dist/tui-interface-impl.js.map +1 -1
- package/dist/tui-interface-opentui.d.ts +2 -0
- package/dist/tui-interface-opentui.d.ts.map +1 -1
- package/dist/tui-interface-opentui.js +265 -24
- package/dist/tui-interface-opentui.js.map +1 -1
- package/dist/tui-interface.d.ts +3 -0
- package/dist/tui-interface.d.ts.map +1 -1
- package/dist/tui-interface.js +12 -4
- package/dist/tui-interface.js.map +1 -1
- package/dist/utils/agent-browser.d.ts.map +1 -1
- package/dist/utils/agent-browser.js +43 -19
- package/dist/utils/agent-browser.js.map +1 -1
- package/dist/utils/agent-selection.d.ts.map +1 -1
- package/dist/utils/agent-selection.js +1 -0
- package/dist/utils/agent-selection.js.map +1 -1
- package/dist/utils/browser-command-argv.d.ts +8 -0
- package/dist/utils/browser-command-argv.d.ts.map +1 -0
- package/dist/utils/browser-command-argv.js +41 -0
- package/dist/utils/browser-command-argv.js.map +1 -0
- package/dist/utils/build-version.d.ts +2 -0
- package/dist/utils/build-version.d.ts.map +1 -0
- package/dist/utils/build-version.js +8 -0
- package/dist/utils/build-version.js.map +1 -0
- package/package.json +24 -21
- package/src/tui-interface-impl.tsx +24 -4
|
@@ -117,6 +117,7 @@ class D3kTUI {
|
|
|
117
117
|
statusText = null;
|
|
118
118
|
// State
|
|
119
119
|
appPort;
|
|
120
|
+
appUrl;
|
|
120
121
|
useHttps;
|
|
121
122
|
portConfirmed = false;
|
|
122
123
|
updateInfo;
|
|
@@ -130,14 +131,21 @@ class D3kTUI {
|
|
|
130
131
|
debugMode = false;
|
|
131
132
|
lastFocusedId = null;
|
|
132
133
|
debugLogFile = join(process.env.HOME || tmpdir(), ".d3k", "tui-debug.log");
|
|
134
|
+
selectionDebugEnabled = process.env.D3K_TUI_SELECTION_DEBUG !== "0";
|
|
135
|
+
selectionDebugLogFile = join(process.env.HOME || tmpdir(), ".d3k", "tui-selection-debug.log");
|
|
133
136
|
lastCopiedSelection = null;
|
|
134
137
|
selectionCopyHandler = null;
|
|
135
138
|
isCompact = false;
|
|
136
139
|
isVeryCompact = false;
|
|
137
140
|
isRebuilding = false;
|
|
141
|
+
logContentWidth = 80;
|
|
142
|
+
logRenderables = new Map();
|
|
143
|
+
logSelection = null;
|
|
144
|
+
logSelectionBackground = RGBA.fromInts(70, 130, 180);
|
|
138
145
|
constructor(options) {
|
|
139
146
|
this.options = options;
|
|
140
147
|
this.appPort = options.appPort;
|
|
148
|
+
this.appUrl = options.appUrl || null;
|
|
141
149
|
this.useHttps = options.useHttps || false;
|
|
142
150
|
this.updateInfo = options.updateInfo || null;
|
|
143
151
|
}
|
|
@@ -167,6 +175,7 @@ class D3kTUI {
|
|
|
167
175
|
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
168
176
|
process.stdout.write = createFilteredWrite(originalStdoutWrite);
|
|
169
177
|
process.stderr.write = createFilteredWrite(originalStderrWrite);
|
|
178
|
+
this.initializeSelectionDebugLog();
|
|
170
179
|
this.renderer = await createCliRenderer(config);
|
|
171
180
|
// Setup UI after renderer is created
|
|
172
181
|
this.setupUI();
|
|
@@ -182,10 +191,15 @@ class D3kTUI {
|
|
|
182
191
|
// Force a redraw after start to ensure borders render correctly
|
|
183
192
|
process.stdout.write("\x1b[2J\x1b[H");
|
|
184
193
|
this.renderer.requestRender();
|
|
194
|
+
setTimeout(() => {
|
|
195
|
+
this.logSelectionDebug(`[START] renderer=${this.renderer?.width}x${this.renderer?.height} logContentWidth=${this.logContentWidth}`);
|
|
196
|
+
this.logLogPaneMetrics("post-start");
|
|
197
|
+
}, 150);
|
|
185
198
|
return {
|
|
186
199
|
app: { unmount: () => this.shutdown() },
|
|
187
200
|
updateStatus: (status) => this.setStatus(status),
|
|
188
201
|
updateAppPort: (port) => this.setAppPort(port),
|
|
202
|
+
updateAppUrl: (url) => this.setAppUrl(url),
|
|
189
203
|
updateUpdateInfo: (info) => this.setUpdateInfo(info),
|
|
190
204
|
updateUseHttps: (useHttps) => this.setUseHttps(useHttps)
|
|
191
205
|
};
|
|
@@ -196,6 +210,7 @@ class D3kTUI {
|
|
|
196
210
|
const { width, height } = this.renderer;
|
|
197
211
|
const isCompact = width < 80;
|
|
198
212
|
const isVeryCompact = width < 60 || height < 15;
|
|
213
|
+
this.logContentWidth = Math.max(20, width - 6);
|
|
199
214
|
this.isCompact = isCompact;
|
|
200
215
|
this.isVeryCompact = isVeryCompact;
|
|
201
216
|
// Main container
|
|
@@ -230,7 +245,6 @@ class D3kTUI {
|
|
|
230
245
|
stickyStart: "bottom",
|
|
231
246
|
scrollAcceleration: new MacOSScrollAccel({ maxMultiplier: 8 }),
|
|
232
247
|
viewportCulling: false, // Disable culling to ensure all items are in hit grid
|
|
233
|
-
paddingLeft: 1, // Force left position calculation for selection hit testing
|
|
234
248
|
onMouse: (event) => {
|
|
235
249
|
// Log ALL mouse events to understand the flow
|
|
236
250
|
if (event.type === "down" || event.type === "up") {
|
|
@@ -241,47 +255,65 @@ class D3kTUI {
|
|
|
241
255
|
const targetW = target?.width ?? "?";
|
|
242
256
|
const targetH = target?.height ?? "?";
|
|
243
257
|
this.debugLog(`[MOUSE_${event.type.toUpperCase()}] screen(${event.x},${event.y}) target=${targetId} pos(${targetX},${targetY}) size(${targetW}x${targetH}) selectable=${target?.selectable} hasSelection=${this.renderer?.hasSelection}`);
|
|
258
|
+
this.logSelectionDebug(`[MOUSE_${event.type.toUpperCase()}] screen(${event.x},${event.y}) target=${targetId} pos(${targetX},${targetY}) size(${targetW}x${targetH}) selectable=${target?.selectable} ${this.describeSelection()}`);
|
|
244
259
|
}
|
|
245
260
|
},
|
|
246
261
|
onMouseDown: (event) => {
|
|
247
|
-
if (
|
|
248
|
-
const
|
|
249
|
-
if (
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
262
|
+
if (event.button === 0) {
|
|
263
|
+
const logLine = this.getSelectionLogLine(event);
|
|
264
|
+
if (logLine) {
|
|
265
|
+
this.logSelectionDebug(`[START_SELECTION] target=${logLine.id} pos(${logLine.x},${logLine.y}) size(${logLine.width}x${logLine.height}) screen(${event.x},${event.y})`);
|
|
266
|
+
this.beginLogSelection(logLine.id, event.x, event.y);
|
|
267
|
+
this.logSelectionDebug(`[START_SELECTION_AFTER] ${this.describeSelection()}`);
|
|
254
268
|
}
|
|
255
269
|
}
|
|
256
270
|
const target = event.target;
|
|
257
271
|
const targetId = target?.id || "(none)";
|
|
258
272
|
const selection = this.renderer?.getSelection();
|
|
259
|
-
this.debugLog(`[MOUSE_DOWN_CB] x=${event.x}, y=${event.y}, target=${targetId}, hasSelection=${this.renderer?.hasSelection},
|
|
273
|
+
this.debugLog(`[MOUSE_DOWN_CB] x=${event.x}, y=${event.y}, target=${targetId}, hasSelection=${this.renderer?.hasSelection}, isDragging=${selection?.isDragging}`);
|
|
274
|
+
this.logSelectionDebug(`[MOUSE_DOWN_CB] x=${event.x}, y=${event.y}, target=${targetId} ${this.describeSelection()}`);
|
|
260
275
|
},
|
|
261
276
|
onMouseUp: (event) => {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
277
|
+
if (event.button === 0) {
|
|
278
|
+
const logLine = this.getSelectionLogLine(event);
|
|
279
|
+
this.finishLogSelection(logLine?.id ?? null, event.x, event.y);
|
|
280
|
+
this.logSelectionDebug(`[FINISH_SELECTION] target=${logLine?.id || "(none)"} screen(${event.x},${event.y}) ${this.describeSelection()}`);
|
|
281
|
+
}
|
|
282
|
+
const selectedText = this.getSelectedLogText() || "(none)";
|
|
283
|
+
this.debugLog(`[MOUSE_UP] x=${event.x}, y=${event.y}, hasSelection=${Boolean(this.logSelection)}, text="${selectedText.slice(0, 30)}"`);
|
|
284
|
+
this.logSelectionDebug(`[MOUSE_UP] x=${event.x}, y=${event.y}, text="${selectedText.slice(0, 60)}" ${this.describeSelection()}`);
|
|
265
285
|
},
|
|
266
286
|
onMouseDrag: (event) => {
|
|
287
|
+
if (this.logSelection) {
|
|
288
|
+
const logLine = this.getSelectionLogLine(event);
|
|
289
|
+
this.updateLogSelection(logLine?.id ?? null, event.x, event.y);
|
|
290
|
+
this.logSelectionDebug(`[UPDATE_SELECTION] target=${logLine?.id || "(none)"} screen(${event.x},${event.y}) ${this.describeSelection()}`);
|
|
291
|
+
}
|
|
267
292
|
const target = event.target;
|
|
268
293
|
const targetId = target?.id || "(none)";
|
|
269
|
-
this.debugLog(`[MOUSE_DRAG] x=${event.x}, y=${event.y}, target=${targetId},
|
|
294
|
+
this.debugLog(`[MOUSE_DRAG] x=${event.x}, y=${event.y}, target=${targetId}, isDragging=${event.isDragging}`);
|
|
295
|
+
this.logSelectionDebug(`[MOUSE_DRAG] x=${event.x}, y=${event.y}, target=${targetId}, eventDragging=${event.isDragging} ${this.describeSelection()}`);
|
|
270
296
|
}
|
|
271
297
|
});
|
|
298
|
+
this.logsScrollBox.content.flexDirection = "column";
|
|
299
|
+
this.logsScrollBox.content.left = 0;
|
|
300
|
+
this.logsScrollBox.content.top = 0;
|
|
301
|
+
this.logsScrollBox.content.width = this.logContentWidth;
|
|
272
302
|
logsSection.add(this.logsScrollBox);
|
|
273
303
|
// Track focus changes on scroll box
|
|
274
304
|
this.logsScrollBox.on("focused", () => {
|
|
275
305
|
this.debugLog(`[FOCUS] logsScrollBox focused`);
|
|
306
|
+
this.logSelectionDebug("[FOCUS] logsScrollBox focused");
|
|
276
307
|
});
|
|
277
308
|
this.logsScrollBox.on("blurred", () => {
|
|
278
309
|
this.debugLog(`[FOCUS] logsScrollBox blurred`);
|
|
310
|
+
this.logSelectionDebug("[FOCUS] logsScrollBox blurred");
|
|
279
311
|
});
|
|
280
312
|
// Container for log lines inside scroll box - provides valid X positions
|
|
281
313
|
this.logsContainer = new BoxRenderable(this.renderer, {
|
|
282
314
|
id: "logs-content",
|
|
283
315
|
flexDirection: "column",
|
|
284
|
-
width:
|
|
316
|
+
width: this.logContentWidth
|
|
285
317
|
});
|
|
286
318
|
this.logsScrollBox.add(this.logsContainer);
|
|
287
319
|
// Bottom status line - show "agent has access" hint in both modes
|
|
@@ -316,12 +348,27 @@ class D3kTUI {
|
|
|
316
348
|
this.renderer.root.onSizeChange = () => {
|
|
317
349
|
if (!this.renderer || this.isRebuilding)
|
|
318
350
|
return;
|
|
351
|
+
this.logContentWidth = Math.max(20, this.renderer.width - 6);
|
|
319
352
|
const nextCompact = this.renderer.width < 80;
|
|
320
353
|
const nextVeryCompact = this.renderer.width < 60 || this.renderer.height < 15;
|
|
321
354
|
if (nextCompact !== this.isCompact || nextVeryCompact !== this.isVeryCompact) {
|
|
322
355
|
this.rebuildUI();
|
|
323
356
|
return;
|
|
324
357
|
}
|
|
358
|
+
if (this.logsContainer) {
|
|
359
|
+
this.logsContainer.width = this.logContentWidth;
|
|
360
|
+
}
|
|
361
|
+
if (this.logsScrollBox) {
|
|
362
|
+
this.logsScrollBox.content.left = 0;
|
|
363
|
+
this.logsScrollBox.content.top = 0;
|
|
364
|
+
this.logsScrollBox.content.width = this.logContentWidth;
|
|
365
|
+
}
|
|
366
|
+
for (const child of this.logsContainer?.getChildren() || []) {
|
|
367
|
+
if (child instanceof TextRenderable) {
|
|
368
|
+
child.width = this.logContentWidth;
|
|
369
|
+
child.left = 0;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
325
372
|
this.renderer.requestRender();
|
|
326
373
|
};
|
|
327
374
|
}
|
|
@@ -378,11 +425,12 @@ class D3kTUI {
|
|
|
378
425
|
headerBox.add(infoCol);
|
|
379
426
|
const protocol = this.useHttps ? "https" : "http";
|
|
380
427
|
const portStatus = this.portConfirmed ? "" : " ...";
|
|
428
|
+
const displayedAppUrl = this.appUrl || `${protocol}://localhost:${this.appPort}`;
|
|
381
429
|
const appLine = new TextRenderable(this.renderer, {
|
|
382
430
|
id: "app-url",
|
|
383
431
|
content: this.portConfirmed
|
|
384
|
-
? t `${cyan(`🌐 App: ${
|
|
385
|
-
: t `${cyan(`🌐 App: ${
|
|
432
|
+
? t `${cyan(`🌐 App: ${displayedAppUrl}`)}`
|
|
433
|
+
: t `${cyan(`🌐 App: ${displayedAppUrl}`)}${yellow(portStatus)}`
|
|
386
434
|
});
|
|
387
435
|
infoCol.add(appLine);
|
|
388
436
|
// Show logs path with ~/ instead of full home path
|
|
@@ -497,6 +545,7 @@ class D3kTUI {
|
|
|
497
545
|
this.baseLogId = undefined;
|
|
498
546
|
this.refreshLogs();
|
|
499
547
|
}
|
|
548
|
+
this.clearLogSelection();
|
|
500
549
|
this.renderer?.clearSelection();
|
|
501
550
|
return;
|
|
502
551
|
}
|
|
@@ -530,7 +579,7 @@ class D3kTUI {
|
|
|
530
579
|
const focusedId = focusedRenderable?.id || "(none)";
|
|
531
580
|
const selectionContainer = this.renderer?.getSelectionContainer();
|
|
532
581
|
const containerId = selectionContainer?.id || "(none)";
|
|
533
|
-
const message = `hasSelection: ${hasSelection},
|
|
582
|
+
const message = `hasSelection: ${hasSelection}, isDragging: ${selection?.isDragging}, renderables: ${selection?.selectedRenderables?.length ?? 0}, focus: ${focusedId}, container: ${containerId}, text: "${selectedText.slice(0, 30)}"`;
|
|
534
583
|
// Write to file if debug mode is on
|
|
535
584
|
this.debugLog(`[SELECTION_STATE] ${message}`);
|
|
536
585
|
// Always show in UI
|
|
@@ -588,9 +637,11 @@ class D3kTUI {
|
|
|
588
637
|
this.selectionCopyHandler = (selection) => {
|
|
589
638
|
if (!selection) {
|
|
590
639
|
this.lastCopiedSelection = null;
|
|
640
|
+
this.logSelectionDebug("[SELECTION_EVENT] selection cleared");
|
|
591
641
|
return;
|
|
592
642
|
}
|
|
593
|
-
|
|
643
|
+
this.logSelectionDebug(`[SELECTION_EVENT] ${this.describeSelection(selection)}`);
|
|
644
|
+
if (selection.isDragging || !selection.isActive)
|
|
594
645
|
return;
|
|
595
646
|
const text = selection.getSelectedText();
|
|
596
647
|
if (!text || text.trim().length === 0)
|
|
@@ -599,14 +650,12 @@ class D3kTUI {
|
|
|
599
650
|
return;
|
|
600
651
|
this.lastCopiedSelection = text;
|
|
601
652
|
this.copyToClipboard(text);
|
|
653
|
+
this.logSelectionDebug(`[SELECTION_COPY] length=${text.length} text="${text.slice(0, 120)}"`);
|
|
602
654
|
};
|
|
603
655
|
this.renderer.on("selection", this.selectionCopyHandler);
|
|
604
656
|
}
|
|
605
657
|
copySelectionToClipboard() {
|
|
606
|
-
|
|
607
|
-
return;
|
|
608
|
-
const selection = this.renderer.getSelection();
|
|
609
|
-
const text = selection?.getSelectedText();
|
|
658
|
+
const text = this.getSelectedLogText() || this.renderer?.getSelection()?.getSelectedText();
|
|
610
659
|
if (!text || text.trim().length === 0)
|
|
611
660
|
return;
|
|
612
661
|
this.lastCopiedSelection = text;
|
|
@@ -708,16 +757,18 @@ class D3kTUI {
|
|
|
708
757
|
id: `log-${log.id}`,
|
|
709
758
|
content: formatted,
|
|
710
759
|
wrapMode: "none",
|
|
711
|
-
selectable:
|
|
760
|
+
selectable: false,
|
|
712
761
|
selectionBg: RGBA.fromInts(70, 130, 180), // Steel blue highlight
|
|
713
762
|
selectionFg: RGBA.fromInts(255, 255, 255), // White text on selection
|
|
714
|
-
width:
|
|
763
|
+
width: this.logContentWidth,
|
|
764
|
+
left: 0,
|
|
715
765
|
flexShrink: 0,
|
|
716
766
|
onMouseDown: (event) => {
|
|
717
767
|
// Record start position for click detection
|
|
718
768
|
clickStartX = event.x;
|
|
719
769
|
clickStartY = event.y;
|
|
720
770
|
this.debugLog(`[TEXT_DOWN] id=${logLine.id} screen(${event.x},${event.y}) pos(${logLine.x},${logLine.y}) size(${logLine.width}x${logLine.height})`);
|
|
771
|
+
this.logSelectionDebug(`[TEXT_DOWN] id=${logLine.id} screen(${event.x},${event.y}) pos(${logLine.x},${logLine.y}) size(${logLine.width}x${logLine.height})`);
|
|
721
772
|
},
|
|
722
773
|
onMouseUp: (event) => {
|
|
723
774
|
// Check if this was a click (no movement) vs a drag
|
|
@@ -728,6 +779,7 @@ class D3kTUI {
|
|
|
728
779
|
const textLocalX = event.x - (logLine.x || 0);
|
|
729
780
|
const TIMESTAMP_COLUMN_WIDTH = 15; // "[HH:MM:SS.mmm] " is ~14 chars
|
|
730
781
|
if (textLocalX <= TIMESTAMP_COLUMN_WIDTH) {
|
|
782
|
+
this.clearLogSelection();
|
|
731
783
|
// Toggle baseline timestamp on click
|
|
732
784
|
const timestampMs = parseTimestampToMs(log.timestamp);
|
|
733
785
|
if (timestampMs !== null) {
|
|
@@ -750,11 +802,16 @@ class D3kTUI {
|
|
|
750
802
|
clickStartY = -1;
|
|
751
803
|
}
|
|
752
804
|
});
|
|
805
|
+
this.logRenderables.set(log.id, logLine);
|
|
753
806
|
this.logsContainer.add(logLine);
|
|
807
|
+
this.updateLogLineSelectionStyle(log.id);
|
|
754
808
|
}
|
|
755
809
|
// Force render to update hit grid after adding new logs
|
|
756
810
|
// This prevents stale hit grid when new logs trigger scroll/layout changes
|
|
757
811
|
this.renderer.requestRender();
|
|
812
|
+
setTimeout(() => {
|
|
813
|
+
this.logLogPaneMetrics(`after-add-${newLogs.length}`);
|
|
814
|
+
}, 0);
|
|
758
815
|
}
|
|
759
816
|
findLogLineAt(x, y) {
|
|
760
817
|
if (!this.logsContainer)
|
|
@@ -769,6 +826,36 @@ class D3kTUI {
|
|
|
769
826
|
}
|
|
770
827
|
return null;
|
|
771
828
|
}
|
|
829
|
+
findLogLineAtRow(y) {
|
|
830
|
+
if (!this.logsContainer)
|
|
831
|
+
return null;
|
|
832
|
+
for (const child of this.logsContainer.getChildren()) {
|
|
833
|
+
if (!(child instanceof TextRenderable))
|
|
834
|
+
continue;
|
|
835
|
+
const withinY = y >= child.y && y < child.y + Math.max(child.height, 1);
|
|
836
|
+
if (withinY)
|
|
837
|
+
return child;
|
|
838
|
+
}
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
841
|
+
getSelectionLogLine(event) {
|
|
842
|
+
const target = event.target;
|
|
843
|
+
if (target?.id?.startsWith("log-")) {
|
|
844
|
+
const child = this.logsContainer?.getChildren().find((entry) => entry.id === target.id);
|
|
845
|
+
if (child instanceof TextRenderable) {
|
|
846
|
+
this.logSelectionDebug(`[HIT] direct target=${target.id} pos(${child.x},${child.y}) size(${child.width}x${child.height})`);
|
|
847
|
+
return child;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
const fallback = this.findLogLineAt(event.x, event.y);
|
|
851
|
+
if (fallback) {
|
|
852
|
+
this.logSelectionDebug(`[HIT] fallback screen(${event.x},${event.y}) -> ${fallback.id} pos(${fallback.x},${fallback.y}) size(${fallback.width}x${fallback.height})`);
|
|
853
|
+
return fallback;
|
|
854
|
+
}
|
|
855
|
+
const rowFallback = this.findLogLineAtRow(event.y);
|
|
856
|
+
this.logSelectionDebug(`[HIT] row-fallback screen(${event.x},${event.y}) -> ${rowFallback?.id || "(none)"} pos(${rowFallback?.x ?? "?"},${rowFallback?.y ?? "?"}) size(${rowFallback?.width ?? "?"}x${rowFallback?.height ?? "?"})`);
|
|
857
|
+
return rowFallback;
|
|
858
|
+
}
|
|
772
859
|
refreshLogs() {
|
|
773
860
|
if (!this.renderer || !this.logsContainer)
|
|
774
861
|
return;
|
|
@@ -776,9 +863,107 @@ class D3kTUI {
|
|
|
776
863
|
for (const child of this.logsContainer.getChildren()) {
|
|
777
864
|
child.destroy();
|
|
778
865
|
}
|
|
866
|
+
this.logRenderables.clear();
|
|
779
867
|
// Re-add filtered logs
|
|
780
868
|
const filteredLogs = this.logs.filter((log) => log.id > this.clearFromLogId);
|
|
781
869
|
this.addLogLines(filteredLogs);
|
|
870
|
+
this.normalizeLogSelection();
|
|
871
|
+
}
|
|
872
|
+
beginLogSelection(logRenderableId, x, y) {
|
|
873
|
+
const logId = this.parseLogRenderableId(logRenderableId);
|
|
874
|
+
if (logId === null)
|
|
875
|
+
return;
|
|
876
|
+
this.logSelection = {
|
|
877
|
+
anchorLogId: logId,
|
|
878
|
+
focusLogId: logId,
|
|
879
|
+
startX: x,
|
|
880
|
+
startY: y,
|
|
881
|
+
hasDragged: false
|
|
882
|
+
};
|
|
883
|
+
this.updateLogSelectionStyles();
|
|
884
|
+
}
|
|
885
|
+
updateLogSelection(logRenderableId, x, y) {
|
|
886
|
+
if (!this.logSelection)
|
|
887
|
+
return;
|
|
888
|
+
const logId = logRenderableId ? this.parseLogRenderableId(logRenderableId) : null;
|
|
889
|
+
if (logId !== null) {
|
|
890
|
+
this.logSelection.focusLogId = logId;
|
|
891
|
+
}
|
|
892
|
+
if (x !== this.logSelection.startX || y !== this.logSelection.startY) {
|
|
893
|
+
this.logSelection.hasDragged = true;
|
|
894
|
+
}
|
|
895
|
+
this.updateLogSelectionStyles();
|
|
896
|
+
}
|
|
897
|
+
finishLogSelection(logRenderableId, x, y) {
|
|
898
|
+
if (!this.logSelection)
|
|
899
|
+
return;
|
|
900
|
+
this.updateLogSelection(logRenderableId, x, y);
|
|
901
|
+
if (!this.logSelection?.hasDragged) {
|
|
902
|
+
this.clearLogSelection();
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
const text = this.getSelectedLogText();
|
|
906
|
+
if (!text || text.trim().length === 0 || text === this.lastCopiedSelection)
|
|
907
|
+
return;
|
|
908
|
+
this.lastCopiedSelection = text;
|
|
909
|
+
this.copyToClipboard(text);
|
|
910
|
+
this.logSelectionDebug(`[SELECTION_COPY] length=${text.length} text="${text.slice(0, 120)}"`);
|
|
911
|
+
}
|
|
912
|
+
clearLogSelection() {
|
|
913
|
+
if (!this.logSelection)
|
|
914
|
+
return;
|
|
915
|
+
this.logSelection = null;
|
|
916
|
+
this.updateLogSelectionStyles();
|
|
917
|
+
}
|
|
918
|
+
normalizeLogSelection() {
|
|
919
|
+
if (!this.logSelection)
|
|
920
|
+
return;
|
|
921
|
+
const selectedLogIds = this.getSelectedLogIds();
|
|
922
|
+
if (selectedLogIds.length === 0) {
|
|
923
|
+
this.logSelection = null;
|
|
924
|
+
this.updateLogSelectionStyles();
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
getSelectedLogIds() {
|
|
928
|
+
if (!this.logSelection)
|
|
929
|
+
return [];
|
|
930
|
+
const visibleLogIds = [...this.logRenderables.keys()].sort((a, b) => a - b);
|
|
931
|
+
const anchorIndex = visibleLogIds.indexOf(this.logSelection.anchorLogId);
|
|
932
|
+
const focusIndex = visibleLogIds.indexOf(this.logSelection.focusLogId);
|
|
933
|
+
if (anchorIndex === -1 || focusIndex === -1)
|
|
934
|
+
return [];
|
|
935
|
+
const startIndex = Math.min(anchorIndex, focusIndex);
|
|
936
|
+
const endIndex = Math.max(anchorIndex, focusIndex);
|
|
937
|
+
return visibleLogIds.slice(startIndex, endIndex + 1);
|
|
938
|
+
}
|
|
939
|
+
getSelectedLogText() {
|
|
940
|
+
const selectedIds = new Set(this.getSelectedLogIds());
|
|
941
|
+
if (selectedIds.size === 0)
|
|
942
|
+
return "";
|
|
943
|
+
return this.logs
|
|
944
|
+
.filter((log) => log.id > this.clearFromLogId && selectedIds.has(log.id))
|
|
945
|
+
.map((log) => log.content)
|
|
946
|
+
.join("\n");
|
|
947
|
+
}
|
|
948
|
+
updateLogSelectionStyles() {
|
|
949
|
+
for (const logId of this.logRenderables.keys()) {
|
|
950
|
+
this.updateLogLineSelectionStyle(logId);
|
|
951
|
+
}
|
|
952
|
+
this.renderer?.requestRender();
|
|
953
|
+
}
|
|
954
|
+
updateLogLineSelectionStyle(logId) {
|
|
955
|
+
const renderable = this.logRenderables.get(logId);
|
|
956
|
+
if (!renderable)
|
|
957
|
+
return;
|
|
958
|
+
const selectedIds = new Set(this.getSelectedLogIds());
|
|
959
|
+
renderable.bg = selectedIds.has(logId) ? this.logSelectionBackground : undefined;
|
|
960
|
+
}
|
|
961
|
+
parseLogRenderableId(logRenderableId) {
|
|
962
|
+
const match = logRenderableId.match(/^log-(\d+)$/);
|
|
963
|
+
if (!match)
|
|
964
|
+
return null;
|
|
965
|
+
const logId = Number.parseInt(match[1], 10);
|
|
966
|
+
return Number.isNaN(logId) ? null : logId;
|
|
782
967
|
}
|
|
783
968
|
updateStatusDisplay() {
|
|
784
969
|
if (!this.statusText || !this.renderer)
|
|
@@ -809,6 +994,10 @@ class D3kTUI {
|
|
|
809
994
|
this.portConfirmed = true;
|
|
810
995
|
this.rebuildUI();
|
|
811
996
|
}
|
|
997
|
+
setAppUrl(url) {
|
|
998
|
+
this.appUrl = url;
|
|
999
|
+
this.rebuildUI();
|
|
1000
|
+
}
|
|
812
1001
|
setUpdateInfo(info) {
|
|
813
1002
|
this.updateInfo = info;
|
|
814
1003
|
this.updateStatusDisplay();
|
|
@@ -835,6 +1024,58 @@ class D3kTUI {
|
|
|
835
1024
|
// Ignore write errors
|
|
836
1025
|
}
|
|
837
1026
|
}
|
|
1027
|
+
initializeSelectionDebugLog() {
|
|
1028
|
+
if (!this.selectionDebugEnabled)
|
|
1029
|
+
return;
|
|
1030
|
+
try {
|
|
1031
|
+
mkdirSync(join(process.env.HOME || tmpdir(), ".d3k"), { recursive: true });
|
|
1032
|
+
writeFileSync(this.selectionDebugLogFile, [
|
|
1033
|
+
`Selection debug started at ${new Date().toISOString()}`,
|
|
1034
|
+
`project=${this.options.projectName || "(unknown)"}`,
|
|
1035
|
+
`logFile=${this.options.logFile}`,
|
|
1036
|
+
`appPort=${this.options.appPort}`,
|
|
1037
|
+
""
|
|
1038
|
+
].join("\n"));
|
|
1039
|
+
}
|
|
1040
|
+
catch {
|
|
1041
|
+
// Ignore write errors
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
logSelectionDebug(message) {
|
|
1045
|
+
if (!this.selectionDebugEnabled)
|
|
1046
|
+
return;
|
|
1047
|
+
const timestamp = new Date().toISOString();
|
|
1048
|
+
try {
|
|
1049
|
+
appendFileSync(this.selectionDebugLogFile, `[${timestamp}] ${message}\n`);
|
|
1050
|
+
}
|
|
1051
|
+
catch {
|
|
1052
|
+
// Ignore write errors
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
describeSelection(selection = this.renderer?.getSelection() || null) {
|
|
1056
|
+
const logSelectionIds = this.getSelectedLogIds();
|
|
1057
|
+
const logSelectionText = this.getSelectedLogText();
|
|
1058
|
+
const logSelectionDescription = logSelectionIds.length === 0
|
|
1059
|
+
? "logSelection=(none)"
|
|
1060
|
+
: `logSelection={dragging:${Boolean(this.logSelection?.hasDragged)},lines:${logSelectionIds.length},textLen:${logSelectionText.length}}`;
|
|
1061
|
+
if (!selection) {
|
|
1062
|
+
return logSelectionDescription;
|
|
1063
|
+
}
|
|
1064
|
+
const text = selection.getSelectedText();
|
|
1065
|
+
return `${logSelectionDescription} nativeSelection={active:${selection.isActive},dragging:${selection.isDragging},renderables:${selection.selectedRenderables.length},textLen:${text.length}}`;
|
|
1066
|
+
}
|
|
1067
|
+
logLogPaneMetrics(reason) {
|
|
1068
|
+
if (!this.selectionDebugEnabled)
|
|
1069
|
+
return;
|
|
1070
|
+
const children = this.logsContainer?.getChildren() || [];
|
|
1071
|
+
const logLines = children.filter((child) => child instanceof TextRenderable);
|
|
1072
|
+
const sample = logLines
|
|
1073
|
+
.slice(0, 3)
|
|
1074
|
+
.map((line) => `${line.id}@(${line.x},${line.y}) ${line.width}x${line.height}`)
|
|
1075
|
+
.join(", ");
|
|
1076
|
+
const last = logLines.at(-1);
|
|
1077
|
+
this.logSelectionDebug(`[LAYOUT:${reason}] scroll=${this.logsScrollBox?.x ?? "?"},${this.logsScrollBox?.y ?? "?"} ${this.logsScrollBox?.width ?? "?"}x${this.logsScrollBox?.height ?? "?"} content=${this.logsContainer?.x ?? "?"},${this.logsContainer?.y ?? "?"} ${this.logsContainer?.width ?? "?"}x${this.logsContainer?.height ?? "?"} lines=${logLines.length} sample=[${sample}] last=${last ? `${last.id}@(${last.x},${last.y}) ${last.width}x${last.height}` : "(none)"}`);
|
|
1078
|
+
}
|
|
838
1079
|
shutdown() {
|
|
839
1080
|
unwatchFile(this.options.logFile);
|
|
840
1081
|
if (this.renderer) {
|