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.
package/dist/bin/cli.js CHANGED
@@ -3522,6 +3522,16 @@ let state = {
3522
3522
  planningOutput: [],
3523
3523
  sidePanelTab: 'logs', // 'logs' or 'details'
3524
3524
  darkMode: localStorage.getItem('darkMode') === 'true', // Add dark mode state
3525
+ // View state (board or roadmap)
3526
+ currentView: 'board', // 'board' or 'roadmap'
3527
+ // Roadmap state
3528
+ roadmap: null,
3529
+ roadmapGenerating: false,
3530
+ roadmapProgress: null,
3531
+ roadmapError: null,
3532
+ roadmapSelectedFeature: null,
3533
+ roadmapEnableCompetitors: false,
3534
+ roadmapCustomPrompt: '',
3525
3535
  };
3526
3536
 
3527
3537
  // Toast notifications
@@ -3768,6 +3778,41 @@ socket.on('planning:cancelled', () => {
3768
3778
  render();
3769
3779
  });
3770
3780
 
3781
+ // Roadmap events
3782
+ socket.on('roadmap:started', () => {
3783
+ state.roadmapGenerating = true;
3784
+ state.roadmapProgress = { phase: 'analyzing', message: 'Starting roadmap generation...' };
3785
+ state.roadmapError = null;
3786
+ render();
3787
+ });
3788
+
3789
+ socket.on('roadmap:progress', ({ phase, message }) => {
3790
+ state.roadmapProgress = { phase, message };
3791
+ render();
3792
+ });
3793
+
3794
+ socket.on('roadmap:completed', ({ roadmap }) => {
3795
+ state.roadmap = roadmap;
3796
+ state.roadmapGenerating = false;
3797
+ state.roadmapProgress = null;
3798
+ state.showModal = null;
3799
+ showToast('Roadmap generated successfully!', 'success');
3800
+ render();
3801
+ });
3802
+
3803
+ socket.on('roadmap:failed', ({ error }) => {
3804
+ state.roadmapGenerating = false;
3805
+ state.roadmapProgress = null;
3806
+ state.roadmapError = error;
3807
+ showToast('Roadmap generation failed: ' + error, 'error');
3808
+ render();
3809
+ });
3810
+
3811
+ socket.on('roadmap:updated', (roadmap) => {
3812
+ state.roadmap = roadmap;
3813
+ render();
3814
+ });
3815
+
3771
3816
  // Load templates
