claude-kanban 0.6.0 → 0.6.2

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.
@@ -3194,6 +3194,16 @@ let state = {
3194
3194
  planningOutput: [],
3195
3195
  sidePanelTab: 'logs', // 'logs' or 'details'
3196
3196
  darkMode: localStorage.getItem('darkMode') === 'true', // Add dark mode state
3197
+ // View state (board or roadmap)
3198
+ currentView: 'board', // 'board' or 'roadmap'
3199
+ // Roadmap state
3200
+ roadmap: null,
3201
+ roadmapGenerating: false,
3202
+ roadmapProgress: null,
3203
+ roadmapError: null,
3204
+ roadmapSelectedFeature: null,
3205
+ roadmapEnableCompetitors: false,
3206
+ roadmapCustomPrompt: '',
3197
3207
  };
3198
3208
 
3199
3209
  // Toast notifications
@@ -3440,6 +3450,41 @@ socket.on('planning:cancelled', () => {
3440
3450
  render();
3441
3451
  });
3442
3452
 
3453
+ // Roadmap events
3454
+ socket.on('roadmap:started', () => {
3455
+ state.roadmapGenerating = true;
3456
+ state.roadmapProgress = { phase: 'analyzing', message: 'Starting roadmap generation...' };
3457
+ state.roadmapError = null;
3458
+ render();
3459
+ });
3460
+
3461
+ socket.on('roadmap:progress', ({ phase, message }) => {
3462
+ state.roadmapProgress = { phase, message };
3463
+ render();
3464
+ });
3465
+
3466
+ socket.on('roadmap:completed', ({ roadmap }) => {
3467
+ state.roadmap = roadmap;
3468
+ state.roadmapGenerating = false;
3469
+ state.roadmapProgress = null;
3470
+ state.showModal = null;
3471
+ showToast('Roadmap generated successfully!', 'success');
3472
+ render();
3473
+ });
3474
+
3475
+ socket.on('roadmap:failed', ({ error }) => {
3476
+ state.roadmapGenerating = false;
3477
+ state.roadmapProgress = null;
3478
+ state.roadmapError = error;
3479
+ showToast('Roadmap generation failed: ' + error, 'error');
3480
+ render();
3481
+ });
3482
+
3483
+ socket.on('roadmap:updated', (roadmap) => {
3484
+ state.roadmap = roadmap;
3485
+ render();
3486
+ });
3487
+
3443
3488
  // Load templates
