kanban-lite 1.0.4

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 (99) hide show
  1. package/.editorconfig +9 -0
  2. package/.github/workflows/ci.yml +59 -0
  3. package/.github/workflows/release.yml +75 -0
  4. package/.prettierignore +6 -0
  5. package/.prettierrc.yaml +4 -0
  6. package/.vscode/extensions.json +3 -0
  7. package/.vscode/launch.json +17 -0
  8. package/.vscode/settings.json +21 -0
  9. package/.vscode/tasks.json +22 -0
  10. package/.vscodeignore +11 -0
  11. package/CHANGELOG.md +184 -0
  12. package/CLAUDE.md +58 -0
  13. package/CONTRIBUTING.md +114 -0
  14. package/LICENSE +22 -0
  15. package/README.md +482 -0
  16. package/SKILL.md +237 -0
  17. package/dist/cli.js +8716 -0
  18. package/dist/extension.js +8463 -0
  19. package/dist/mcp-server.js +1327 -0
  20. package/dist/standalone-webview/icons-Dx9MGYqN.js +180 -0
  21. package/dist/standalone-webview/icons-Dx9MGYqN.js.map +1 -0
  22. package/dist/standalone-webview/index.js +85 -0
  23. package/dist/standalone-webview/index.js.map +1 -0
  24. package/dist/standalone-webview/react-vendor-DkYdDBET.js +25 -0
  25. package/dist/standalone-webview/react-vendor-DkYdDBET.js.map +1 -0
  26. package/dist/standalone-webview/style.css +1 -0
  27. package/dist/standalone.js +7513 -0
  28. package/dist/webview/icons-Dx9MGYqN.js +180 -0
  29. package/dist/webview/icons-Dx9MGYqN.js.map +1 -0
  30. package/dist/webview/index.js +85 -0
  31. package/dist/webview/index.js.map +1 -0
  32. package/dist/webview/react-vendor-DkYdDBET.js +25 -0
  33. package/dist/webview/react-vendor-DkYdDBET.js.map +1 -0
  34. package/dist/webview/style.css +1 -0
  35. package/docs/images/board-overview.png +0 -0
  36. package/docs/images/editor-view.png +0 -0
  37. package/docs/plans/2026-02-20-kanban-json-config-design.md +74 -0
  38. package/docs/plans/2026-02-20-kanban-json-config.md +690 -0
  39. package/eslint.config.mjs +31 -0
  40. package/package.json +161 -0
  41. package/postcss.config.js +6 -0
  42. package/resources/icon-light.png +0 -0
  43. package/resources/icon-light.svg +105 -0
  44. package/resources/icon.png +0 -0
  45. package/resources/icon.svg +105 -0
  46. package/resources/kanban-dark.svg +21 -0
  47. package/resources/kanban-light.svg +21 -0
  48. package/resources/kanban.svg +21 -0
  49. package/src/cli/index.ts +846 -0
  50. package/src/extension/FeatureHeaderProvider.ts +370 -0
  51. package/src/extension/KanbanPanel.ts +973 -0
  52. package/src/extension/SidebarViewProvider.ts +507 -0
  53. package/src/extension/featureFileUtils.ts +82 -0
  54. package/src/extension/index.ts +234 -0
  55. package/src/mcp-server/index.ts +632 -0
  56. package/src/sdk/KanbanSDK.ts +349 -0
  57. package/src/sdk/__tests__/KanbanSDK.test.ts +468 -0
  58. package/src/sdk/__tests__/parser.test.ts +170 -0
  59. package/src/sdk/fileUtils.ts +76 -0
  60. package/src/sdk/index.ts +6 -0
  61. package/src/sdk/parser.ts +70 -0
  62. package/src/sdk/types.ts +15 -0
  63. package/src/shared/config.ts +113 -0
  64. package/src/shared/editorTypes.ts +14 -0
  65. package/src/shared/types.ts +120 -0
  66. package/src/standalone/__tests__/server.integration.test.ts +1916 -0
  67. package/src/standalone/__tests__/webhooks.test.ts +357 -0
  68. package/src/standalone/fileUtils.ts +70 -0
  69. package/src/standalone/index.ts +71 -0
  70. package/src/standalone/server.ts +1046 -0
  71. package/src/standalone/webhooks.ts +135 -0
  72. package/src/webview/App.tsx +469 -0
  73. package/src/webview/assets/main.css +329 -0
  74. package/src/webview/assets/standalone-theme.css +130 -0
  75. package/src/webview/components/ColumnDialog.tsx +119 -0
  76. package/src/webview/components/CreateFeatureDialog.tsx +524 -0
  77. package/src/webview/components/DatePicker.tsx +185 -0
  78. package/src/webview/components/FeatureCard.tsx +186 -0
  79. package/src/webview/components/FeatureEditor.tsx +623 -0
  80. package/src/webview/components/KanbanBoard.tsx +144 -0
  81. package/src/webview/components/KanbanColumn.tsx +159 -0
  82. package/src/webview/components/MarkdownEditor.tsx +291 -0
  83. package/src/webview/components/PrioritySelect.tsx +39 -0
  84. package/src/webview/components/QuickAddInput.tsx +72 -0
  85. package/src/webview/components/SettingsPanel.tsx +284 -0
  86. package/src/webview/components/Toolbar.tsx +175 -0
  87. package/src/webview/components/UndoToast.tsx +70 -0
  88. package/src/webview/index.html +12 -0
  89. package/src/webview/lib/utils.ts +6 -0
  90. package/src/webview/main.tsx +11 -0
  91. package/src/webview/standalone-main.tsx +13 -0
  92. package/src/webview/standalone-shim.ts +132 -0
  93. package/src/webview/standalone.html +12 -0
  94. package/src/webview/store/index.ts +241 -0
  95. package/tailwind.config.js +53 -0
  96. package/tsconfig.json +22 -0
  97. package/vite.config.ts +36 -0
  98. package/vite.standalone.config.ts +62 -0
  99. package/vitest.config.ts +15 -0