3772
3817
  fetch('/api/templates').then(r => r.json()).then(data => {
3773
3818
  state.templates = data.templates;
@@ -3859,6 +3904,69 @@ async function cancelPlanning() {
3859
3904
  await fetch('/api/plan/cancel', { method: 'POST' });
3860
3905
  }
3861
3906
 
3907
+ // Roadmap API functions
3908
+ async function loadRoadmap() {
3909
+ const res = await fetch('/api/roadmap');
3910
+ const data = await res.json();
3911
+ state.roadmap = data.roadmap;
3912
+ render();
3913
+ }
3914
+
3915
+ async function generateRoadmap() {
3916
+ state.roadmapGenerating = true;
3917
+ state.roadmapError = null;
3918
+ render();
3919
+ try {
3920
+ await fetch('/api/roadmap/generate', {
3921
+ method: 'POST',
3922
+ headers: { 'Content-Type': 'application/json' },
3923
+ body: JSON.stringify({
3924
+ enableCompetitorResearch: state.roadmapEnableCompetitors,
3925
+ customPrompt: state.roadmapCustomPrompt || undefined
3926
+ })
3927
+ });
3928
+ } catch (e) {
3929
+ state.roadmapGenerating = false;
3930
+ state.roadmapError = e.message;
3931
+ render();
3932
+ }
3933
+ }
3934
+
3935
+ async function cancelRoadmap() {
3936
+ await fetch('/api/roadmap/cancel', { method: 'POST' });
3937
+ state.roadmapGenerating = false;
3938
+ state.roadmapProgress = null;
3939
+ render();
3940
+ }
3941
+
3942
+ async function addFeatureToKanban(featureId) {
3943
+ try {
3944
+ const res = await fetch('/api/roadmap/features/' + featureId + '/add-to-kanban', {
3945
+ method: 'POST'
3946
+ });
3947
+ const data = await res.json();
3948
+ if (data.task) {
3949
+ showToast('Feature added to kanban!', 'success');
3950
+ // Reload roadmap to update addedToKanban status
3951
+ await loadRoadmap();
3952
+ }
3953
+ } catch (e) {
3954
+ showToast('Failed to add feature: ' + e.message, 'error');
3955
+ }
3956
+ }
3957
+
3958
+ async function deleteRoadmapFeature(featureId) {
3959
+ try {
3960
+ await fetch('/api/roadmap/features/' + featureId, { method: 'DELETE' });
3961
+ showToast('Feature removed', 'info');
3962
+ } catch (e) {
3963
+ showToast('Failed to remove feature: ' + e.message, 'error');
3964
+ }
3965
+ }
3966
+
3967
+ // Load roadmap on init
3968
+ loadRoadmap();
3969
+
3862
3970
  // Enhanced Drag and drop
3863
3971
  let draggedTask = null;
3864
3972
  let draggedElement = null;
@@ -4031,6 +4139,185 @@ function showTaskMenu(taskId) {
4031
4139
  openSidePanel(taskId);
4032
4140
  }
4033
4141
 
4142
+ // Roadmap rendering functions
4143
+ function renderRoadmap() {
4144
+ if (state.roadmapGenerating) {
4145
+ return \`
4146
+ <div class="flex-1 flex items-center justify-center">
4147
+ <div class="text-center">
4148
+ <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-accent mx-auto mb-4"></div>
4149
+ <h3 class="text-lg font-medium text-canvas-800 mb-2">Generating Roadmap...</h3>
4150
+ <p class="text-canvas-500">\${state.roadmapProgress?.message || 'Please wait...'}</p>
4151
+ <button onclick="cancelRoadmap()" class="btn btn-ghost mt-4 text-sm">Cancel</button>
4152
+ </div>
4153
+ </div>
4154
+ \`;
4155
+ }
4156
+
4157
+ if (!state.roadmap) {
4158
+ return \`
4159
+ <div class="flex-1 flex items-center justify-center">
4160
+ <div class="text-center max-w-md">
4161
+ <div class="text-6xl mb-4">\u{1F5FA}\uFE0F</div>
4162
+ <h3 class="text-xl font-semibold text-canvas-800 mb-2">No Roadmap Yet</h3>
4163
+ <p class="text-canvas-500 mb-6">Generate a strategic feature roadmap using AI to analyze your project and suggest features.</p>
4164
+ <button onclick="state.showModal = 'roadmap'; render();" class="btn btn-primary px-6 py-2">
4165
+ \u{1F680} Generate Roadmap
4166
+ </button>
4167
+ </div>
4168
+ </div>
4169
+ \`;
4170
+ }
4171
+
4172
+ const roadmap = state.roadmap;
4173
+ const phases = roadmap.phases || [];
4174
+ const features = roadmap.features || [];
4175
+
4176
+ return \`
4177
+ <div class="flex-1 overflow-y-auto p-6">
4178
+ <!-- Roadmap Header -->
4179
+ <div class="mb-6 flex items-start justify-between">
4180
+ <div>
4181
+ <h2 class="text-2xl font-semibold text-canvas-900">\${escapeHtml(roadmap.projectName)} Roadmap</h2>
4182
+ <p class="text-canvas-500 mt-1">\${escapeHtml(roadmap.projectDescription || '')}</p>
4183
+ <p class="text-sm text-canvas-400 mt-2">Target: \${escapeHtml(roadmap.targetAudience || 'Developers')}</p>
4184
+ </div>
4185
+ <div class="flex gap-2">
4186
+ <button onclick="state.showModal = 'roadmap'; render();" class="btn btn-ghost text-sm">
4187
+ \u{1F504} Regenerate
4188
+ </button>
4189
+ </div>
4190
+ </div>
4191
+
4192
+ <!-- Competitors (if available) -->
4193
+ \${roadmap.competitors && roadmap.competitors.length > 0 ? \`
4194
+ <div class="mb-6">
4195
+ <h3 class="text-sm font-medium text-canvas-700 mb-2">Competitor Insights</h3>
4196
+ <div class="flex gap-2 flex-wrap">
4197
+ \${roadmap.competitors.map(c => \`
4198
+ <span class="px-3 py-1 bg-canvas-100 rounded-full text-sm text-canvas-600">
4199
+ \${escapeHtml(c.name)}
4200
+ </span>
4201
+ \`).join('')}
4202
+ </div>
4203
+ </div>
4204
+ \` : ''}
4205
+
4206
+ <!-- Phases -->
4207
+ <div class="space-y-8">
4208
+ \${phases.map(phase => {
4209
+ const phaseFeatures = features.filter(f => f.phase === phase.name);
4210
+ return \`
4211
+ <div class="phase-section">
4212
+ <div class="flex items-center gap-3 mb-4">
4213
+ <h3 class="text-lg font-semibold text-canvas-800">\${escapeHtml(phase.name)}</h3>
4214
+ <span class="text-xs bg-canvas-100 px-2 py-0.5 rounded-full text-canvas-500">\${phaseFeatures.length} features</span>
4215
+ </div>
4216
+ <p class="text-sm text-canvas-500 mb-4">\${escapeHtml(phase.description || '')}</p>
4217
+ <div class="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
4218
+ \${phaseFeatures.map(f => renderRoadmapFeature(f)).join('')}
4219
+ </div>
4220
+ </div>
4221
+ \`;
4222
+ }).join('')}
4223
+ </div>
4224
+ </div>
4225
+ \`;
4226
+ }
4227
+
4228
+ function renderRoadmapFeature(feature) {
4229
+ const priorityColors = {
4230
+ must: 'bg-red-100 text-red-700',
4231
+ should: 'bg-orange-100 text-orange-700',
4232
+ could: 'bg-blue-100 text-blue-700',
4233
+ wont: 'bg-gray-100 text-gray-500'
4234
+ };
4235
+ const priorityLabels = {
4236
+ must: 'Must Have',
4237
+ should: 'Should Have',
4238
+ could: 'Could Have',
4239
+ wont: "Won't Have"
4240
+ };
4241
+ const effortIcons = {
4242
+ low: '\u26A1',
4243
+ medium: '\u23F1\uFE0F',
4244
+ high: '\u{1F3CB}\uFE0F'
4245
+ };
4246
+ const impactIcons = {
4247
+ low: '\u{1F4C9}',
4248
+ medium: '\u{1F4CA}',
4249
+ high: '\u{1F4C8}'
4250
+ };
4251
+
4252
+ return \`
4253
+ <div class="card p-4 \${feature.addedToKanban ? 'opacity-60' : ''}" onclick="state.roadmapSelectedFeature = '\${feature.id}'; render();">
4254
+ <div class="flex items-start justify-between mb-2">
4255
+ <h4 class="font-medium text-canvas-800 text-sm">\${escapeHtml(feature.title)}</h4>
4256
+ <span class="text-xs px-2 py-0.5 rounded-full \${priorityColors[feature.priority] || 'bg-gray-100'}">\${priorityLabels[feature.priority] || feature.priority}</span>
4257
+ </div>
4258
+ <p class="text-xs text-canvas-500 mb-3 line-clamp-2">\${escapeHtml(feature.description)}</p>
4259
+ <div class="flex items-center justify-between">
4260
+ <div class="flex gap-2 text-xs text-canvas-400">
4261
+ <span title="Effort">\${effortIcons[feature.effort] || '\u23F1\uFE0F'} \${feature.effort}</span>
4262
+ <span title="Impact">\${impactIcons[feature.impact] || '\u{1F4CA}'} \${feature.impact}</span>
4263
+ </div>
4264
+ \${feature.addedToKanban ? \`
4265
+ <span class="text-xs text-green-600">\u2713 Added</span>
4266
+ \` : \`
4267
+ <button onclick="event.stopPropagation(); addFeatureToKanban('\${feature.id}')" class="text-xs text-accent hover:underline">+ Add to Kanban</button>
4268
+ \`}
4269
+ </div>
4270
+ </div>
4271
+ \`;
4272
+ }
4273
+
4274
+ function renderRoadmapModal() {
4275
+ return \`
4276
+ <div class="modal-backdrop fixed inset-0 flex items-center justify-center z-50" onclick="if(event.target === event.currentTarget) { state.showModal = null; render(); }">
4277
+ <div class="modal-content card rounded-xl w-full max-w-lg mx-4">
4278
+ <div class="px-6 py-4 border-b border-canvas-200 flex justify-between items-center">
4279
+ <h3 class="font-display font-semibold text-canvas-800 text-lg">\u{1F5FA}\uFE0F Generate Roadmap</h3>
4280
+ <button onclick="state.showModal = null; render();" class="text-canvas-400 hover:text-canvas-600 text-xl leading-none">&times;</button>
4281
+ </div>
4282
+ <div class="p-6">
4283
+ <p class="text-sm text-canvas-500 mb-6">
4284
+ Generate a strategic feature roadmap by analyzing your project structure and optionally researching competitors.
4285
+ </p>
4286
+
4287
+ <div class="space-y-5">
4288
+ <label class="flex items-start gap-3 cursor-pointer p-3 rounded-lg border border-canvas-200 hover:border-canvas-300 transition-colors">
4289
+ <input type="checkbox"
4290
+ \${state.roadmapEnableCompetitors ? 'checked' : ''}
4291
+ onchange="state.roadmapEnableCompetitors = this.checked; render();"
4292
+ class="w-4 h-4 mt-0.5 accent-accent">
4293
+ <div>
4294
+ <span class="text-sm font-medium text-canvas-700">Enable competitor research</span>
4295
+ <p class="text-xs text-canvas-400 mt-0.5">Use web search to analyze competitors (takes longer)</p>
4296
+ </div>
4297
+ </label>
4298
+
4299
+ <div>
4300
+ <label class="block text-sm font-medium text-canvas-700 mb-1.5">Additional context (optional)</label>
4301
+ <textarea
4302
+ class="input w-full text-sm"
4303
+ rows="3"
4304
+ placeholder="E.g., Focus on mobile features, target enterprise users..."
4305
+ oninput="state.roadmapCustomPrompt = this.value;"
4306
+ >\${escapeHtml(state.roadmapCustomPrompt)}</textarea>
4307
+ </div>
4308
+ </div>
4309
+ </div>
4310
+ <div class="px-6 py-4 border-t border-canvas-200 flex justify-end gap-3">
4311
+ <button onclick="state.showModal = null; render();" class="btn btn-ghost px-4 py-2">Cancel</button>
4312
+ <button onclick="generateRoadmap(); state.showModal = null; render();" class="btn btn-primary px-4 py-2">
4313
+ \u{1F680} Generate Roadmap
4314
+ </button>
4315
+ </div>
4316
+ </div>
4317
+ </div>
4318
+ \`;
4319
+ }
4320
+
4034
4321
  function renderColumn(status, title, tasks) {
4035
4322
  const columnTasks = tasks.filter(t => t.status === status);
4036
4323
  const statusLabels = {
@@ -4377,6 +4664,11 @@ function renderModal() {
4377
4664
  \`;
4378
4665
  }
4379
4666
 
4667
+ // Roadmap generation modal
4668
+ if (state.showModal === 'roadmap') {
4669
+ return renderRoadmapModal();
4670
+ }
4671
+
4380
4672
  return '';
4381
4673
  }
4382
4674
 
@@ -4696,7 +4988,7 @@ function escapeHtml(str) {
4696
4988
  // Main render
4697
4989
  function render() {
4698
4990
  const app = document.getElementById('app');
4699
- const hasSidePanel = state.sidePanel !== null;
4991
+ const hasSidePanel = state.sidePanel !== null && state.currentView === 'board';
4700
4992
 
4701
4993
  app.innerHTML = \`
4702
4994
  <div class="min-h-screen flex flex-col bg-canvas-50">
@@ -4705,51 +4997,76 @@ function render() {
4705
4997
  <div class="px-6 py-3 flex items-center justify-between">
4706
4998
  <div class="flex items-center gap-6">
4707
4999
  <h1 class="text-lg font-semibold text-canvas-900">Claude Kanban</h1>
4708
- <div class="flex items-center gap-2">
4709
- <input type="text"
4710
- placeholder="Search tasks..."
4711
- value="\${escapeHtml(state.searchQuery)}"
4712
- oninput="state.searchQuery = this.value; render();"
4713
- class="input text-sm py-1.5 w-48">
4714
- </div>
5000
+ <!-- Navigation Tabs -->
5001
+ <nav class="flex items-center gap-1 bg-canvas-100 rounded-lg p-1">
5002
+ <button onclick="state.currentView = 'board'; render();"
5003
+ 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'}">
5004
+ \u{1F4CB} Board
5005
+ </button>
5006
+ <button onclick="state.currentView = 'roadmap'; render();"
5007
+ 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'}">
5008
+ \u{1F5FA}\uFE0F Roadmap
5009
+ </button>
5010
+ </nav>
5011
+ \${state.currentView === 'board' ? \`
5012
+ <div class="flex items-center gap-2">
5013
+ <input type="text"
5014
+ placeholder="Search tasks..."
5015
+ value="\${escapeHtml(state.searchQuery)}"
5016
+ oninput="state.searchQuery = this.value; render();"
5017
+ class="input text-sm py-1.5 w-48">
5018
+ </div>
5019
+ \` : ''}
4715
5020
  </div>
4716
5021
  <div class="flex items-center gap-2">
4717
- <button onclick="openPlanningModal();"
4718
- class="btn px-4 py-2 text-sm bg-blue-500 hover:bg-blue-600 text-white \${state.planning ? 'opacity-50 cursor-not-allowed' : ''}"
4719
- \${state.planning ? 'disabled' : ''}
4720
- title="AI Task Planner">
4721
- \u{1F3AF} \${state.planning ? 'Planning...' : 'Plan'}
4722
- </button>
4723
- <button onclick="state.showModal = 'new'; render();"
4724
- class="btn btn-primary px-4 py-2 text-sm">
4725
- + Add Task
4726
- </button>
4727
- <button onclick="state.showModal = 'afk'; render();"
4728
- class="btn btn-ghost px-3 py-2 text-sm \${state.afk.running ? 'text-status-success' : ''}"
4729
- title="AFK Mode">
4730
- \u{1F504} \${state.afk.running ? 'AFK On' : 'AFK'}
4731
- </button>
5022
+ \${state.currentView === 'board' ? \`
5023
+ <button onclick="openPlanningModal();"
5024
+ class="btn px-4 py-2 text-sm bg-blue-500 hover:bg-blue-600 text-white \${state.planning ? 'opacity-50 cursor-not-allowed' : ''}"
5025
+ \${state.planning ? 'disabled' : ''}
5026
+ title="AI Task Planner">
5027
+ \u{1F3AF} \${state.planning ? 'Planning...' : 'Plan'}
5028
+ </button>
5029
+ <button onclick="state.showModal = 'new'; render();"
5030
+ class="btn btn-primary px-4 py-2 text-sm">
5031
+ + Add Task
5032
+ </button>
5033
+ <button onclick="state.showModal = 'afk'; render();"
5034
+ class="btn btn-ghost px-3 py-2 text-sm \${state.afk.running ? 'text-status-success' : ''}"
5035
+ title="AFK Mode">
5036
+ \u{1F504} \${state.afk.running ? 'AFK On' : 'AFK'}
5037
+ </button>
5038
+ \` : \`
5039
+ <button onclick="state.showModal = 'roadmap'; render();"
5040
+ class="btn btn-primary px-4 py-2 text-sm \${state.roadmapGenerating ? 'opacity-50 cursor-not-allowed' : ''}"
5041
+ \${state.roadmapGenerating ? 'disabled' : ''}>
5042
+ \${state.roadmapGenerating ? '\u23F3 Generating...' : '\u{1F680} Generate Roadmap'}
5043
+ </button>
5044
+ \`}
4732
5045
  </div>
4733
5046
  </div>
4734
5047
  </header>
4735
5048
 
4736
- \${renderAFKBar()}
5049
+ \${state.currentView === 'board' ? renderAFKBar() : ''}
4737
5050
 
4738
- <!-- Main Content Area - Flex container for board + sidebar -->
5051
+ <!-- Main Content Area -->
4739
5052
  <div class="flex flex-1 overflow-hidden">
4740
- <!-- Kanban Board -->
4741
- <main class="main-content overflow-x-auto p-6">
4742
- <div class="flex gap-4">
4743
- \${renderColumn('draft', 'To Do', filterTasks(state.tasks))}
4744
- \${renderColumn('ready', 'Ready', filterTasks(state.tasks))}
4745
- \${renderColumn('in_progress', 'In Progress', filterTasks(state.tasks))}
4746
- \${renderColumn('completed', 'Done', filterTasks(state.tasks))}
4747
- \${renderColumn('failed', 'Failed', filterTasks(state.tasks))}
4748
- </div>
4749
- </main>
4750
-
4751
- <!-- Side Panel (pushes content when open) -->
4752
- \${hasSidePanel ? renderSidePanel() : ''}
5053
+ \${state.currentView === 'board' ? \`
5054
+ <!-- Kanban Board -->
5055
+ <main class="main-content overflow-x-auto p-6">
5056
+ <div class="flex gap-4">
5057
+ \${renderColumn('draft', 'To Do', filterTasks(state.tasks))}
5058
+ \${renderColumn('ready', 'Ready', filterTasks(state.tasks))}
5059
+ \${renderColumn('in_progress', 'In Progress', filterTasks(state.tasks))}
5060
+ \${renderColumn('completed', 'Done', filterTasks(state.tasks))}
5061
+ \${renderColumn('failed', 'Failed', filterTasks(state.tasks))}
5062
+ </div>
5063
+ </main>
5064
+ <!-- Side Panel (pushes content when open) -->
5065
+ \${hasSidePanel ? renderSidePanel() : ''}
5066
+ \` : \`
5067
+ <!-- Roadmap View -->
5068
+ \${renderRoadmap()}
5069
+ \`}
4753
5070
  </div>
4754
5071
 
4755
5072
  \${renderModal()}
@@ -4811,6 +5128,11 @@ window.closeSidePanel = closeSidePanel;
4811
5128
  window.showTaskMenu = showTaskMenu;
4812
5129
  window.filterTasks = filterTasks;
4813
5130
  window.scrollSidePanelLog = scrollSidePanelLog;
5131
+ window.generateRoadmap = generateRoadmap;
5132
+ window.cancelRoadmap = cancelRoadmap;
5133
+ window.addFeatureToKanban = addFeatureToKanban;
5134
+ window.deleteRoadmapFeature = deleteRoadmapFeature;
5135
+ window.loadRoadmap = loadRoadmap;
4814
5136
 
4815
5137
  // Keyboard shortcuts
4816
5138
  document.addEventListener('keydown', (e) => {