jettypod 4.4.43 → 4.4.44

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.
@@ -1,3 +1,6 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
1
4
  import Link from 'next/link';
2
5
  import type { WorkItem, InFlightItem, KanbanGroup } from '@/lib/db';
3
6
 
@@ -21,34 +24,72 @@ interface KanbanCardProps {
21
24
  }
22
25
 
23
26
  function KanbanCard({ item, epicTitle, showEpic = false }: KanbanCardProps) {
27
+ const [expanded, setExpanded] = useState(false);
28
+ const hasChores = item.chores && item.chores.length > 0;
29
+
24
30
  return (
25
- <Link
26
- href={`/work/${item.id}`}
27
- className="block bg-white dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700 p-3 hover:border-zinc-300 dark:hover:border-zinc-600 hover:shadow-sm transition-all"
28
- >
29
- <div className="flex items-start gap-2">
30
- <span className="text-sm flex-shrink-0">{typeIcons[item.type] || '📄'}</span>
31
- <div className="flex-1 min-w-0">
32
- <div className="flex items-center gap-2 mb-1 flex-wrap">
33
- <span className="text-xs text-zinc-400 font-mono">#{item.id}</span>
34
- {item.mode && modeLabels[item.mode] && (
35
- <span className={`text-xs px-1.5 py-0.5 rounded ${modeLabels[item.mode].color}`}>
36
- {modeLabels[item.mode].label}
37
- </span>
31
+ <div className="bg-white dark:bg-zinc-800 rounded-lg border border-zinc-200 dark:border-zinc-700 hover:border-zinc-300 dark:hover:border-zinc-600 hover:shadow-sm transition-all">
32
+ <Link
33
+ href={`/work/${item.id}`}
34
+ className="block p-3"
35
+ >
36
+ <div className="flex items-start gap-2">
37
+ <span className="text-sm flex-shrink-0">{typeIcons[item.type] || '📄'}</span>
38
+ <div className="flex-1 min-w-0">
39
+ <div className="flex items-center gap-2 mb-1 flex-wrap">
40
+ <span className="text-xs text-zinc-400 font-mono">#{item.id}</span>
41
+ {item.mode && modeLabels[item.mode] && (
42
+ <span className={`text-xs px-1.5 py-0.5 rounded ${modeLabels[item.mode].color}`}>
43
+ {modeLabels[item.mode].label}
44
+ </span>
45
+ )}
46
+ </div>
47
+ <p className="text-sm text-zinc-900 dark:text-zinc-100 leading-snug">
48
+ {item.title}
49
+ </p>
50
+ {showEpic && epicTitle && (
51
+ <p className="text-xs text-zinc-500 dark:text-zinc-400 mt-1.5 flex items-center gap-1">
52
+ <span>🎯</span>
53
+ <span>{epicTitle}</span>
54
+ </p>
38
55
  )}
39
56
  </div>
40
- <p className="text-sm text-zinc-900 dark:text-zinc-100 leading-snug">
41
- {item.title}
42
- </p>
43
- {showEpic && epicTitle && (
44
- <p className="text-xs text-zinc-500 dark:text-zinc-400 mt-1.5 flex items-center gap-1">
45
- <span>🎯</span>
46
- <span>{epicTitle}</span>
47
- </p>
57
+ </div>
58
+ </Link>
59
+ {hasChores && (
60
+ <div className="border-t border-zinc-200 dark:border-zinc-700">
61
+ <button
62
+ onClick={() => setExpanded(!expanded)}
63
+ className="w-full px-3 py-1.5 flex items-center gap-1.5 text-xs text-zinc-600 dark:text-zinc-300 hover:bg-zinc-50 dark:hover:bg-zinc-700/50 transition-colors"
64
+ >
65
+ <span>{expanded ? '▼' : '▶'}</span>
66
+ <span>🔧</span>
67
+ <span>{item.chores!.length} chore{item.chores!.length !== 1 ? 's' : ''}</span>
68
+ </button>
69
+ {expanded && (
70
+ <div className="px-3 pb-2 space-y-1">
71
+ {item.chores!.map((chore) => (
72
+ <Link
73
+ key={chore.id}
74
+ href={`/work/${chore.id}`}
75
+ className="block py-1 px-2 text-xs rounded hover:bg-zinc-100 dark:hover:bg-zinc-700 transition-colors"
76
+ >
77
+ <div className="flex items-center gap-2">
78
+ <span className="text-zinc-400 font-mono">#{chore.id}</span>
79
+ {chore.mode && modeLabels[chore.mode] && (
80
+ <span className={`px-1 py-0.5 rounded text-[10px] ${modeLabels[chore.mode].color}`}>
81
+ {modeLabels[chore.mode].label}
82
+ </span>
83
+ )}
84
+ <span className="text-zinc-700 dark:text-zinc-300 truncate">{chore.title}</span>
85
+ </div>
86
+ </Link>
87
+ ))}
88
+ </div>
48
89
  )}
49
90
  </div>
50
- </div>
51
- </Link>
91
+ )}
92
+ </div>
52
93
  );
53
94
  }
54
95
 
@@ -20,6 +20,7 @@ export interface WorkItem {
20
20
  completed_at: string | null;
21
21
  created_at: string;
22
22
  children?: WorkItem[];
23
+ chores?: WorkItem[];
23
24
  }
24
25
 
25
26
  export interface Decision {
@@ -203,6 +204,26 @@ export function getKanbanData(doneLimit: number = 50): KanbanData {
203
204
  `).all() as { id: number; title: string }[];
204
205
  const epicMap = new Map(epics.map(e => [e.id, e.title]));
205
206
 
207
+ // Get all chores that belong to features (for chore expansion)
208
+ const featureChores = db.prepare(`
209
+ SELECT c.id, c.type, c.title, c.description, c.status, c.parent_id, c.epic_id,
210
+ c.branch_name, c.mode, c.phase, c.completed_at, c.created_at
211
+ FROM work_items c
212
+ INNER JOIN work_items f ON c.parent_id = f.id
213
+ WHERE c.type = 'chore' AND f.type = 'feature'
214
+ ORDER BY c.id
215
+ `).all() as WorkItem[];
216
+
217
+ // Group chores by parent feature ID
218
+ const choresByFeature = new Map<number, WorkItem[]>();
219
+ for (const chore of featureChores) {
220
+ if (chore.parent_id) {
221
+ const existing = choresByFeature.get(chore.parent_id) || [];
222
+ existing.push(chore);
223
+ choresByFeature.set(chore.parent_id, existing);
224
+ }
225
+ }
226
+
206
227
  // Get kanban-eligible items:
207
228
  // - Features (type = 'feature')
208
229
  // - Chores that are NOT children of features (parent is null, or parent is an epic)
@@ -241,6 +262,11 @@ export function getKanbanData(doneLimit: number = 50): KanbanData {
241
262
  // Strip parent_type from the item
242
263
  const { parent_type, ...cleanItem } = item;
243
264
 
265
+ // Attach chores to features
266
+ if (cleanItem.type === 'feature') {
267
+ cleanItem.chores = choresByFeature.get(cleanItem.id) || [];
268
+ }
269
+
244
270
  if (cleanItem.status === 'in_progress') {
245
271
  const epicId = cleanItem.parent_id || cleanItem.epic_id;
246
272
  const epicTitle = epicId ? epicMap.get(epicId) || null : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jettypod",
3
- "version": "4.4.43",
3
+ "version": "4.4.44",
4
4
  "description": "AI-powered development workflow manager with TDD, BDD, and automatic test generation",
5
5
  "main": "jettypod.js",
6
6
  "bin": {
@@ -703,24 +703,26 @@ Sound good? I'll create these chores once you confirm.
703
703
 
704
704
  **After user confirms, execute autonomously:**
705
705
 
706
- **1. Create stable mode chores:**
706
+ **1. Set feature mode to stable FIRST:**
707
707
 
708
708
  ```bash
709
- jettypod work create chore "[Chore title]" "[Description with scenarios addressed]" --parent=<feature-id> --mode=stable
709
+ jettypod work set-mode <feature-id> stable
710
710
  ```
711
711
 
712
- Repeat for each confirmed chore.
712
+ **🛑 CRITICAL:** You MUST set mode to stable BEFORE creating chores. The system blocks chore creation while feature is in speed mode.
713
713
 
714
- **2. Release merge lock:**
714
+ **2. Create stable mode chores:**
715
715
 
716
716
  ```bash
717
- jettypod work merge --release-lock
717
+ jettypod work create chore "[Chore title]" "[Description with scenarios addressed]" --parent=<feature-id>
718
718
  ```
719
719
 
720
- **3. Set feature mode to stable:**
720
+ Repeat for each confirmed chore. **Do NOT use `--mode` flag** - chores inherit mode from their parent feature.
721
+
722
+ **3. Release merge lock:**
721
723
 
722
724
  ```bash
723
- jettypod work set-mode <feature-id> stable
725
+ jettypod work merge --release-lock
724
726
  ```
725
727
 
726
728
  #### Step 7E: Start First Stable Chore and Invoke Stable Mode Skill
@@ -730,9 +732,11 @@ jettypod work set-mode <feature-id> stable
730
732
  **1. Get the first stable chore:**
731
733
 
732
734
  ```bash
733
- sqlite3 .jettypod/work.db "SELECT id, title FROM work_items WHERE parent_id = <feature-id> AND type = 'chore' AND mode = 'stable' AND status != 'done' ORDER BY created_at LIMIT 1"
735
+ sqlite3 .jettypod/work.db "SELECT id, title FROM work_items WHERE parent_id = <feature-id> AND type = 'chore' AND status != 'done' ORDER BY created_at LIMIT 1"
734
736
  ```
735
737
 
738
+ Note: Chores don't have a `mode` column - they inherit context from their parent feature. Since we just set the feature to stable mode, these are the stable chores.
739
+
736
740
  **2. Start the first stable chore:**
737
741
 
738
742
  ```bash
@@ -822,15 +826,16 @@ jettypod work merge --release-lock # Release held lock
822
826
  jettypod work start <chore-id> # Create worktree and start chore
823
827
  ```
824
828
 
825
- **Create chores:**
829
+ **Set feature mode (BEFORE creating chores):**
826
830
  ```bash
827
- jettypod work create chore "<title>" "<description>" --parent=<feature-id> --mode=stable
831
+ jettypod work set-mode <feature-id> stable
828
832
  ```
829
833
 
830
- **Set feature mode:**
834
+ **Create chores (AFTER setting mode):**
831
835
  ```bash
832
- jettypod work set-mode <feature-id> stable
836
+ jettypod work create chore "<title>" "<description>" --parent=<feature-id>
833
837
  ```
838
+ Note: Do NOT use `--mode` flag - chores inherit mode from parent feature.
834
839
 
835
840
  **❌ DO NOT use these to complete chores:**
836
841
  - `jettypod work status <id> done`