cctd-web 0.1.0 → 0.2.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/public/index.html +72 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cctd-web",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Lightweight web viewer for CCTD .tasks/ directory",
5
5
  "bin": {
6
6
  "cctd-web": "./bin/cctd-web.js"
package/public/index.html CHANGED
@@ -164,6 +164,20 @@ a{color:var(--accent);text-decoration:none}
164
164
  </div>
165
165
 
166
166
  <div class="filter-bar" id="filterBar">
167
+ <label>Story Status:</label>
168
+ <select id="filterStoryStatus">
169
+ <option value="">All</option>
170
+ <option value="active">Active (non-DONE)</option>
171
+ <option value="BACKLOG">BACKLOG</option>
172
+ <option value="DEFINED">DEFINED</option>
173
+ <option value="AI_READY">AI_READY</option>
174
+ <option value="IN_PROGRESS">IN_PROGRESS</option>
175
+ <option value="TESTING">TESTING</option>
176
+ <option value="REVIEW">REVIEW</option>
177
+ <option value="DONE">DONE</option>
178
+ </select>
179
+ <label>Story Label:</label>
180
+ <select id="filterStoryLabel"><option value="">All</option></select>
167
181
  <label>Story:</label>
168
182
  <select id="filterStory"><option value="">All</option></select>
169
183
  <label>Priority:</label>
@@ -189,7 +203,7 @@ const STATUS_COLORS = {BACKLOG:'var(--backlog)',DEFINED:'var(--defined)',AI_READ
189
203
  let data = {stories:[], tasks:[]};
190
204
  let activityLog = [];
191
205
  let view = 'kanban';
192
- let filters = {story:'', priority:''};
206
+ let filters = {storyStatus:'', storyLabel:'', story:'', priority:''};
193
207
 
194
208
  // Normalize status
195
209
  function norm(s){return STATUS_ALIASES[s?.toUpperCase()] || s?.toUpperCase() || 'BACKLOG';}
@@ -229,6 +243,8 @@ document.querySelectorAll('.view-toggle button').forEach(btn => {
229
243
  });
230
244
 
231
245
  // Filters
246
+ document.getElementById('filterStoryStatus').addEventListener('change', e => {filters.storyStatus = e.target.value; render();});
247
+ document.getElementById('filterStoryLabel').addEventListener('change', e => {filters.storyLabel = e.target.value; render();});
232
248
  document.getElementById('filterStory').addEventListener('change', e => {filters.story = e.target.value; render();});
233
249
  document.getElementById('filterPriority').addEventListener('change', e => {filters.priority = e.target.value; render();});
234
250
 
@@ -320,9 +336,38 @@ function esc(s){
320
336
  return d.innerHTML;
321
337
  }
322
338
 
339
+ function getFilteredStories(){
340
+ let stories = data.stories;
341
+ if(filters.storyStatus){
342
+ if(filters.storyStatus === 'active'){
343
+ stories = stories.filter(s => norm(s.status) !== 'DONE');
344
+ } else {
345
+ stories = stories.filter(s => norm(s.status) === filters.storyStatus);
346
+ }
347
+ }
348
+ if(filters.storyLabel){
349
+ stories = stories.filter(s => (s.labels||[]).some(l => l.toLowerCase() === filters.storyLabel.toLowerCase()));
350
+ }
351
+ if(filters.story){
352
+ stories = stories.filter(s => s.id === filters.story);
353
+ }
354
+ return stories;
355
+ }
356
+
323
357
  function getFiltered(){
358
+ const matchedStories = getFilteredStories();
359
+ const storyIds = new Set(matchedStories.map(s => s.id));
324
360
  let tasks = data.tasks;
325
- if(filters.story) tasks = tasks.filter(t => t.story === filters.story || t.id?.startsWith(filters.story));
361
+
362
+ // If any story filter is active, restrict to matching stories' tasks
363
+ const hasStoryFilter = filters.storyStatus || filters.storyLabel || filters.story;
364
+ if(hasStoryFilter){
365
+ tasks = tasks.filter(t => {
366
+ const sid = t.story || (t.id?.includes('-') ? t.id.split('-')[0] : '');
367
+ return sid && storyIds.has(sid);
368
+ });
369
+ }
370
+
326
371
  if(filters.priority) tasks = tasks.filter(t => (t.priority||'medium').toLowerCase() === filters.priority);
327
372
  return tasks;
328
373
  }
@@ -348,19 +393,35 @@ function updateStats(){
348
393
  }
349
394
 
350
395
  function updateStoryFilter(){
396
+ // Story dropdown — show only stories that pass status+label filters
397
+ const filtered = getFilteredStories();
351
398
  const sel = document.getElementById('filterStory');
352
399
  const cur = sel.value;
353
- const opts = data.stories.map(s => `<option value="${esc(s.id)}">${esc(s.id)} - ${esc(s.title)}</option>`);
400
+ const opts = filtered.map(s => `<option value="${esc(s.id)}">${esc(s.id)} - ${esc(s.title)}</option>`);
354
401
  sel.innerHTML = `<option value="">All</option>` + opts.join('');
355
402
  sel.value = cur;
403
+
404
+ // Story label dropdown — collect all unique labels
405
+ const labelSel = document.getElementById('filterStoryLabel');
406
+ const curLabel = labelSel.value;
407
+ const allLabels = new Set();
408
+ data.stories.forEach(s => (s.labels||[]).forEach(l => allLabels.add(l)));
409
+ const labelOpts = [...allLabels].sort().map(l => `<option value="${esc(l)}">${esc(l)}</option>`);
410
+ labelSel.innerHTML = `<option value="">All</option>` + labelOpts.join('');
411
+ labelSel.value = curLabel;
356
412
  }
357
413
 
358
414
  function renderKanban(){
359
415
  const tasks = getFiltered();
360
416
  const main = document.getElementById('main');
417
+ const hasStoryFilter = filters.storyStatus || filters.storyLabel || filters.story;
361
418
 
362
- if(!tasks.length && !data.stories.length){
363
- main.innerHTML = `<div class="empty"><h2>No tasks found</h2><p>Create tasks with CCTD to see them here.</p><code>/cctd init</code></div>`;
419
+ if(!tasks.length){
420
+ if(!data.stories.length && !data.tasks.length){
421
+ main.innerHTML = `<div class="empty"><h2>No tasks found</h2><p>Create tasks with CCTD to see them here.</p><code>/cctd init</code></div>`;
422
+ } else {
423
+ main.innerHTML = `<div class="empty"><h2>No matching tasks</h2><p>Try adjusting your filters.</p></div>`;
424
+ }
364
425
  return;
365
426
  }
366
427
 
@@ -393,8 +454,12 @@ function renderList(){
393
454
  const tasks = getFiltered();
394
455
  const main = document.getElementById('main');
395
456
 
396
- if(!tasks.length && !data.stories.length){
397
- main.innerHTML = `<div class="empty"><h2>No tasks found</h2><p>Create tasks with CCTD to see them here.</p><code>/cctd init</code></div>`;
457
+ if(!tasks.length){
458
+ if(!data.stories.length && !data.tasks.length){
459
+ main.innerHTML = `<div class="empty"><h2>No tasks found</h2><p>Create tasks with CCTD to see them here.</p><code>/cctd init</code></div>`;
460
+ } else {
461
+ main.innerHTML = `<div class="empty"><h2>No matching tasks</h2><p>Try adjusting your filters.</p></div>`;
462
+ }
398
463
  return;
399
464
  }
400
465