@@ -0,0 +1,234 @@
1
+ import * as net from 'node:net'
2
+ import * as path from 'node:path'
3
+
4
+ import { generateKeyBetween } from 'fractional-indexing'
5
+ import * as vscode from 'vscode'
6
+
7
+ import type { Feature, FeatureStatus, Priority } from '../shared/types'
8
+ import { generateFeatureFilename } from '../shared/types'
9
+ import { readConfig, allocateCardId } from '../shared/config'
10
+ import { startServer } from '../standalone/server'
11
+ import { ensureStatusSubfolders, getFeatureFilePath } from './featureFileUtils'
12
+ import { KanbanPanel } from './KanbanPanel'
13
+ import { SidebarViewProvider } from './SidebarViewProvider'
14
+
15
+ async function createFeatureFromPrompts(): Promise<void> {
16
+ const workspaceFolders = vscode.workspace.workspaceFolders
17
+ if (!workspaceFolders || workspaceFolders.length === 0) {
18
+ vscode.window.showErrorMessage('No workspace folder open')
19
+ return
20
+ }
21
+
22
+ // Ask for title
23
+ const title = await vscode.window.showInputBox({
24
+ prompt: 'Feature title',
25
+ placeHolder: 'Enter a title for the new feature'
26
+ })
27
+ if (!title) return
28
+
29
+ // Ask for status
30
+ const statusItems: vscode.QuickPickItem[] = [
31
+ { label: 'Backlog', description: 'Not yet planned' },
32
+ { label: 'To Do', description: 'Planned for development' },
33
+ { label: 'In Progress', description: 'Currently being worked on' },
34
+ { label: 'Review', description: 'Ready for review' },
35
+ { label: 'Done', description: 'Completed' }
36
+ ]
37
+ const statusPick = await vscode.window.showQuickPick(statusItems, {
38
+ placeHolder: 'Select initial status'
39
+ })
40
+ if (!statusPick) return
41
+
42
+ const statusMap: Record<string, FeatureStatus> = {
43
+ 'Backlog': 'backlog',
44
+ 'To Do': 'todo',
45
+ 'In Progress': 'in-progress',
46
+ 'Review': 'review',
47
+ 'Done': 'done'
48
+ }
49
+ const status = statusMap[statusPick.label]
50
+
51
+ // Ask for priority
52
+ const priorityItems: vscode.QuickPickItem[] = [
53
+ { label: 'Critical', description: 'Urgent, needs immediate attention' },
54
+ { label: 'High', description: 'Important, should be done soon' },
55
+ { label: 'Medium', description: 'Normal priority' },
56
+ { label: 'Low', description: 'Nice to have, can wait' }
57
+ ]
58
+ const priorityPick = await vscode.window.showQuickPick(priorityItems, {
59
+ placeHolder: 'Select priority'
60
+ })
61
+ if (!priorityPick) return
62
+
63
+ const priority = priorityPick.label.toLowerCase() as Priority
64
+
65
+ // Ask for description (optional)
66
+ const description = await vscode.window.showInputBox({
67
+ prompt: 'Description (optional)',
68
+ placeHolder: 'Enter a description for the feature'
69
+ })
70
+
71
+ // Create the feature file
72
+ const root = workspaceFolders[0].uri.fsPath
73
+ const kanbanConfig = readConfig(root)
74
+ const featuresDir = path.join(root, kanbanConfig.featuresDirectory)
75
+ await vscode.workspace.fs.createDirectory(vscode.Uri.file(featuresDir))
76
+ await ensureStatusSubfolders(featuresDir, kanbanConfig.columns.map(c => c.id))
77
+
78
+ const numericId = allocateCardId(root)
79
+ const filename = generateFeatureFilename(numericId, title)
80
+ const now = new Date().toISOString()
81
+
82
+ // Build content with title as first # heading
83
+ const content = `# ${title}${description ? '\n\n' + description : ''}`
84
+
85
+ const feature: Feature = {
86
+ id: String(numericId),
87
+ status,
88
+ priority,
89
+ assignee: null,
90
+ dueDate: null,
91
+ created: now,
92
+ modified: now,
93
+ completedAt: status === 'done' ? now : null,
94
+ labels: [],
95
+ attachments: [],
96
+ order: generateKeyBetween(null, null),
97
+ content,
98
+ filePath: getFeatureFilePath(featuresDir, status, filename)
99
+ }
100
+
101
+ const fileContent = serializeFeature(feature)
102
+ await vscode.workspace.fs.writeFile(vscode.Uri.file(feature.filePath), new TextEncoder().encode(fileContent))
103
+
104
+ // Open the created file
105
+ const document = await vscode.workspace.openTextDocument(feature.filePath)
106
+ await vscode.window.showTextDocument(document)
107
+
108
+ vscode.window.showInformationMessage(`Created feature: ${title}`)
109
+ }
110
+
111
+ function serializeFeature(feature: Feature): string {
112
+ const frontmatter = [
113
+ '---',
114
+ `id: "${feature.id}"`,
115
+ `status: "${feature.status}"`,
116
+ `priority: "${feature.priority}"`,
117
+ `assignee: ${feature.assignee ? `"${feature.assignee}"` : 'null'}`,
118
+ `dueDate: ${feature.dueDate ? `"${feature.dueDate}"` : 'null'}`,
119
+ `created: "${feature.created}"`,
120
+ `modified: "${feature.modified}"`,
121
+ `completedAt: ${feature.completedAt ? `"${feature.completedAt}"` : 'null'}`,
122
+ `labels: [${feature.labels.map(l => `"${l}"`).join(', ')}]`,
123
+ `order: "${feature.order}"`,
124
+ '---',
125
+ ''
126
+ ].join('\n')
127
+
128
+ return frontmatter + feature.content
129
+ }
130
+
131
+ let standaloneServer: ReturnType<typeof startServer> | undefined
132
+ let statusBarItem: vscode.StatusBarItem | undefined
133
+
134
+ function findFreePort(startPort: number, maxAttempts = 10): Promise<number> {
135
+ return new Promise((resolve, reject) => {
136
+ let attempt = 0
137
+ function tryPort(port: number) {
138
+ const server = net.createServer()
139
+ server.once('error', () => {
140
+ attempt++
141
+ if (attempt >= maxAttempts) {
142
+ reject(new Error('No free port found'))
143
+ } else {
144
+ tryPort(port + 1)
145
+ }
146
+ })
147
+ server.once('listening', () => {
148
+ server.close(() => resolve(port))
149
+ })
150
+ server.listen(port)
151
+ }
152
+ tryPort(startPort)
153
+ })
154
+ }
155
+
156
+ export function activate(context: vscode.ExtensionContext) {
157
+ // Start standalone HTTP server so the board is accessible in a browser
158
+ const workspaceFolders = vscode.workspace.workspaceFolders
159
+ if (workspaceFolders) {
160
+ const root = workspaceFolders[0].uri.fsPath
161
+ const kanbanConfig = readConfig(root)
162
+ const featuresDir = path.join(root, kanbanConfig.featuresDirectory)
163
+ const webviewDir = path.join(context.extensionPath, 'dist', 'standalone-webview')
164
+
165
+ findFreePort(3464).then(port => {
166
+ standaloneServer = startServer(featuresDir, port, webviewDir)
167
+ standaloneServer.on('error', () => {
168
+ standaloneServer = undefined
169
+ })
170
+
171
+ statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 50)
172
+ statusBarItem.text = `kanban:${port}`
173
+ statusBarItem.tooltip = `Kanban board running at http://localhost:${port}`
174
+ statusBarItem.command = {
175
+ title: 'Open Kanban in Browser',
176
+ command: 'vscode.open',
177
+ arguments: [vscode.Uri.parse(`http://localhost:${port}`)]
178
+ }
179
+ statusBarItem.show()
180
+ context.subscriptions.push(statusBarItem)
181
+ }).catch(() => {
182
+ // No free port found — non-critical, skip
183
+ })
184
+ }
185
+
186
+ // Sidebar webview in the activity bar
187
+ const sidebarProvider = new SidebarViewProvider(context.extensionUri, context)
188
+ context.subscriptions.push(
189
+ vscode.window.registerWebviewViewProvider(SidebarViewProvider.viewType, sidebarProvider)
190
+ )
191
+
192
+ context.subscriptions.push(
193
+ vscode.commands.registerCommand('kanban-lite.open', () => {
194
+ const wasOpen = !!KanbanPanel.currentPanel
195
+ KanbanPanel.createOrShow(context.extensionUri, context)
196
+ if (!wasOpen && KanbanPanel.currentPanel) {
197
+ sidebarProvider.setBoardOpen(true)
198
+ KanbanPanel.currentPanel.onDispose(() => {
199
+ sidebarProvider.setBoardOpen(false)
200
+ })
201
+ }
202
+ })
203
+ )
204
+
205
+ context.subscriptions.push(
206
+ vscode.commands.registerCommand('kanban-lite.addFeature', () => {
207
+ createFeatureFromPrompts()
208
+ })
209
+ )
210
+
211
+ // If a panel already exists, revive it
212
+ if (vscode.window.registerWebviewPanelSerializer) {
213
+ vscode.window.registerWebviewPanelSerializer(KanbanPanel.viewType, {
214
+ async deserializeWebviewPanel(webviewPanel: vscode.WebviewPanel) {
215
+ KanbanPanel.revive(webviewPanel, context.extensionUri, context)
216
+ sidebarProvider.setBoardOpen(true)
217
+ KanbanPanel.currentPanel?.onDispose(() => {
218
+ sidebarProvider.setBoardOpen(false)
219
+ })
220
+ }
221
+ })
222
+ }
223
+ }
224
+
225
+ export function deactivate() {
226
+ if (standaloneServer) {
227
+ standaloneServer.close()
228
+ standaloneServer = undefined
229
+ }
230
+ if (statusBarItem) {
231
+ statusBarItem.dispose()
232
+ statusBarItem = undefined
233
+ }
234
+ }