tlc-claude-code 0.9.8 → 1.0.0
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/package.json +1 -1
- package/server/dashboard/index.html +176 -6
- package/server/index.js +19 -0
- package/server/lib/plan-parser.js +23 -2
package/package.json
CHANGED
|
@@ -424,6 +424,65 @@
|
|
|
424
424
|
opacity: 0.5;
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
+
/* App Preview */
|
|
428
|
+
.app-preview-container {
|
|
429
|
+
display: flex;
|
|
430
|
+
flex-direction: column;
|
|
431
|
+
height: 100%;
|
|
432
|
+
}
|
|
433
|
+
.app-preview-toolbar {
|
|
434
|
+
display: flex;
|
|
435
|
+
gap: 10px;
|
|
436
|
+
padding: 10px 0;
|
|
437
|
+
align-items: center;
|
|
438
|
+
border-bottom: 1px solid #30363d;
|
|
439
|
+
margin-bottom: 10px;
|
|
440
|
+
}
|
|
441
|
+
.app-preview-toolbar .app-url {
|
|
442
|
+
margin-left: auto;
|
|
443
|
+
font-family: monospace;
|
|
444
|
+
font-size: 12px;
|
|
445
|
+
color: #8b949e;
|
|
446
|
+
}
|
|
447
|
+
.app-iframe {
|
|
448
|
+
flex: 1;
|
|
449
|
+
border: 1px solid #30363d;
|
|
450
|
+
border-radius: 8px;
|
|
451
|
+
background: white;
|
|
452
|
+
width: 100%;
|
|
453
|
+
min-height: 500px;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/* Progress Bar */
|
|
457
|
+
.progress-container {
|
|
458
|
+
margin-bottom: 20px;
|
|
459
|
+
}
|
|
460
|
+
.progress-header {
|
|
461
|
+
display: flex;
|
|
462
|
+
justify-content: space-between;
|
|
463
|
+
margin-bottom: 8px;
|
|
464
|
+
font-size: 13px;
|
|
465
|
+
}
|
|
466
|
+
.progress-header .phase-name {
|
|
467
|
+
color: #58a6ff;
|
|
468
|
+
font-weight: 500;
|
|
469
|
+
}
|
|
470
|
+
.progress-header .progress-pct {
|
|
471
|
+
color: #3fb950;
|
|
472
|
+
}
|
|
473
|
+
.progress-bar {
|
|
474
|
+
height: 8px;
|
|
475
|
+
background: #21262d;
|
|
476
|
+
border-radius: 4px;
|
|
477
|
+
overflow: hidden;
|
|
478
|
+
}
|
|
479
|
+
.progress-fill {
|
|
480
|
+
height: 100%;
|
|
481
|
+
background: linear-gradient(90deg, #238636, #3fb950);
|
|
482
|
+
border-radius: 4px;
|
|
483
|
+
transition: width 0.3s ease;
|
|
484
|
+
}
|
|
485
|
+
|
|
427
486
|
/* Buttons */
|
|
428
487
|
.btn {
|
|
429
488
|
padding: 8px 16px;
|
|
@@ -462,7 +521,8 @@
|
|
|
462
521
|
<div class="container">
|
|
463
522
|
<div class="main-content">
|
|
464
523
|
<div class="tabs">
|
|
465
|
-
<button class="tab active" data-tab="
|
|
524
|
+
<button class="tab active" data-tab="app">🖥️ App</button>
|
|
525
|
+
<button class="tab" data-tab="plan">Plan</button>
|
|
466
526
|
<button class="tab" data-tab="tests">Tests</button>
|
|
467
527
|
<button class="tab" data-tab="bugs">Bugs <span class="badge" id="bugs-badge" style="display:none">0</span></button>
|
|
468
528
|
<button class="tab" data-tab="logs">Logs</button>
|
|
@@ -470,8 +530,21 @@
|
|
|
470
530
|
</div>
|
|
471
531
|
|
|
472
532
|
<div class="tab-content">
|
|
533
|
+
<!-- App Preview Panel -->
|
|
534
|
+
<div class="tab-panel active" id="panel-app">
|
|
535
|
+
<div class="app-preview-container">
|
|
536
|
+
<div class="app-preview-toolbar">
|
|
537
|
+
<button class="btn btn-secondary" onclick="reloadApp()">🔄 Reload</button>
|
|
538
|
+
<button class="btn btn-secondary" onclick="openAppNewTab()">↗️ Open in New Tab</button>
|
|
539
|
+
<button class="btn btn-primary" onclick="captureScreenshot()">📸 Screenshot for Bug</button>
|
|
540
|
+
<span class="app-url" id="app-url">Loading...</span>
|
|
541
|
+
</div>
|
|
542
|
+
<iframe id="app-iframe" src="/app" class="app-iframe"></iframe>
|
|
543
|
+
</div>
|
|
544
|
+
</div>
|
|
545
|
+
|
|
473
546
|
<!-- Plan Panel -->
|
|
474
|
-
<div class="tab-panel
|
|
547
|
+
<div class="tab-panel" id="panel-plan">
|
|
475
548
|
<div class="plan-content" id="plan-content">
|
|
476
549
|
<div class="empty-state">
|
|
477
550
|
<div class="icon">📋</div>
|
|
@@ -549,6 +622,19 @@
|
|
|
549
622
|
</div>
|
|
550
623
|
|
|
551
624
|
<div class="sidebar">
|
|
625
|
+
<div class="sidebar-section">
|
|
626
|
+
<h3>Progress</h3>
|
|
627
|
+
<div class="progress-container">
|
|
628
|
+
<div class="progress-header">
|
|
629
|
+
<span class="phase-name" id="progress-phase">Phase 1</span>
|
|
630
|
+
<span class="progress-pct" id="progress-pct">0%</span>
|
|
631
|
+
</div>
|
|
632
|
+
<div class="progress-bar">
|
|
633
|
+
<div class="progress-fill" id="progress-fill" style="width: 0%"></div>
|
|
634
|
+
</div>
|
|
635
|
+
</div>
|
|
636
|
+
</div>
|
|
637
|
+
|
|
552
638
|
<div class="sidebar-section">
|
|
553
639
|
<h3>Stats</h3>
|
|
554
640
|
<div class="stats-grid">
|
|
@@ -589,15 +675,15 @@
|
|
|
589
675
|
<div class="sidebar-section">
|
|
590
676
|
<h3>Links</h3>
|
|
591
677
|
<div class="links">
|
|
592
|
-
<a class="link" href="
|
|
593
|
-
<span>App</span>
|
|
594
|
-
<span class="url">:
|
|
678
|
+
<a class="link" id="link-app" href="#" target="_blank">
|
|
679
|
+
<span>App (Direct)</span>
|
|
680
|
+
<span class="url" id="link-app-port">:3000</span>
|
|
595
681
|
</a>
|
|
596
682
|
<a class="link" href="http://localhost:8080" target="_blank">
|
|
597
683
|
<span>Database Admin</span>
|
|
598
684
|
<span class="url">:8080</span>
|
|
599
685
|
</a>
|
|
600
|
-
<a class="link" href="
|
|
686
|
+
<a class="link" href="#" onclick="event.preventDefault(); alert('Connect with: postgres://postgres:postgres@localhost:5433/app')">
|
|
601
687
|
<span>PostgreSQL</span>
|
|
602
688
|
<span class="url">:5433</span>
|
|
603
689
|
</a>
|
|
@@ -611,6 +697,8 @@
|
|
|
611
697
|
const logs = { app: [], test: [], git: [] };
|
|
612
698
|
let currentLogType = 'app';
|
|
613
699
|
let reconnectAttempts = 0;
|
|
700
|
+
let appPort = 3000;
|
|
701
|
+
let screenshotData = null;
|
|
614
702
|
|
|
615
703
|
// Tab switching
|
|
616
704
|
document.querySelectorAll('.tab').forEach(tab => {
|
|
@@ -651,8 +739,17 @@
|
|
|
651
739
|
switch(msg.type) {
|
|
652
740
|
case 'init':
|
|
653
741
|
Object.assign(logs, msg.data.logs || {});
|
|
742
|
+
if (msg.data.appPort) {
|
|
743
|
+
updateAppPort(msg.data.appPort);
|
|
744
|
+
}
|
|
654
745
|
renderLogs();
|
|
655
746
|
break;
|
|
747
|
+
case 'app-start':
|
|
748
|
+
if (msg.data.port) {
|
|
749
|
+
updateAppPort(msg.data.port);
|
|
750
|
+
reloadApp();
|
|
751
|
+
}
|
|
752
|
+
break;
|
|
656
753
|
case 'app-log':
|
|
657
754
|
addLog('app', msg.data.data, msg.data.level || detectLogLevel(msg.data.data));
|
|
658
755
|
break;
|
|
@@ -886,12 +983,85 @@
|
|
|
886
983
|
refreshBugs();
|
|
887
984
|
refreshChangelog();
|
|
888
985
|
refreshStats();
|
|
986
|
+
refreshProgress();
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// App Preview functions
|
|
990
|
+
function reloadApp() {
|
|
991
|
+
const iframe = document.getElementById('app-iframe');
|
|
992
|
+
iframe.src = iframe.src;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
function openAppNewTab() {
|
|
996
|
+
window.open(`http://localhost:${appPort}`, '_blank');
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
async function captureScreenshot() {
|
|
1000
|
+
try {
|
|
1001
|
+
// Use html2canvas-like approach via the iframe
|
|
1002
|
+
const iframe = document.getElementById('app-iframe');
|
|
1003
|
+
|
|
1004
|
+
// For cross-origin iframes, we can't capture directly
|
|
1005
|
+
// Instead, prompt user to use browser screenshot or provide manual upload
|
|
1006
|
+
const desc = prompt('Describe the bug (screenshot will be from current app view):');
|
|
1007
|
+
if (!desc) return;
|
|
1008
|
+
|
|
1009
|
+
// Create bug with current URL
|
|
1010
|
+
const res = await fetch('/api/bug', {
|
|
1011
|
+
method: 'POST',
|
|
1012
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1013
|
+
body: JSON.stringify({
|
|
1014
|
+
description: desc,
|
|
1015
|
+
severity: 'medium',
|
|
1016
|
+
url: iframe.src
|
|
1017
|
+
})
|
|
1018
|
+
});
|
|
1019
|
+
const data = await res.json();
|
|
1020
|
+
if (data.success) {
|
|
1021
|
+
alert(`Bug ${data.bugId} created!\n\nTip: Use browser's screenshot tool (Cmd+Shift+4 on Mac, Win+Shift+S on Windows) to capture and attach manually.`);
|
|
1022
|
+
refreshBugs();
|
|
1023
|
+
}
|
|
1024
|
+
} catch (e) {
|
|
1025
|
+
alert('Failed to create bug: ' + e.message);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
async function refreshProgress() {
|
|
1030
|
+
try {
|
|
1031
|
+
const res = await fetch('/api/progress');
|
|
1032
|
+
const data = await res.json();
|
|
1033
|
+
|
|
1034
|
+
const phaseName = `Phase ${data.phase || '?'}: ${data.phaseName || 'Unknown'}`;
|
|
1035
|
+
const pct = data.progress || 0;
|
|
1036
|
+
|
|
1037
|
+
document.getElementById('progress-phase').textContent = phaseName;
|
|
1038
|
+
document.getElementById('progress-pct').textContent = `${pct}%`;
|
|
1039
|
+
document.getElementById('progress-fill').style.width = `${pct}%`;
|
|
1040
|
+
} catch (e) {
|
|
1041
|
+
// Fallback to stats-based progress
|
|
1042
|
+
try {
|
|
1043
|
+
const res = await fetch('/api/status');
|
|
1044
|
+
const data = await res.json();
|
|
1045
|
+
const phaseName = `Phase ${data.phase || '?'}`;
|
|
1046
|
+
document.getElementById('progress-phase').textContent = phaseName;
|
|
1047
|
+
} catch (e2) {
|
|
1048
|
+
console.error('Failed to load progress:', e2);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
function updateAppPort(port) {
|
|
1054
|
+
appPort = port;
|
|
1055
|
+
document.getElementById('app-url').textContent = `localhost:${port}`;
|
|
1056
|
+
document.getElementById('link-app').href = `http://localhost:${port}`;
|
|
1057
|
+
document.getElementById('link-app-port').textContent = `:${port}`;
|
|
889
1058
|
}
|
|
890
1059
|
|
|
891
1060
|
// Initialize
|
|
892
1061
|
connect();
|
|
893
1062
|
refreshAll();
|
|
894
1063
|
setInterval(refreshStats, 30000);
|
|
1064
|
+
setInterval(refreshProgress, 30000);
|
|
895
1065
|
</script>
|
|
896
1066
|
</body>
|
|
897
1067
|
</html>
|
package/server/index.js
CHANGED
|
@@ -218,6 +218,25 @@ app.get('/api/status', (req, res) => {
|
|
|
218
218
|
});
|
|
219
219
|
});
|
|
220
220
|
|
|
221
|
+
app.get('/api/progress', (req, res) => {
|
|
222
|
+
const plan = parsePlan(PROJECT_DIR);
|
|
223
|
+
|
|
224
|
+
// Calculate progress from tasks
|
|
225
|
+
let progress = 0;
|
|
226
|
+
if (plan.tasks && plan.tasks.length > 0) {
|
|
227
|
+
const completed = plan.tasks.filter(t => t.status === 'done' || t.status === 'complete').length;
|
|
228
|
+
progress = Math.round((completed / plan.tasks.length) * 100);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
res.json({
|
|
232
|
+
phase: plan.currentPhase,
|
|
233
|
+
phaseName: plan.currentPhaseName,
|
|
234
|
+
totalTasks: plan.tasks?.length || 0,
|
|
235
|
+
completedTasks: plan.tasks?.filter(t => t.status === 'done' || t.status === 'complete').length || 0,
|
|
236
|
+
progress
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
221
240
|
app.get('/api/logs/:type', (req, res) => {
|
|
222
241
|
const type = req.params.type;
|
|
223
242
|
if (logs[type]) {
|
|
@@ -108,11 +108,32 @@ function parseBugs(projectDir) {
|
|
|
108
108
|
|
|
109
109
|
let match;
|
|
110
110
|
while ((match = bugRegex.exec(content)) !== null) {
|
|
111
|
-
const [, id, title, status] = match;
|
|
111
|
+
const [fullMatch, id, title, status] = match;
|
|
112
|
+
|
|
113
|
+
// Try to extract date and description from following lines
|
|
114
|
+
const afterBug = content.slice(match.index + fullMatch.length);
|
|
115
|
+
const nextBugIndex = afterBug.search(/###\s+BUG-/);
|
|
116
|
+
const bugSection = nextBugIndex > 0 ? afterBug.slice(0, nextBugIndex) : afterBug;
|
|
117
|
+
|
|
118
|
+
// Extract date
|
|
119
|
+
const dateMatch = bugSection.match(/\*\*Reported:\*\*\s*(\S+)/);
|
|
120
|
+
const date = dateMatch ? dateMatch[1] : null;
|
|
121
|
+
|
|
122
|
+
// Extract severity
|
|
123
|
+
const severityMatch = bugSection.match(/\*\*Severity:\*\*\s*(\w+)/);
|
|
124
|
+
const severity = severityMatch ? severityMatch[1].toLowerCase() : 'medium';
|
|
125
|
+
|
|
126
|
+
// Extract description (text after metadata, before ---)
|
|
127
|
+
const lines = bugSection.split('\n').filter(l => l.trim() && !l.startsWith('-') && !l.startsWith('*'));
|
|
128
|
+
const description = lines.slice(0, 2).join(' ').trim().slice(0, 200);
|
|
129
|
+
|
|
112
130
|
bugs.push({
|
|
113
131
|
id,
|
|
114
132
|
title: title.trim(),
|
|
115
|
-
status: status.toLowerCase()
|
|
133
|
+
status: status.toLowerCase(),
|
|
134
|
+
date,
|
|
135
|
+
severity,
|
|
136
|
+
description: description || title.trim()
|
|
116
137
|
});
|
|
117
138
|
}
|
|
118
139
|
|