claude-task-viewer 1.3.0 → 1.5.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/README.md +4 -1
- package/package.json +1 -1
- package/public/index.html +154 -106
package/README.md
CHANGED
|
@@ -26,8 +26,11 @@ Tasks can block other tasks. The viewer shows these relationships clearly — bl
|
|
|
26
26
|
### Notes
|
|
27
27
|
Add context to any task. Your notes are appended to the task description, so Claude sees them when it reads the task. Use this to clarify requirements, add constraints, or redirect work — all without interrupting Claude's flow.
|
|
28
28
|
|
|
29
|
+
### Project Filtering
|
|
30
|
+
Filter tasks by project using the dropdown. Working on multiple codebases? See only what's relevant. Combine with the session filter to show just active sessions for a specific project.
|
|
31
|
+
|
|
29
32
|
### Session Management
|
|
30
|
-
Browse all your Claude Code sessions with progress indicators. Filter to active sessions only. Each session shows completion percentage
|
|
33
|
+
Browse all your Claude Code sessions with progress indicators. Filter to active sessions only, or show everything. Each session shows completion percentage.
|
|
31
34
|
|
|
32
35
|
## Installation
|
|
33
36
|
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -139,40 +139,6 @@
|
|
|
139
139
|
background: #ef4444;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
/* Nav item */
|
|
143
|
-
.nav-item {
|
|
144
|
-
display: flex;
|
|
145
|
-
align-items: center;
|
|
146
|
-
gap: 10px;
|
|
147
|
-
width: 100%;
|
|
148
|
-
padding: 10px 12px;
|
|
149
|
-
background: transparent;
|
|
150
|
-
border: none;
|
|
151
|
-
border-radius: 6px;
|
|
152
|
-
color: var(--text-secondary);
|
|
153
|
-
font-family: var(--mono);
|
|
154
|
-
font-size: 12px;
|
|
155
|
-
cursor: pointer;
|
|
156
|
-
transition: all 0.15s ease;
|
|
157
|
-
text-align: left;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
.nav-item:hover {
|
|
161
|
-
background: var(--bg-hover);
|
|
162
|
-
color: var(--text-primary);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
.nav-item.active {
|
|
166
|
-
background: var(--bg-elevated);
|
|
167
|
-
color: var(--text-primary);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
.nav-item svg {
|
|
171
|
-
width: 16px;
|
|
172
|
-
height: 16px;
|
|
173
|
-
opacity: 0.6;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
142
|
/* Sidebar sections */
|
|
177
143
|
.sidebar-section {
|
|
178
144
|
display: flex;
|
|
@@ -198,26 +164,37 @@
|
|
|
198
164
|
color: var(--text-muted);
|
|
199
165
|
}
|
|
200
166
|
|
|
201
|
-
.filter-
|
|
167
|
+
.filter-row {
|
|
202
168
|
display: flex;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
font-weight: 400;
|
|
206
|
-
color: var(--text-tertiary);
|
|
207
|
-
cursor: pointer;
|
|
169
|
+
gap: 8px;
|
|
170
|
+
padding: 0 12px 12px;
|
|
208
171
|
}
|
|
209
172
|
|
|
210
|
-
.filter-
|
|
173
|
+
.filter-dropdown {
|
|
174
|
+
flex: 1;
|
|
211
175
|
appearance: none;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
border:
|
|
215
|
-
|
|
176
|
+
background: var(--bg-elevated);
|
|
177
|
+
border: 1px solid var(--border);
|
|
178
|
+
border-radius: 4px;
|
|
179
|
+
padding: 6px 24px 6px 10px;
|
|
180
|
+
font-family: var(--mono);
|
|
181
|
+
font-size: 11px;
|
|
182
|
+
color: var(--text-secondary);
|
|
216
183
|
cursor: pointer;
|
|
184
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%235a5c60' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
|
|
185
|
+
background-repeat: no-repeat;
|
|
186
|
+
background-position: right 8px center;
|
|
187
|
+
text-overflow: ellipsis;
|
|
188
|
+
min-width: 0;
|
|
217
189
|
}
|
|
218
190
|
|
|
219
|
-
.filter-
|
|
220
|
-
background: var(--
|
|
191
|
+
.filter-dropdown option {
|
|
192
|
+
background: var(--bg-surface);
|
|
193
|
+
color: var(--text-primary);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.filter-dropdown:focus {
|
|
197
|
+
outline: none;
|
|
221
198
|
border-color: var(--accent);
|
|
222
199
|
}
|
|
223
200
|
|
|
@@ -292,17 +269,6 @@
|
|
|
292
269
|
padding: 0 12px 12px;
|
|
293
270
|
}
|
|
294
271
|
|
|
295
|
-
.sessions-list .nav-item {
|
|
296
|
-
margin-bottom: 8px;
|
|
297
|
-
border-bottom: 1px solid var(--border);
|
|
298
|
-
padding-bottom: 12px;
|
|
299
|
-
border-radius: 0;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
.sessions-list .nav-item:hover {
|
|
303
|
-
background: transparent;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
272
|
.session-item {
|
|
307
273
|
display: block;
|
|
308
274
|
width: 100%;
|
|
@@ -355,10 +321,10 @@
|
|
|
355
321
|
50% { opacity: 0.6; transform: scale(0.9); }
|
|
356
322
|
}
|
|
357
323
|
|
|
358
|
-
.session-
|
|
324
|
+
.session-secondary {
|
|
359
325
|
font-size: 11px;
|
|
360
|
-
color: var(--text-
|
|
361
|
-
margin-top:
|
|
326
|
+
color: var(--text-muted);
|
|
327
|
+
margin-top: 2px;
|
|
362
328
|
white-space: nowrap;
|
|
363
329
|
overflow: hidden;
|
|
364
330
|
text-overflow: ellipsis;
|
|
@@ -503,6 +469,26 @@
|
|
|
503
469
|
color: var(--accent);
|
|
504
470
|
}
|
|
505
471
|
|
|
472
|
+
.search-input {
|
|
473
|
+
background: var(--bg-elevated);
|
|
474
|
+
border: 1px solid var(--border);
|
|
475
|
+
border-radius: 6px;
|
|
476
|
+
padding: 8px 12px;
|
|
477
|
+
font-family: var(--mono);
|
|
478
|
+
font-size: 12px;
|
|
479
|
+
color: var(--text-primary);
|
|
480
|
+
width: 200px;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.search-input:focus {
|
|
484
|
+
outline: none;
|
|
485
|
+
border-color: var(--accent);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
.search-input::placeholder {
|
|
489
|
+
color: var(--text-muted);
|
|
490
|
+
}
|
|
491
|
+
|
|
506
492
|
.icon-btn {
|
|
507
493
|
width: 32px;
|
|
508
494
|
height: 32px;
|
|
@@ -1013,23 +999,21 @@
|
|
|
1013
999
|
</div>
|
|
1014
1000
|
</div>
|
|
1015
1001
|
|
|
1016
|
-
<!--
|
|
1002
|
+
<!-- Tasks -->
|
|
1017
1003
|
<div class="sidebar-section flex-1">
|
|
1018
1004
|
<div class="section-header">
|
|
1019
|
-
<span>
|
|
1020
|
-
<label class="filter-toggle">
|
|
1021
|
-
<input type="checkbox" id="hide-inactive" onchange="toggleHideInactive()">
|
|
1022
|
-
<span>Active only</span>
|
|
1023
|
-
</label>
|
|
1005
|
+
<span>Tasks</span>
|
|
1024
1006
|
</div>
|
|
1025
|
-
<div
|
|
1026
|
-
<
|
|
1027
|
-
<
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
<
|
|
1031
|
-
|
|
1007
|
+
<div class="filter-row">
|
|
1008
|
+
<select id="project-filter" class="filter-dropdown" onchange="filterByProject(this.value)">
|
|
1009
|
+
<option value="">All Projects</option>
|
|
1010
|
+
</select>
|
|
1011
|
+
<select id="session-filter" class="filter-dropdown" onchange="filterBySessions(this.value)">
|
|
1012
|
+
<option value="all">All Sessions</option>
|
|
1013
|
+
<option value="active">Active Only</option>
|
|
1014
|
+
</select>
|
|
1032
1015
|
</div>
|
|
1016
|
+
<div id="sessions-list" class="sessions-list"></div>
|
|
1033
1017
|
</div>
|
|
1034
1018
|
|
|
1035
1019
|
<footer class="sidebar-footer">
|
|
@@ -1055,6 +1039,8 @@
|
|
|
1055
1039
|
<p id="session-meta" class="view-meta"></p>
|
|
1056
1040
|
</div>
|
|
1057
1041
|
<div class="view-actions">
|
|
1042
|
+
<input type="text" id="task-search" class="search-input"
|
|
1043
|
+
placeholder="Search tasks..." oninput="searchTasks(this.value)">
|
|
1058
1044
|
<div class="view-progress">
|
|
1059
1045
|
<div class="progress-bar">
|
|
1060
1046
|
<div id="progress-bar" class="progress-fill" style="width: 0%"></div>
|
|
@@ -1124,7 +1110,9 @@
|
|
|
1124
1110
|
let currentSessionId = null;
|
|
1125
1111
|
let currentTasks = [];
|
|
1126
1112
|
let viewMode = 'session';
|
|
1127
|
-
let
|
|
1113
|
+
let sessionFilter = localStorage.getItem('sessionFilter') || 'all'; // 'all' or 'active'
|
|
1114
|
+
let filterProject = null; // null = all projects, or project path to filter
|
|
1115
|
+
let searchQuery = '';
|
|
1128
1116
|
|
|
1129
1117
|
// DOM
|
|
1130
1118
|
const sessionsList = document.getElementById('sessions-list');
|
|
@@ -1159,7 +1147,10 @@
|
|
|
1159
1147
|
try {
|
|
1160
1148
|
const res = await fetch('/api/tasks/all');
|
|
1161
1149
|
const allTasks = await res.json();
|
|
1162
|
-
|
|
1150
|
+
let activeTasks = allTasks.filter(t => t.status === 'in_progress');
|
|
1151
|
+
if (filterProject) {
|
|
1152
|
+
activeTasks = activeTasks.filter(t => t.project === filterProject);
|
|
1153
|
+
}
|
|
1163
1154
|
renderLiveUpdates(activeTasks);
|
|
1164
1155
|
} catch (error) {
|
|
1165
1156
|
console.error('Failed to fetch live updates:', error);
|
|
@@ -1193,6 +1184,7 @@
|
|
|
1193
1184
|
async function fetchTasks(sessionId) {
|
|
1194
1185
|
try {
|
|
1195
1186
|
viewMode = 'session';
|
|
1187
|
+
clearSearch();
|
|
1196
1188
|
const res = await fetch(`/api/sessions/${sessionId}`);
|
|
1197
1189
|
currentTasks = await res.json();
|
|
1198
1190
|
currentSessionId = sessionId;
|
|
@@ -1206,8 +1198,13 @@
|
|
|
1206
1198
|
try {
|
|
1207
1199
|
viewMode = 'all';
|
|
1208
1200
|
currentSessionId = null;
|
|
1201
|
+
clearSearch();
|
|
1209
1202
|
const res = await fetch('/api/tasks/all');
|
|
1210
|
-
|
|
1203
|
+
let tasks = await res.json();
|
|
1204
|
+
if (filterProject) {
|
|
1205
|
+
tasks = tasks.filter(t => t.project === filterProject);
|
|
1206
|
+
}
|
|
1207
|
+
currentTasks = tasks;
|
|
1211
1208
|
renderAllTasks();
|
|
1212
1209
|
renderSessions();
|
|
1213
1210
|
} catch (error) {
|
|
@@ -1223,8 +1220,11 @@
|
|
|
1223
1220
|
const completed = currentTasks.filter(t => t.status === 'completed').length;
|
|
1224
1221
|
const percent = totalTasks > 0 ? Math.round((completed / totalTasks) * 100) : 0;
|
|
1225
1222
|
|
|
1226
|
-
|
|
1227
|
-
|
|
1223
|
+
const projectName = filterProject ? filterProject.split('/').pop() : null;
|
|
1224
|
+
sessionTitle.textContent = filterProject ? `Tasks: ${projectName}` : 'All Tasks';
|
|
1225
|
+
sessionMeta.textContent = filterProject
|
|
1226
|
+
? `${totalTasks} tasks in this project`
|
|
1227
|
+
: `${totalTasks} tasks across ${sessions.length} sessions`;
|
|
1228
1228
|
progressPercent.textContent = `${percent}%`;
|
|
1229
1229
|
progressBar.style.width = `${percent}%`;
|
|
1230
1230
|
|
|
@@ -1232,46 +1232,56 @@
|
|
|
1232
1232
|
}
|
|
1233
1233
|
|
|
1234
1234
|
function renderSessions() {
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
if (!sessionsContainer) {
|
|
1245
|
-
sessionsContainer = document.createElement('div');
|
|
1246
|
-
sessionsContainer.id = 'sessions-items';
|
|
1247
|
-
sessionsList.appendChild(sessionsContainer);
|
|
1235
|
+
// Update project dropdown
|
|
1236
|
+
updateProjectDropdown();
|
|
1237
|
+
|
|
1238
|
+
let filteredSessions = sessions;
|
|
1239
|
+
if (sessionFilter === 'active') {
|
|
1240
|
+
filteredSessions = filteredSessions.filter(s => s.pending > 0 || s.inProgress > 0);
|
|
1241
|
+
}
|
|
1242
|
+
if (filterProject) {
|
|
1243
|
+
filteredSessions = filteredSessions.filter(s => s.project === filterProject);
|
|
1248
1244
|
}
|
|
1249
1245
|
|
|
1250
1246
|
if (filteredSessions.length === 0) {
|
|
1251
|
-
|
|
1247
|
+
let emptyMsg = 'No sessions found';
|
|
1248
|
+
let emptyHint = 'Tasks appear when you use Claude Code';
|
|
1249
|
+
if (filterProject && sessionFilter === 'active') {
|
|
1250
|
+
emptyMsg = 'No active sessions for this project';
|
|
1251
|
+
emptyHint = 'Try "All Sessions" or "All Projects"';
|
|
1252
|
+
} else if (filterProject) {
|
|
1253
|
+
emptyMsg = 'No sessions for this project';
|
|
1254
|
+
emptyHint = 'Select "All Projects" to see all';
|
|
1255
|
+
} else if (sessionFilter === 'active') {
|
|
1256
|
+
emptyMsg = 'No active sessions';
|
|
1257
|
+
emptyHint = 'Select "All Sessions" to see all';
|
|
1258
|
+
}
|
|
1259
|
+
sessionsList.innerHTML = `
|
|
1252
1260
|
<div style="padding: 24px 12px; text-align: center; color: var(--text-muted); font-size: 12px;">
|
|
1253
|
-
<p>${
|
|
1254
|
-
<p style="margin-top: 8px; font-size: 11px;">${
|
|
1261
|
+
<p>${emptyMsg}</p>
|
|
1262
|
+
<p style="margin-top: 8px; font-size: 11px;">${emptyHint}</p>
|
|
1255
1263
|
</div>
|
|
1256
1264
|
`;
|
|
1257
1265
|
return;
|
|
1258
1266
|
}
|
|
1259
1267
|
|
|
1260
|
-
|
|
1268
|
+
sessionsList.innerHTML = filteredSessions.map(session => {
|
|
1261
1269
|
const total = session.taskCount;
|
|
1262
1270
|
const percent = total > 0 ? Math.round((session.completed / total) * 100) : 0;
|
|
1263
1271
|
const isActive = session.id === currentSessionId && viewMode === 'session';
|
|
1264
1272
|
const hasInProgress = session.inProgress > 0;
|
|
1265
|
-
const
|
|
1273
|
+
const sessionName = session.name || session.id.slice(0, 8) + '...';
|
|
1266
1274
|
const projectName = session.project ? session.project.split('/').pop() : null;
|
|
1275
|
+
const primaryName = projectName || sessionName;
|
|
1276
|
+
const secondaryName = projectName ? sessionName : null;
|
|
1267
1277
|
|
|
1268
1278
|
return `
|
|
1269
1279
|
<button onclick="fetchTasks('${session.id}')" class="session-item ${isActive ? 'active' : ''}">
|
|
1270
1280
|
<div class="session-name">
|
|
1271
|
-
<span>${escapeHtml(
|
|
1281
|
+
<span>${escapeHtml(primaryName)}</span>
|
|
1272
1282
|
${hasInProgress ? '<span class="pulse"></span>' : ''}
|
|
1273
1283
|
</div>
|
|
1274
|
-
${
|
|
1284
|
+
${secondaryName ? `<div class="session-secondary">${escapeHtml(secondaryName)}</div>` : ''}
|
|
1275
1285
|
<div class="session-progress">
|
|
1276
1286
|
<div class="progress-bar"><div class="progress-fill" style="width: ${percent}%"></div></div>
|
|
1277
1287
|
<span class="progress-text">${session.completed}/${total}</span>
|
|
@@ -1326,9 +1336,17 @@
|
|
|
1326
1336
|
}
|
|
1327
1337
|
|
|
1328
1338
|
function renderKanban() {
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1339
|
+
let filteredTasks = currentTasks;
|
|
1340
|
+
if (searchQuery) {
|
|
1341
|
+
filteredTasks = currentTasks.filter(t =>
|
|
1342
|
+
t.subject.toLowerCase().includes(searchQuery) ||
|
|
1343
|
+
(t.description && t.description.toLowerCase().includes(searchQuery))
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
const pending = filteredTasks.filter(t => t.status === 'pending');
|
|
1348
|
+
const inProgress = filteredTasks.filter(t => t.status === 'in_progress');
|
|
1349
|
+
const completed = filteredTasks.filter(t => t.status === 'completed');
|
|
1332
1350
|
|
|
1333
1351
|
pendingCount.textContent = pending.length;
|
|
1334
1352
|
inProgressCount.textContent = inProgress.length;
|
|
@@ -1507,10 +1525,40 @@
|
|
|
1507
1525
|
return div.innerHTML;
|
|
1508
1526
|
}
|
|
1509
1527
|
|
|
1510
|
-
function
|
|
1511
|
-
|
|
1512
|
-
localStorage.setItem('
|
|
1528
|
+
function filterBySessions(value) {
|
|
1529
|
+
sessionFilter = value;
|
|
1530
|
+
localStorage.setItem('sessionFilter', sessionFilter);
|
|
1531
|
+
renderSessions();
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
function filterByProject(project) {
|
|
1535
|
+
filterProject = project || null;
|
|
1513
1536
|
renderSessions();
|
|
1537
|
+
fetchLiveUpdates();
|
|
1538
|
+
showAllTasks();
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
function searchTasks(query) {
|
|
1542
|
+
searchQuery = query.toLowerCase();
|
|
1543
|
+
renderKanban();
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
function clearSearch() {
|
|
1547
|
+
searchQuery = '';
|
|
1548
|
+
const searchInput = document.getElementById('task-search');
|
|
1549
|
+
if (searchInput) searchInput.value = '';
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
function updateProjectDropdown() {
|
|
1553
|
+
const dropdown = document.getElementById('project-filter');
|
|
1554
|
+
const projects = [...new Set(sessions.map(s => s.project).filter(Boolean))].sort();
|
|
1555
|
+
|
|
1556
|
+
dropdown.innerHTML = '<option value="">All Projects</option>' +
|
|
1557
|
+
projects.map(p => {
|
|
1558
|
+
const name = p.split('/').pop();
|
|
1559
|
+
const selected = p === filterProject ? ' selected' : '';
|
|
1560
|
+
return `<option value="${p}"${selected} title="${escapeHtml(p)}">${escapeHtml(name)}</option>`;
|
|
1561
|
+
}).join('');
|
|
1514
1562
|
}
|
|
1515
1563
|
|
|
1516
1564
|
function toggleTheme() {
|
|
@@ -1529,7 +1577,7 @@
|
|
|
1529
1577
|
}
|
|
1530
1578
|
|
|
1531
1579
|
function loadPreferences() {
|
|
1532
|
-
document.getElementById('
|
|
1580
|
+
document.getElementById('session-filter').value = sessionFilter;
|
|
1533
1581
|
}
|
|
1534
1582
|
|
|
1535
1583
|
// Init
|