3444
3489
  fetch('/api/templates').then(r => r.json()).then(data => {
3445
3490
  state.templates = data.templates;
@@ -3531,6 +3576,69 @@ async function cancelPlanning() {
3531
3576
  await fetch('/api/plan/cancel', { method: 'POST' });
3532
3577
  }
3533
3578
 
3579
+ // Roadmap API functions
3580
+ async function loadRoadmap() {
3581
+ const res = await fetch('/api/roadmap');
3582
+ const data = await res.json();
3583
+ state.roadmap = data.roadmap;
3584
+ render();
3585
+ }
3586
+
3587
+ async function generateRoadmap() {
3588
+ state.roadmapGenerating = true;
3589
+ state.roadmapError = null;
3590
+ render();
3591
+ try {
3592
+ await fetch('/api/roadmap/generate', {
3593
+ method: 'POST',
3594
+ headers: { 'Content-Type': 'application/json' },
3595
+ body: JSON.stringify({
3596
+ enableCompetitorResearch: state.roadmapEnableCompetitors,
3597
+ customPrompt: state.roadmapCustomPrompt || undefined
3598
+ })
3599
+ });
3600
+ } catch (e) {
3601
+ state.roadmapGenerating = false;
3602
+ state.roadmapError = e.message;
3603
+ render();
3604
+ }
3605
+ }
3606
+
3607
+ async function cancelRoadmap() {
3608
+ await fetch('/api/roadmap/cancel', { method: 'POST' });
3609
+ state.roadmapGenerating = false;
3610
+ state.roadmapProgress = null;
3611
+ render();
3612
+ }
3613
+
3614
+ async function addFeatureToKanban(featureId) {
3615
+ try {
3616
+ const res = await fetch('/api/roadmap/features/' + featureId + '/add-to-kanban', {
3617
+ method: 'POST'
3618
+ });
3619
+ const data = await res.json();
3620
+ if (data.task) {
3621
+ showToast('Feature added to kanban!', 'success');
3622
+ // Reload roadmap to update addedToKanban status
3623
+ await loadRoadmap();
3624
+ }
3625
+ } catch (e) {
3626
+ showToast('Failed to add feature: ' + e.message, 'error');
3627
+ }
3628
+ }
3629
+
3630
+ async function deleteRoadmapFeature(featureId) {
3631
+ try {
3632
+ await fetch('/api/roadmap/features/' + featureId, { method: 'DELETE' });
3633
+ showToast('Feature removed', 'info');
3634
+ } catch (e) {
3635
+ showToast('Failed to remove feature: ' + e.message, 'error');
3636
+ }
3637
+ }
3638
+
3639
+ // Load roadmap on init
3640
+ loadRoadmap();
3641
+
3534
3642
  // Enhanced Drag and drop
3535
3643
  let draggedTask = null;
3536
3644
  let draggedElement = null;
@@ -3703,6 +3811,185 @@ function showTaskMenu(taskId) {
3703
3811
  openSidePanel(taskId);
3704
3812
  }
3705
3813
 
3814
+ // Roadmap rendering functions
3815
+ function renderRoadmap() {
3816
+ if (state.roadmapGenerating) {
3817
+ return \`
3818
+ <div class="flex-1 flex items-center justify-center">
3819
+ <div class="text-center">
3820
+ <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-accent mx-auto mb-4"></div>
3821
+ <h3 class="text-lg font-medium text-canvas-800 mb-2">Generating Roadmap...</h3>
3822
+ <p class="text-canvas-500">\${state.roadmapProgress?.message || 'Please wait...'}</p>
3823
+ <button onclick="cancelRoadmap()" class="btn btn-ghost mt-4 text-sm">Cancel</button>
3824
+ </div>
3825
+ </div>
3826
+ \`;
3827
+ }
3828
+
3829
+ if (!state.roadmap) {
3830
+ return \`
3831
+ <div class="flex-1 flex items-center justify-center">
3832
+ <div class="text-center max-w-md">
3833
+ <div class="text-6xl mb-4">\u{1F5FA}\uFE0F</div>
3834
+ <h3 class="text-xl font-semibold text-canvas-800 mb-2">No Roadmap Yet</h3>
3835
+ <p class="text-canvas-500 mb-6">Generate a strategic feature roadmap using AI to analyze your project and suggest features.</p>
3836
+ <button onclick="state.showModal = 'roadmap'; render();" class="btn btn-primary px-6 py-2">
3837
+ \u{1F680} Generate Roadmap
3838
+ </button>
3839
+ </div>
3840
+ </div>
3841
+ \`;
3842
+ }
3843
+
3844
+ const roadmap = state.roadmap;
3845
+ const phases = roadmap.phases || [];
3846
+ const features = roadmap.features || [];
3847
+
3848
+ return \`
3849
+ <div class="flex-1 overflow-y-auto p-6">
3850
+ <!-- Roadmap Header -->
3851
+ <div class="mb-6 flex items-start justify-between">
3852
+ <div>
3853
+ <h2 class="text-2xl font-semibold text-canvas-900">\${escapeHtml(roadmap.projectName)} Roadmap</h2>
3854
+ <p class="text-canvas-500 mt-1">\${escapeHtml(roadmap.projectDescription || '')}</p>
3855
+ <p class="text-sm text-canvas-400 mt-2">Target: \${escapeHtml(roadmap.targetAudience || 'Developers')}</p>
3856
+ </div>
3857
+ <div class="flex gap-2">
3858
+ <button onclick="state.showModal = 'roadmap'; render();" class="btn btn-ghost text-sm">
3859
+ \u{1F504} Regenerate
3860
+ </button>
3861
+ </div>
3862
+ </div>
3863
+
3864
+ <!-- Competitors (if available) -->
3865
+ \${roadmap.competitors && roadmap.competitors.length > 0 ? \`
3866
+ <div class="mb-6">
3867
+ <h3 class="text-sm font-medium text-canvas-700 mb-2">Competitor Insights</h3>
3868
+ <div class="flex gap-2 flex-wrap">
3869
+ \${roadmap.competitors.map(c => \`
3870
+ <span class="px-3 py-1 bg-canvas-100 rounded-full text-sm text-canvas-600">
3871
+ \${escapeHtml(c.name)}
3872
+ </span>
3873
+ \`).join('')}
3874
+ </div>
3875
+ </div>
3876
+ \` : ''}
3877
+
3878
+ <!-- Phases -->
3879
+ <div class="space-y-8">
3880
+ \${phases.map(phase => {
3881
+ const phaseFeatures = features.filter(f => f.phase === phase.name);
3882
+ return \`
3883
+ <div class="phase-section">
3884
+ <div class="flex items-center gap-3 mb-4">
3885
+ <h3 class="text-lg font-semibold text-canvas-800">\${escapeHtml(phase.name)}</h3>
3886
+ <span class="text-xs bg-canvas-100 px-2 py-0.5 rounded-full text-canvas-500">\${phaseFeatures.length} features</span>
3887
+ </div>
3888
+ <p class="text-sm text-canvas-500 mb-4">\${escapeHtml(phase.description || '')}</p>
3889
+ <div class="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
3890
+ \${phaseFeatures.map(f => renderRoadmapFeature(f)).join('')}
3891
+ </div>
3892
+ </div>
3893
+ \`;
3894
+ }).join('')}
3895
+ </div>
3896
+ </div>
3897
+ \`;
3898
+ }
3899
+
3900
+ function renderRoadmapFeature(feature) {
3901
+ const priorityColors = {
3902
+ must: 'bg-red-100 text-red-700',
3903
+ should: 'bg-orange-100 text-orange-700',
3904
+ could: 'bg-blue-100 text-blue-700',
3905
+ wont: 'bg-gray-100 text-gray-500'
3906
+ };
3907
+ const priorityLabels = {
3908
+ must: 'Must Have',
3909
+ should: 'Should Have',
3910
+ could: 'Could Have',
3911
+ wont: "Won't Have"
3912
+ };
3913
+ const effortIcons = {
3914
+ low: '\u26A1',
3915
+ medium: '\u23F1\uFE0F',
3916
+ high: '\u{1F3CB}\uFE0F'
3917
+ };
3918
+ const impactIcons = {
3919
+ low: '\u{1F4C9}',
3920
+ medium: '\u{1F4CA}',
3921
+ high: '\u{1F4C8}'
3922
+ };
3923
+
3924
+ return \`
3925
+ <div class="card p-4 \${feature.addedToKanban ? 'opacity-60' : ''}" onclick="state.roadmapSelectedFeature = '\${feature.id}'; render();">
3926
+ <div class="flex items-start justify-between mb-2">
3927
+ <h4 class="font-medium text-canvas-800 text-sm">\${escapeHtml(feature.title)}</h4>
3928
+ <span class="text-xs px-2 py-0.5 rounded-full \${priorityColors[feature.priority] || 'bg-gray-100'}">\${priorityLabels[feature.priority] || feature.priority}</span>
3929
+ </div>
3930
+ <p class="text-xs text-canvas-500 mb-3 line-clamp-2">\${escapeHtml(feature.description)}</p>
3931
+ <div class="flex items-center justify-between">
3932
+ <div class="flex gap-2 text-xs text-canvas-400">
3933
+ <span title="Effort">\${effortIcons[feature.effort] || '\u23F1\uFE0F'} \${feature.effort}</span>
3934
+ <span title="Impact">\${impactIcons[feature.impact] || '\u{1F4CA}'} \${feature.impact}</span>
3935
+ </div>
3936
+ \${feature.addedToKanban ? \`
3937
+ <span class="text-xs text-green-600">\u2713 Added</span>
3938
+ \` : \`
3939
+ <button onclick="event.stopPropagation(); addFeatureToKanban('\${feature.id}')" class="text-xs text-accent hover:underline">+ Add to Kanban</button>
3940
+ \`}
3941
+ </div>
3942
+ </div>
3943
+ \`;
3944
+ }
3945
+
3946
+ function renderRoadmapModal() {
3947
+ return \`
3948
+ <div class="modal-backdrop fixed inset-0 flex items-center justify-center z-50" onclick="if(event.target === event.currentTarget) { state.showModal = null; render(); }">
3949
+ <div class="modal-content card rounded-xl w-full max-w-lg mx-4">
3950
+ <div class="px-6 py-4 border-b border-canvas-200 flex justify-between items-center">
3951
+ <h3 class="font-display font-semibold text-canvas-800 text-lg">\u{1F5FA}\uFE0F Generate Roadmap</h3>
3952
+ <button onclick="state.showModal = null; render();" class="text-canvas-400 hover:text-canvas-600 text-xl leading-none">&times;</button>
3953
+ </div>
3954
+ <div class="p-6">
3955
+ <p class="text-sm text-canvas-500 mb-6">
3956
+ Generate a strategic feature roadmap by analyzing your project structure and optionally researching competitors.
3957
+ </p>
3958
+
3959
+ <div class="space-y-5">
3960
+ <label class="flex items-start gap-3 cursor-pointer p-3 rounded-lg border border-canvas-200 hover:border-canvas-300 transition-colors">
3961
+ <input type="checkbox"
3962
+ \${state.roadmapEnableCompetitors ? 'checked' : ''}
3963
+ onchange="state.roadmapEnableCompetitors = this.checked; render();"
3964
+ class="w-4 h-4 mt-0.5 accent-accent">
3965
+ <div>
3966
+ <span class="text-sm font-medium text-canvas-700">Enable competitor research</span>
3967
+ <p class="text-xs text-canvas-400 mt-0.5">Use web search to analyze competitors (takes longer)</p>
3968
+ </div>
3969
+ </label>
3970
+
3971
+ <div>
3972
+ <label class="block text-sm font-medium text-canvas-700 mb-1.5">Additional context (optional)</label>
3973
+ <textarea
3974
+ class="input w-full text-sm"
3975
+ rows="3"
3976
+ placeholder="E.g., Focus on mobile features, target enterprise users..."
3977
+ oninput="state.roadmapCustomPrompt = this.value;"
3978
+ >\${escapeHtml(state.roadmapCustomPrompt)}</textarea>
3979
+ </div>
3980
+ </div>
3981
+ </div>
3982
+ <div class="px-6 py-4 border-t border-canvas-200 flex justify-end gap-3">
3983
+ <button onclick="state.showModal = null; render();" class="btn btn-ghost px-4 py-2">Cancel</button>
3984
+ <button onclick="generateRoadmap(); state.showModal = null; render();" class="btn btn-primary px-4 py-2">
3985
+ \u{1F680} Generate Roadmap
3986
+ </button>
3987
+ </div>
3988
+ </div>
3989
+ </div>
3990
+ \`;
3991
+ }
3992
+
3706
3993
  function renderColumn(status, title, tasks) {
3707
3994
  const columnTasks = tasks.filter(t => t.status === status);
3708
3995
  const statusLabels = {
@@ -4049,6 +4336,11 @@ function renderModal() {
4049
4336
  \`;
4050
4337
  }
4051
4338
 
4339
+ // Roadmap generation modal
4340
+ if (state.showModal === 'roadmap') {
4341
+ return renderRoadmapModal();
4342
+ }
4343
+
4052
4344
  return '';
4053
4345
  }
4054
4346
 
@@ -4368,7 +4660,7 @@ function escapeHtml(str) {
4368
4660
  // Main render
4369
4661
  function render() {
4370
4662
  const app = document.getElementById('app');
4371
- const hasSidePanel = state.sidePanel !== null;
4663
+ const hasSidePanel = state.sidePanel !== null && state.currentView === 'board';
4372
4664
 
4373
4665
  app.innerHTML = \`
4374
4666
  <div class="min-h-screen flex flex-col bg-canvas-50">
@@ -4377,51 +4669,76 @@ function render() {
4377
4669
  <div class="px-6 py-3 flex items-center justify-between">
4378
4670
  <div class="flex items-center gap-6">
4379
4671
  <h1 class="text-lg font-semibold text-canvas-900">Claude Kanban</h1>
4380
- <div class="flex items-center gap-2">
4381
- <input type="text"
4382
- placeholder="Search tasks..."
4383
- value="\${escapeHtml(state.searchQuery)}"
4384
- oninput="state.searchQuery = this.value; render();"
4385
- class="input text-sm py-1.5 w-48">
4386
- </div>
4672
+ <!-- Navigation Tabs -->
4673
+ <nav class="flex items-center gap-1 bg-canvas-100 rounded-lg p-1">
4674
+ <button onclick="state.currentView = 'board'; render();"
4675
+ class="px-3 py-1.5 text-sm rounded-md transition-colors \${state.currentView === 'board' ? 'bg-white shadow-sm text-canvas-900 font-medium' : 'text-canvas-500 hover:text-canvas-700'}">
4676
+ \u{1F4CB} Board
4677
+ </button>
4678
+ <button onclick="state.currentView = 'roadmap'; render();"
4679
+ class="px-3 py-1.5 text-sm rounded-md transition-colors \${state.currentView === 'roadmap' ? 'bg-white shadow-sm text-canvas-900 font-medium' : 'text-canvas-500 hover:text-canvas-700'}">
4680
+ \u{1F5FA}\uFE0F Roadmap
4681
+ </button>
4682
+ </nav>
4683
+ \${state.currentView === 'board' ? \`
4684
+ <div class="flex items-center gap-2">
4685
+ <input type="text"
4686
+ placeholder="Search tasks..."
4687
+ value="\${escapeHtml(state.searchQuery)}"
4688
+ oninput="state.searchQuery = this.value; render();"
4689
+ class="input text-sm py-1.5 w-48">
4690
+ </div>
4691
+ \` : ''}
4387
4692
  </div>
4388
4693
  <div class="flex items-center gap-2">
4389
- <button onclick="openPlanningModal();"
4390
- class="btn px-4 py-2 text-sm bg-blue-500 hover:bg-blue-600 text-white \${state.planning ? 'opacity-50 cursor-not-allowed' : ''}"
4391
- \${state.planning ? 'disabled' : ''}
4392
- title="AI Task Planner">
4393
- \u{1F3AF} \${state.planning ? 'Planning...' : 'Plan'}
4394
- </button>
4395
- <button onclick="state.showModal = 'new'; render();"
4396
- class="btn btn-primary px-4 py-2 text-sm">
4397
- + Add Task
4398
- </button>
4399
- <button onclick="state.showModal = 'afk'; render();"
4400
- class="btn btn-ghost px-3 py-2 text-sm \${state.afk.running ? 'text-status-success' : ''}"
4401
- title="AFK Mode">
4402
- \u{1F504} \${state.afk.running ? 'AFK On' : 'AFK'}
4403
- </button>
4694
+ \${state.currentView === 'board' ? \`
4695
+ <button onclick="openPlanningModal();"
4696
+ class="btn px-4 py-2 text-sm bg-blue-500 hover:bg-blue-600 text-white \${state.planning ? 'opacity-50 cursor-not-allowed' : ''}"
4697
+ \${state.planning ? 'disabled' : ''}
4698
+ title="AI Task Planner">
4699
+ \u{1F3AF} \${state.planning ? 'Planning...' : 'Plan'}
4700
+ </button>
4701
+ <button onclick="state.showModal = 'new'; render();"
4702
+ class="btn btn-primary px-4 py-2 text-sm">
4703
+ + Add Task
4704
+ </button>
4705
+ <button onclick="state.showModal = 'afk'; render();"
4706
+ class="btn btn-ghost px-3 py-2 text-sm \${state.afk.running ? 'text-status-success' : ''}"
4707
+ title="AFK Mode">
4708
+ \u{1F504} \${state.afk.running ? 'AFK On' : 'AFK'}
4709
+ </button>
4710
+ \` : \`
4711
+ <button onclick="state.showModal = 'roadmap'; render();"
4712
+ class="btn btn-primary px-4 py-2 text-sm \${state.roadmapGenerating ? 'opacity-50 cursor-not-allowed' : ''}"
4713
+ \${state.roadmapGenerating ? 'disabled' : ''}>
4714
+ \${state.roadmapGenerating ? '\u23F3 Generating...' : '\u{1F680} Generate Roadmap'}
4715
+ </button>
4716
+ \`}
4404
4717
  </div>
4405
4718
  </div>
4406
4719
  </header>
4407
4720
 
4408
- \${renderAFKBar()}
4721
+ \${state.currentView === 'board' ? renderAFKBar() : ''}
4409
4722
 
4410
- <!-- Main Content Area - Flex container for board + sidebar -->
4723
+ <!-- Main Content Area -->
4411
4724
  <div class="flex flex-1 overflow-hidden">
4412
- <!-- Kanban Board -->
4413
- <main class="main-content overflow-x-auto p-6">
4414
- <div class="flex gap-4">
4415
- \${renderColumn('draft', 'To Do', filterTasks(state.tasks))}
4416
- \${renderColumn('ready', 'Ready', filterTasks(state.tasks))}
4417
- \${renderColumn('in_progress', 'In Progress', filterTasks(state.tasks))}
4418
- \${renderColumn('completed', 'Done', filterTasks(state.tasks))}
4419
- \${renderColumn('failed', 'Failed', filterTasks(state.tasks))}
4420
- </div>
4421
- </main>
4422
-
4423
- <!-- Side Panel (pushes content when open) -->
4424
- \${hasSidePanel ? renderSidePanel() : ''}
4725
+ \${state.currentView === 'board' ? \`
4726
+ <!-- Kanban Board -->
4727
+ <main class="main-content overflow-x-auto p-6">
4728
+ <div class="flex gap-4">
4729
+ \${renderColumn('draft', 'To Do', filterTasks(state.tasks))}
4730
+ \${renderColumn('ready', 'Ready', filterTasks(state.tasks))}
4731
+ \${renderColumn('in_progress', 'In Progress', filterTasks(state.tasks))}
4732
+ \${renderColumn('completed', 'Done', filterTasks(state.tasks))}
4733
+ \${renderColumn('failed', 'Failed', filterTasks(state.tasks))}
4734
+ </div>
4735
+ </main>
4736
+ <!-- Side Panel (pushes content when open) -->
4737
+ \${hasSidePanel ? renderSidePanel() : ''}
4738
+ \` : \`
4739
+ <!-- Roadmap View -->
4740
+ \${renderRoadmap()}
4741
+ \`}
4425
4742
  </div>
4426
4743
 
4427
4744
  \${renderModal()}
@@ -4483,6 +4800,11 @@ window.closeSidePanel = closeSidePanel;
4483
4800
  window.showTaskMenu = showTaskMenu;
4484
4801
  window.filterTasks = filterTasks;
4485
4802
  window.scrollSidePanelLog = scrollSidePanelLog;
4803
+ window.generateRoadmap = generateRoadmap;
4804
+ window.cancelRoadmap = cancelRoadmap;
4805
+ window.addFeatureToKanban = addFeatureToKanban;
4806
+ window.deleteRoadmapFeature = deleteRoadmapFeature;
4807
+ window.loadRoadmap = loadRoadmap;
4486
4808
 
4487
4809
  // Keyboard shortcuts
4488
4810
  document.addEventListener('keydown', (e) => {