backlog.md 0.1.0 → 0.1.1
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/bin/backlog-darwin-arm64/backlog +0 -0
- package/bin/backlog-darwin-x64/backlog +0 -0
- package/bin/backlog-linux-arm64/backlog +0 -0
- package/{cli → bin/backlog-linux-x64}/backlog +0 -0
- package/bin/backlog-win32-x64/backlog.exe +0 -0
- package/cli.js +62 -0
- package/package.json +57 -46
- package/.backlog/archive/drafts/readme.md +0 -3
- package/.backlog/archive/drafts/task-41 - temporary-test-task.md +0 -13
- package/.backlog/archive/readme.md +0 -6
- package/.backlog/archive/tasks/readme.md +0 -3
- package/.backlog/archive/tasks/task-41 - cli-migrate-terminal-ui-to-bblessed.md +0 -14
- package/.backlog/config.yml +0 -7
- package/.backlog/decisions/readme.md +0 -7
- package/.backlog/docs/readme.md +0 -20
- package/.backlog/drafts/readme.md +0 -3
- package/.backlog/drafts/task-26 - docs-add-board-export-step-to-agent-dod.md +0 -21
- package/.backlog/drafts/task-28 - add-code-of-conduct.md +0 -20
- package/.backlog/drafts/task-30 - create-changelog.md +0 -19
- package/.backlog/milestones/m-0 - project-setup.md +0 -8
- package/.backlog/milestones/m-1 - cli.md +0 -8
- package/.backlog/milestones/m-2 - cli-kanban.md +0 -8
- package/.backlog/milestones/m-3 - gui.md +0 -8
- package/.backlog/milestones/m-4 - gui-kanban.md +0 -8
- package/.backlog/milestones/m-5 - gui-advanced.md +0 -12
- package/.backlog/milestones/readme.md +0 -3
- package/.backlog/readme.md +0 -5
- package/.backlog/tasks/readme.md +0 -37
- package/.backlog/tasks/task-1 - cli-setup-core-project.md +0 -23
- package/.backlog/tasks/task-10 - gui-init-packaging.md +0 -23
- package/.backlog/tasks/task-11 - gui-kanban-board.md +0 -26
- package/.backlog/tasks/task-12 - gui-advanced.md +0 -25
- package/.backlog/tasks/task-13 - cli-add-agent-instruction-prompt.md +0 -53
- package/.backlog/tasks/task-13.1 - cli-agent-instruction-file-selection.md +0 -40
- package/.backlog/tasks/task-14 - gui-introduction-screens.md +0 -21
- package/.backlog/tasks/task-15 - improve-tasks-readme-with-generic-example-and-cli-reference.md +0 -20
- package/.backlog/tasks/task-16 - improve-docs-readme-with-generic-example-and-cli-reference.md +0 -20
- package/.backlog/tasks/task-17 - improve-drafts-readme-with-generic-example-and-cli-reference.md +0 -20
- package/.backlog/tasks/task-18 - improve-decisions-readme-with-generic-example-and-cli-reference.md +0 -20
- package/.backlog/tasks/task-19 - cli-fix-default-task-status-and-remove-draft-from-statuses.md +0 -55
- package/.backlog/tasks/task-2 - cli-core-logic-library.md +0 -28
- package/.backlog/tasks/task-20 - add-agent-guideline-to-mark-tasks-in-progress-on-start.md +0 -32
- package/.backlog/tasks/task-21 - kanban-board-vertical-layout.md +0 -31
- package/.backlog/tasks/task-22 - cli-prevent-double-dash-in-task-filenames.md +0 -24
- package/.backlog/tasks/task-23 - cli-kanban-board-order-tasks-by-id-asc.md +0 -30
- package/.backlog/tasks/task-24 - handle-subtasks-in-the-kanban-view.md +0 -38
- package/.backlog/tasks/task-24.1 - cli-kanban-board-milestone-view.md +0 -19
- package/.backlog/tasks/task-25 - cli-export-kanban-board-to-readme.md +0 -28
- package/.backlog/tasks/task-27 - add-contributing-guidelines.md +0 -27
- package/.backlog/tasks/task-29 - add-github-templates.md +0 -28
- package/.backlog/tasks/task-3 - cli-implement-backlog-init.md +0 -63
- package/.backlog/tasks/task-31 - update-readme-for-open-source.md +0 -26
- package/.backlog/tasks/task-32 - cli-hide-empty-'no-status'-column.md +0 -31
- package/.backlog/tasks/task-33 - cli-export-milestones-board-as-roadmap.md +0 -20
- package/.backlog/tasks/task-34 - split-readme.md-for-users-and-contributors.md +0 -26
- package/.backlog/tasks/task-35 - finalize-package.json-metadata-for-publishing.md +0 -24
- package/.backlog/tasks/task-36 - cli-prompt-for-project-name-in-init.md +0 -24
- package/.backlog/tasks/task-37 - cli-board-view-open-tasks-in-ide.md +0 -19
- package/.backlog/tasks/task-38 - cli-improved-agent-selection-for-init.md +0 -25
- package/.backlog/tasks/task-39 - cli-fix-empty-agent-instruction-files-on-init.md +0 -31
- package/.backlog/tasks/task-4 - cli-task-management-commands.md +0 -28
- package/.backlog/tasks/task-4.1 - cli-task-create.md +0 -27
- package/.backlog/tasks/task-4.10 - use-cli-to-mark-tasks-done.md +0 -51
- package/.backlog/tasks/task-4.11 - docs-add-definition-of-done-to-agent-guidelines.md +0 -23
- package/.backlog/tasks/task-4.12 - cli-handle-task-id-conflicts-across-branches.md +0 -53
- package/.backlog/tasks/task-4.13 - cli-fix-config-command-local-global-logic.md +0 -58
- package/.backlog/tasks/task-4.2 - cli-task-list-view.md +0 -25
- package/.backlog/tasks/task-4.3 - cli-task-edit.md +0 -24
- package/.backlog/tasks/task-4.4 - cli-task-archive-transition.md +0 -27
- package/.backlog/tasks/task-4.5 - cli-init-prompts-for-reporter-name-and-global-local-config.md +0 -28
- package/.backlog/tasks/task-4.6 - cli-add-empty-assignee-array-field-for-new-tasks.md +0 -35
- package/.backlog/tasks/task-4.7 - cli-parse-unquoted-created_date.md +0 -40
- package/.backlog/tasks/task-4.8 - cli-enforce-description-header.md +0 -48
- package/.backlog/tasks/task-4.9 - cli-normalize-task-id-inputs.md +0 -66
- package/.backlog/tasks/task-40 - cli-board-command-defaults-to-view.md +0 -38
- package/.backlog/tasks/task-41 - cli-migrate-terminal-ui-to-bblessed.md +0 -93
- package/.backlog/tasks/task-41.1 - cli-bblessed-init-wizard.md +0 -42
- package/.backlog/tasks/task-41.2 - cli-bblessed-task-view.md +0 -44
- package/.backlog/tasks/task-41.3 - cli-bblessed-doc-view.md +0 -45
- package/.backlog/tasks/task-41.4 - cli-bblessed-board-view.md +0 -49
- package/.backlog/tasks/task-41.5 - cli-audit-remaining-ui-for-bblessed.md +0 -55
- package/.backlog/tasks/task-42 - visual-hierarchy.md +0 -54
- package/.backlog/tasks/task-43 - remove-duplicate-acceptance-criteria-and-style-metadata.md +0 -56
- package/.backlog/tasks/task-44 - checklist-alignment.md +0 -24
- package/.backlog/tasks/task-45 - safe-line-wrapping.md +0 -23
- package/.backlog/tasks/task-46 - split-pane-layout.md +0 -24
- package/.backlog/tasks/task-47 - sticky-header-in-detail-view.md +0 -43
- package/.backlog/tasks/task-48 - footer-hint-line.md +0 -21
- package/.backlog/tasks/task-49 - status-styling.md +0 -53
- package/.backlog/tasks/task-5 - cli-docs-decisions.md +0 -57
- package/.backlog/tasks/task-50 - borders-&-padding.md +0 -22
- package/.backlog/tasks/task-51 - code-path-styling.md +0 -23
- package/.backlog/tasks/task-52 - cli-filter-tasks-list-by-status-or-assignee.md +0 -29
- package/.backlog/tasks/task-6 - cli-packaging.md +0 -65
- package/.backlog/tasks/task-6.1 - cli-local-installation-support-for-bunx-npx.md +0 -49
- package/.backlog/tasks/task-6.2 - cli-github-actions-for-build-&-publish.md +0 -64
- package/.backlog/tasks/task-7 - cli-kanban-view.md +0 -60
- package/.backlog/tasks/task-7.1 - cli-kanban-board-detect-remote-task-status.md +0 -62
- package/.backlog/tasks/task-8 - gui-project-setup.md +0 -21
- package/.backlog/tasks/task-9 - gui-task-crud.md +0 -24
- package/.cursorrules +0 -223
- package/.gitattributes +0 -2
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -25
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -15
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -8
- package/.github/workflows/ci.yml +0 -36
- package/.husky/pre-commit +0 -1
- package/AGENTS.md +0 -65
- package/CLAUDE.md +0 -87
- package/CONTRIBUTING.md +0 -19
- package/DEVELOPMENT.md +0 -37
- package/biome.json +0 -31
- package/bun.lock +0 -152
- package/cli/.cursorrules-xh86jabm.md +0 -82
- package/cli/AGENTS-xh86jabm.md +0 -82
- package/cli/CLAUDE-xh86jabm.md +0 -82
- package/cli/cli.js +0 -19622
- package/cli/index.js +0 -2
- package/docs/npm-publishing.md +0 -69
- package/scripts/build.js +0 -73
- package/src/agent-instructions.ts +0 -54
- package/src/board.ts +0 -263
- package/src/cli.ts +0 -806
- package/src/constants/index.ts +0 -48
- package/src/core/backlog.ts +0 -183
- package/src/core/remote-tasks.ts +0 -168
- package/src/file-system/operations.ts +0 -515
- package/src/git/operations.ts +0 -189
- package/src/guidelines/.cursorrules.md +0 -82
- package/src/guidelines/AGENTS.md +0 -82
- package/src/guidelines/CLAUDE.md +0 -82
- package/src/guidelines/index.ts +0 -7
- package/src/index.ts +0 -30
- package/src/markdown/parser.ts +0 -145
- package/src/markdown/serializer.ts +0 -71
- package/src/test/agent-instructions.test.ts +0 -62
- package/src/test/board.test.ts +0 -291
- package/src/test/build.test.ts +0 -28
- package/src/test/checklist.test.ts +0 -273
- package/src/test/cli.test.ts +0 -1300
- package/src/test/code-path.test.ts +0 -204
- package/src/test/core.test.ts +0 -330
- package/src/test/filesystem.test.ts +0 -435
- package/src/test/git.test.ts +0 -26
- package/src/test/heading.test.ts +0 -102
- package/src/test/line-wrapping.test.ts +0 -252
- package/src/test/local-install.test.ts +0 -34
- package/src/test/markdown.test.ts +0 -526
- package/src/test/parallel-loading.test.ts +0 -160
- package/src/test/parent-id-normalization.test.ts +0 -48
- package/src/test/remote-id-conflict.test.ts +0 -60
- package/src/test/status-icon.test.ts +0 -93
- package/src/types/blessed.d.ts +0 -14
- package/src/types/index.ts +0 -55
- package/src/types/raw.d.ts +0 -4
- package/src/ui/board.ts +0 -322
- package/src/ui/checklist.ts +0 -103
- package/src/ui/code-path.ts +0 -113
- package/src/ui/heading.ts +0 -121
- package/src/ui/loading.ts +0 -216
- package/src/ui/status-icon.ts +0 -53
- package/src/ui/task-list.ts +0 -168
- package/src/ui/task-viewer.ts +0 -640
- package/src/ui/tui.ts +0 -301
- package/tsconfig.json +0 -26
package/src/ui/task-viewer.ts
DELETED
|
@@ -1,640 +0,0 @@
|
|
|
1
|
-
/* Enhanced task viewer for displaying task details in a structured format */
|
|
2
|
-
|
|
3
|
-
import { createRequire } from "node:module";
|
|
4
|
-
import { stdin as input, stdout as output } from "node:process";
|
|
5
|
-
import { Core } from "../core/backlog.ts";
|
|
6
|
-
import { parseMarkdown } from "../markdown/parser.ts";
|
|
7
|
-
import type { Task } from "../types/index.ts";
|
|
8
|
-
import { formatChecklistItem, parseCheckboxLine } from "./checklist.ts";
|
|
9
|
-
import { transformCodePaths, transformCodePathsPlain } from "./code-path.ts";
|
|
10
|
-
import { formatHeading } from "./heading.ts";
|
|
11
|
-
import { formatStatusWithIcon, getStatusColor, getStatusIcon } from "./status-icon.ts";
|
|
12
|
-
import { TaskList } from "./task-list.ts";
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Extract only the Description section content from markdown, avoiding duplication
|
|
16
|
-
*/
|
|
17
|
-
function extractDescriptionSection(content: string): string | null {
|
|
18
|
-
if (!content) return null;
|
|
19
|
-
|
|
20
|
-
// Look for ## Description section
|
|
21
|
-
const regex = /## Description\s*\n([\s\S]*?)(?=\n## |$)/i;
|
|
22
|
-
const match = content.match(regex);
|
|
23
|
-
return match?.[1]?.trim() || null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Extract checkbox lines from Acceptance Criteria section for display
|
|
28
|
-
*/
|
|
29
|
-
function extractAcceptanceCriteriaWithCheckboxes(content: string): string[] {
|
|
30
|
-
if (!content) return [];
|
|
31
|
-
|
|
32
|
-
// Look for ## Acceptance Criteria section
|
|
33
|
-
const regex = /## Acceptance Criteria\s*\n([\s\S]*?)(?=\n## |$)/i;
|
|
34
|
-
const match = content.match(regex);
|
|
35
|
-
if (!match) return [];
|
|
36
|
-
|
|
37
|
-
return match[1]
|
|
38
|
-
.split("\n")
|
|
39
|
-
.map((line) => line.trim())
|
|
40
|
-
.filter((line) => line.startsWith("- [ ]") || line.startsWith("- [x]"));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Load blessed dynamically
|
|
44
|
-
// biome-ignore lint/suspicious/noExplicitAny: blessed is dynamically loaded
|
|
45
|
-
async function loadBlessed(): Promise<any | null> {
|
|
46
|
-
if (output.isTTY === false) return null;
|
|
47
|
-
try {
|
|
48
|
-
const require = createRequire(import.meta.url);
|
|
49
|
-
const blessed = require("blessed");
|
|
50
|
-
return blessed;
|
|
51
|
-
} catch {
|
|
52
|
-
try {
|
|
53
|
-
// biome-ignore lint/suspicious/noExplicitAny: dynamic import
|
|
54
|
-
const mod = (await import("blessed")) as any;
|
|
55
|
-
return mod.default ?? mod;
|
|
56
|
-
} catch {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Display task details in a split-pane UI with task list on left and detail on right
|
|
64
|
-
*/
|
|
65
|
-
export async function viewTaskEnhanced(task: Task, content: string): Promise<void> {
|
|
66
|
-
const blessed = await loadBlessed();
|
|
67
|
-
if (!blessed) {
|
|
68
|
-
// Fallback to formatted plain text
|
|
69
|
-
console.log(formatTaskPlainText(task, content));
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Get project root and load all tasks
|
|
74
|
-
const cwd = process.cwd();
|
|
75
|
-
const core = new Core(cwd);
|
|
76
|
-
const allTasks = await core.filesystem.listTasks();
|
|
77
|
-
|
|
78
|
-
// Find the initial selected task index
|
|
79
|
-
const initialIndex = allTasks.findIndex((t) => t.id === task.id);
|
|
80
|
-
let currentSelectedTask = task;
|
|
81
|
-
let currentSelectedContent = content;
|
|
82
|
-
|
|
83
|
-
const screen = blessed.screen({
|
|
84
|
-
smartCSR: true,
|
|
85
|
-
title: "Backlog Tasks",
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// Main container using grid layout
|
|
89
|
-
const container = blessed.box({
|
|
90
|
-
parent: screen,
|
|
91
|
-
width: "100%",
|
|
92
|
-
height: "100%",
|
|
93
|
-
autoPadding: true,
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// Task list pane (left 40%)
|
|
97
|
-
const taskListPane = blessed.box({
|
|
98
|
-
parent: container,
|
|
99
|
-
top: 0,
|
|
100
|
-
left: 0,
|
|
101
|
-
width: "40%",
|
|
102
|
-
height: "100%-1", // Leave space for help bar
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// Detail pane (right 60%) with border and padding
|
|
106
|
-
const detailPane = blessed.box({
|
|
107
|
-
parent: container,
|
|
108
|
-
top: 0,
|
|
109
|
-
left: "40%",
|
|
110
|
-
// left: taskListPane.width,
|
|
111
|
-
// left : "0%",
|
|
112
|
-
width: "60%",
|
|
113
|
-
height: "100%-1", // Leave space for help bar
|
|
114
|
-
// border: {
|
|
115
|
-
// type: "line",
|
|
116
|
-
// },
|
|
117
|
-
// padding: {
|
|
118
|
-
// left: 1,
|
|
119
|
-
// },
|
|
120
|
-
// style: {
|
|
121
|
-
// border: { fg: "gray" },
|
|
122
|
-
// },
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// Create task list
|
|
126
|
-
const taskList = new TaskList({
|
|
127
|
-
parent: taskListPane,
|
|
128
|
-
tasks: allTasks,
|
|
129
|
-
selectedIndex: Math.max(0, initialIndex),
|
|
130
|
-
onSelect: (selectedTask: Task, index: number) => {
|
|
131
|
-
currentSelectedTask = selectedTask;
|
|
132
|
-
// Load the content for the selected task asynchronously
|
|
133
|
-
(async () => {
|
|
134
|
-
try {
|
|
135
|
-
const files = await Array.fromAsync(new Bun.Glob("*.md").scan({ cwd: core.filesystem.tasksDir }));
|
|
136
|
-
const normalizedId = selectedTask.id.startsWith("task-") ? selectedTask.id : `task-${selectedTask.id}`;
|
|
137
|
-
const taskFile = files.find((f) => f.startsWith(`${normalizedId} -`));
|
|
138
|
-
|
|
139
|
-
if (taskFile) {
|
|
140
|
-
const filePath = `${core.filesystem.tasksDir}/${taskFile}`;
|
|
141
|
-
currentSelectedContent = await Bun.file(filePath).text();
|
|
142
|
-
} else {
|
|
143
|
-
currentSelectedContent = "";
|
|
144
|
-
}
|
|
145
|
-
} catch (error) {
|
|
146
|
-
currentSelectedContent = "";
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Refresh the detail pane
|
|
150
|
-
refreshDetailPane();
|
|
151
|
-
})();
|
|
152
|
-
},
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
// Detail pane components
|
|
156
|
-
// biome-ignore lint/suspicious/noExplicitAny: blessed components don't have proper types
|
|
157
|
-
let headerBox: any;
|
|
158
|
-
// biome-ignore lint/suspicious/noExplicitAny: blessed components don't have proper types
|
|
159
|
-
let metadataBox: any;
|
|
160
|
-
// biome-ignore lint/suspicious/noExplicitAny: blessed components don't have proper types
|
|
161
|
-
let descriptionBox: any;
|
|
162
|
-
// biome-ignore lint/suspicious/noExplicitAny: blessed components don't have proper types
|
|
163
|
-
let bottomBox: any;
|
|
164
|
-
|
|
165
|
-
function refreshDetailPane() {
|
|
166
|
-
// Clear existing detail pane content
|
|
167
|
-
if (headerBox) headerBox.destroy();
|
|
168
|
-
if (metadataBox) metadataBox.destroy();
|
|
169
|
-
if (descriptionBox) descriptionBox.destroy();
|
|
170
|
-
if (bottomBox) bottomBox.destroy();
|
|
171
|
-
|
|
172
|
-
// Update screen title
|
|
173
|
-
screen.title = `Task ${currentSelectedTask.id} - ${currentSelectedTask.title}`;
|
|
174
|
-
|
|
175
|
-
// Fixed header section with task ID, title, status, date, and tags
|
|
176
|
-
headerBox = blessed.box({
|
|
177
|
-
parent: detailPane,
|
|
178
|
-
top: 0,
|
|
179
|
-
left: 0,
|
|
180
|
-
width: "100%-2", // Account for border
|
|
181
|
-
height: 3,
|
|
182
|
-
border: "line",
|
|
183
|
-
style: {
|
|
184
|
-
border: { fg: "blue" },
|
|
185
|
-
},
|
|
186
|
-
tags: true,
|
|
187
|
-
wrap: true,
|
|
188
|
-
scrollable: false, // Header should never scroll
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
// Format header content with key metadata
|
|
192
|
-
const headerContent = [];
|
|
193
|
-
headerContent.push(` {bold}{blue-fg}${currentSelectedTask.id}{/blue-fg}{/bold} - ${currentSelectedTask.title}`);
|
|
194
|
-
|
|
195
|
-
// Second line with status, date, and assignee
|
|
196
|
-
const statusLine = [];
|
|
197
|
-
statusLine.push(
|
|
198
|
-
`{${getStatusColor(currentSelectedTask.status)}-fg}${formatStatusWithIcon(currentSelectedTask.status)}{/}`,
|
|
199
|
-
);
|
|
200
|
-
statusLine.push(`{gray-fg}${currentSelectedTask.createdDate}{/}`);
|
|
201
|
-
|
|
202
|
-
if (currentSelectedTask.assignee?.length) {
|
|
203
|
-
const assigneeList = currentSelectedTask.assignee.map((a) => (a.startsWith("@") ? a : `@${a}`)).join(", ");
|
|
204
|
-
statusLine.push(`{cyan-fg}${assigneeList}{/}`);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Add labels to header if they exist
|
|
208
|
-
if (currentSelectedTask.labels?.length) {
|
|
209
|
-
statusLine.push(`${currentSelectedTask.labels.map((l) => `{yellow-fg}[${l}]{/}`).join(" ")}`);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
headerContent.push(` ${statusLine.join(" • ")}`);
|
|
213
|
-
|
|
214
|
-
headerBox.setContent(headerContent.join("\n"));
|
|
215
|
-
|
|
216
|
-
// Scrollable body container beneath the header
|
|
217
|
-
const bodyContainer = blessed.box({
|
|
218
|
-
parent: detailPane,
|
|
219
|
-
top: 3, // Start below the fixed header
|
|
220
|
-
left: 0,
|
|
221
|
-
width: "100%-2", // Account for border
|
|
222
|
-
height: "100%-4", // Fill remaining space below header
|
|
223
|
-
scrollable: true,
|
|
224
|
-
alwaysScroll: true,
|
|
225
|
-
keys: true,
|
|
226
|
-
mouse: true,
|
|
227
|
-
tags: true,
|
|
228
|
-
wrap: true,
|
|
229
|
-
padding: { left: 1, right: 1, top: 1 },
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
// Build the scrollable body content
|
|
233
|
-
const bodyContent = [];
|
|
234
|
-
|
|
235
|
-
// Add additional metadata section
|
|
236
|
-
if (
|
|
237
|
-
currentSelectedTask.reporter ||
|
|
238
|
-
currentSelectedTask.updatedDate ||
|
|
239
|
-
currentSelectedTask.milestone ||
|
|
240
|
-
currentSelectedTask.parentTaskId ||
|
|
241
|
-
currentSelectedTask.subtasks?.length ||
|
|
242
|
-
currentSelectedTask.dependencies?.length
|
|
243
|
-
) {
|
|
244
|
-
bodyContent.push(formatHeading("Details", 2));
|
|
245
|
-
|
|
246
|
-
const metadata = [];
|
|
247
|
-
if (currentSelectedTask.reporter) {
|
|
248
|
-
const reporterText = currentSelectedTask.reporter.startsWith("@")
|
|
249
|
-
? currentSelectedTask.reporter
|
|
250
|
-
: `@${currentSelectedTask.reporter}`;
|
|
251
|
-
metadata.push(`{bold}Reporter:{/bold} {cyan-fg}${reporterText}{/}`);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (currentSelectedTask.updatedDate) {
|
|
255
|
-
metadata.push(`{bold}Updated:{/bold} ${currentSelectedTask.updatedDate}`);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (currentSelectedTask.milestone) {
|
|
259
|
-
metadata.push(`{bold}Milestone:{/bold} {magenta-fg}${currentSelectedTask.milestone}{/}`);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (currentSelectedTask.parentTaskId) {
|
|
263
|
-
metadata.push(`{bold}Parent:{/bold} {blue-fg}${currentSelectedTask.parentTaskId}{/}`);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (currentSelectedTask.subtasks?.length) {
|
|
267
|
-
metadata.push(
|
|
268
|
-
`{bold}Subtasks:{/bold} ${currentSelectedTask.subtasks.length} task${currentSelectedTask.subtasks.length > 1 ? "s" : ""}`,
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if (currentSelectedTask.dependencies?.length) {
|
|
273
|
-
metadata.push(`{bold}Dependencies:{/bold} ${currentSelectedTask.dependencies.join(", ")}`);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
bodyContent.push(metadata.join("\n"));
|
|
277
|
-
bodyContent.push("");
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Description section
|
|
281
|
-
bodyContent.push(formatHeading("Description", 2));
|
|
282
|
-
// Extract only the Description section content, not the full markdown
|
|
283
|
-
const extractedDescription = extractDescriptionSection(currentSelectedTask.description);
|
|
284
|
-
const descriptionContent = extractedDescription
|
|
285
|
-
? transformCodePaths(extractedDescription)
|
|
286
|
-
: "{gray-fg}No description provided{/}";
|
|
287
|
-
bodyContent.push(descriptionContent);
|
|
288
|
-
bodyContent.push("");
|
|
289
|
-
|
|
290
|
-
// Acceptance criteria section
|
|
291
|
-
bodyContent.push(formatHeading("Acceptance Criteria", 2));
|
|
292
|
-
// Extract checkbox lines from raw content to preserve checkbox state
|
|
293
|
-
const checkboxLines = extractAcceptanceCriteriaWithCheckboxes(currentSelectedContent);
|
|
294
|
-
if (checkboxLines.length > 0) {
|
|
295
|
-
const formattedCriteria = checkboxLines.map((line) => {
|
|
296
|
-
const checkboxItem = parseCheckboxLine(line);
|
|
297
|
-
if (checkboxItem) {
|
|
298
|
-
// Use nice Unicode symbols for checkboxes in TUI
|
|
299
|
-
return formatChecklistItem(checkboxItem, {
|
|
300
|
-
padding: " ",
|
|
301
|
-
checkedSymbol: "{green-fg}✓{/}",
|
|
302
|
-
uncheckedSymbol: "{gray-fg}○{/}",
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
// Handle non-checkbox lines
|
|
306
|
-
return ` ${line}`;
|
|
307
|
-
});
|
|
308
|
-
const criteriaContent = styleCodePaths(formattedCriteria.join("\n"));
|
|
309
|
-
bodyContent.push(criteriaContent);
|
|
310
|
-
} else if (currentSelectedTask.acceptanceCriteria?.length) {
|
|
311
|
-
// Fallback to parsed criteria if no checkboxes found in raw content
|
|
312
|
-
const criteriaContent = styleCodePaths(
|
|
313
|
-
currentSelectedTask.acceptanceCriteria.map((text) => ` • ${text}`).join("\n"),
|
|
314
|
-
);
|
|
315
|
-
bodyContent.push(criteriaContent);
|
|
316
|
-
} else {
|
|
317
|
-
bodyContent.push("{gray-fg}No acceptance criteria defined{/}");
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// Set the complete body content
|
|
321
|
-
bodyContainer.setContent(bodyContent.join("\n"));
|
|
322
|
-
|
|
323
|
-
// Store reference to body container for focus management
|
|
324
|
-
descriptionBox = bodyContainer;
|
|
325
|
-
|
|
326
|
-
screen.render();
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
await taskList.create({
|
|
330
|
-
parent: taskListPane,
|
|
331
|
-
tasks: allTasks,
|
|
332
|
-
selectedIndex: Math.max(0, initialIndex),
|
|
333
|
-
width: "100%",
|
|
334
|
-
height: "100%",
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
// Initial render of detail pane
|
|
338
|
-
refreshDetailPane();
|
|
339
|
-
|
|
340
|
-
return new Promise<void>((resolve) => {
|
|
341
|
-
// Footer hint line
|
|
342
|
-
const helpBar = blessed.box({
|
|
343
|
-
parent: screen,
|
|
344
|
-
bottom: 0,
|
|
345
|
-
left: 0,
|
|
346
|
-
width: "100%",
|
|
347
|
-
height: 1,
|
|
348
|
-
border: "line",
|
|
349
|
-
content: " ↑/↓ navigate · Tab switch pane · ←/→ scroll · q/Esc quit ",
|
|
350
|
-
style: {
|
|
351
|
-
fg: "gray",
|
|
352
|
-
border: { fg: "gray" },
|
|
353
|
-
},
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
// Focus management
|
|
357
|
-
const focusableElements = [taskList.getListBox(), descriptionBox];
|
|
358
|
-
let focusIndex = 0; // Start with task list
|
|
359
|
-
focusableElements[focusIndex].focus();
|
|
360
|
-
|
|
361
|
-
// Tab navigation between panes
|
|
362
|
-
screen.key(["tab"], () => {
|
|
363
|
-
focusIndex = (focusIndex + 1) % focusableElements.length;
|
|
364
|
-
focusableElements[focusIndex].focus();
|
|
365
|
-
screen.render();
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
screen.key(["S-tab"], () => {
|
|
369
|
-
focusIndex = (focusIndex - 1 + focusableElements.length) % focusableElements.length;
|
|
370
|
-
focusableElements[focusIndex].focus();
|
|
371
|
-
screen.render();
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
// Exit keys
|
|
375
|
-
screen.key(["escape", "q", "C-c"], () => {
|
|
376
|
-
screen.destroy();
|
|
377
|
-
resolve();
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
screen.render();
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* Generate enhanced detail content structure (reusable)
|
|
386
|
-
*/
|
|
387
|
-
function generateDetailContent(task: Task, rawContent = ""): { headerContent: string[]; bodyContent: string[] } {
|
|
388
|
-
// Format header content with key metadata
|
|
389
|
-
const headerContent = [];
|
|
390
|
-
headerContent.push(` {bold}{blue-fg}${task.id}{/blue-fg}{/bold} - ${task.title}`);
|
|
391
|
-
|
|
392
|
-
// Second line with status, date, and assignee
|
|
393
|
-
const statusLine = [];
|
|
394
|
-
statusLine.push(`{${getStatusColor(task.status)}-fg}${formatStatusWithIcon(task.status)}{/}`);
|
|
395
|
-
statusLine.push(`{gray-fg}${task.createdDate}{/}`);
|
|
396
|
-
|
|
397
|
-
if (task.assignee?.length) {
|
|
398
|
-
const assigneeList = task.assignee.map((a) => (a.startsWith("@") ? a : `@${a}`)).join(", ");
|
|
399
|
-
statusLine.push(`{cyan-fg}${assigneeList}{/}`);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// Add labels to header if they exist
|
|
403
|
-
if (task.labels?.length) {
|
|
404
|
-
statusLine.push(`${task.labels.map((l) => `{yellow-fg}[${l}]{/}`).join(" ")}`);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
headerContent.push(` ${statusLine.join(" • ")}`);
|
|
408
|
-
|
|
409
|
-
// Build the scrollable body content
|
|
410
|
-
const bodyContent = [];
|
|
411
|
-
|
|
412
|
-
// Add additional metadata section
|
|
413
|
-
if (
|
|
414
|
-
task.reporter ||
|
|
415
|
-
task.updatedDate ||
|
|
416
|
-
task.milestone ||
|
|
417
|
-
task.parentTaskId ||
|
|
418
|
-
task.subtasks?.length ||
|
|
419
|
-
task.dependencies?.length
|
|
420
|
-
) {
|
|
421
|
-
bodyContent.push(formatHeading("Details", 2));
|
|
422
|
-
|
|
423
|
-
const metadata = [];
|
|
424
|
-
if (task.reporter) {
|
|
425
|
-
const reporterText = task.reporter.startsWith("@") ? task.reporter : `@${task.reporter}`;
|
|
426
|
-
metadata.push(`{bold}Reporter:{/bold} {cyan-fg}${reporterText}{/}`);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
if (task.updatedDate) {
|
|
430
|
-
metadata.push(`{bold}Updated:{/bold} ${task.updatedDate}`);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
if (task.milestone) {
|
|
434
|
-
metadata.push(`{bold}Milestone:{/bold} {magenta-fg}${task.milestone}{/}`);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
if (task.parentTaskId) {
|
|
438
|
-
metadata.push(`{bold}Parent:{/bold} {blue-fg}${task.parentTaskId}{/}`);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
if (task.subtasks?.length) {
|
|
442
|
-
metadata.push(`{bold}Subtasks:{/bold} ${task.subtasks.length} task${task.subtasks.length > 1 ? "s" : ""}`);
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
if (task.dependencies?.length) {
|
|
446
|
-
metadata.push(`{bold}Dependencies:{/bold} ${task.dependencies.join(", ")}`);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
bodyContent.push(metadata.join("\n"));
|
|
450
|
-
bodyContent.push("");
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// Description section
|
|
454
|
-
bodyContent.push(formatHeading("Description", 2));
|
|
455
|
-
// Extract only the Description section content, not the full markdown
|
|
456
|
-
const extractedDescription = extractDescriptionSection(task.description);
|
|
457
|
-
const descriptionContent = extractedDescription
|
|
458
|
-
? transformCodePaths(extractedDescription)
|
|
459
|
-
: "{gray-fg}No description provided{/}";
|
|
460
|
-
bodyContent.push(descriptionContent);
|
|
461
|
-
bodyContent.push("");
|
|
462
|
-
|
|
463
|
-
// Acceptance criteria section
|
|
464
|
-
bodyContent.push(formatHeading("Acceptance Criteria", 2));
|
|
465
|
-
// Extract checkbox lines from raw content to preserve checkbox state
|
|
466
|
-
const checkboxLines = extractAcceptanceCriteriaWithCheckboxes(rawContent);
|
|
467
|
-
if (checkboxLines.length > 0) {
|
|
468
|
-
const formattedCriteria = checkboxLines.map((line) => {
|
|
469
|
-
const checkboxItem = parseCheckboxLine(line);
|
|
470
|
-
if (checkboxItem) {
|
|
471
|
-
// Use nice Unicode symbols for checkboxes in TUI
|
|
472
|
-
return formatChecklistItem(checkboxItem, {
|
|
473
|
-
padding: " ",
|
|
474
|
-
checkedSymbol: "{green-fg}✓{/}",
|
|
475
|
-
uncheckedSymbol: "{gray-fg}○{/}",
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
|
-
// Handle non-checkbox lines
|
|
479
|
-
return ` ${line}`;
|
|
480
|
-
});
|
|
481
|
-
const criteriaContent = styleCodePaths(formattedCriteria.join("\n"));
|
|
482
|
-
bodyContent.push(criteriaContent);
|
|
483
|
-
} else if (task.acceptanceCriteria?.length) {
|
|
484
|
-
// Fallback to parsed criteria if no checkboxes found in raw content
|
|
485
|
-
const criteriaContent = styleCodePaths(task.acceptanceCriteria.map((text) => ` • ${text}`).join("\n"));
|
|
486
|
-
bodyContent.push(criteriaContent);
|
|
487
|
-
} else {
|
|
488
|
-
bodyContent.push("{gray-fg}No acceptance criteria defined{/}");
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
return { headerContent, bodyContent };
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
/**
|
|
495
|
-
* Display task details in a popup (for board view) using enhanced detail structure
|
|
496
|
-
*/
|
|
497
|
-
// biome-ignore lint/suspicious/noExplicitAny: blessed types
|
|
498
|
-
export async function createTaskPopup(screen: any, task: Task, content: string): Promise<any> {
|
|
499
|
-
const blessed = await loadBlessed();
|
|
500
|
-
if (!blessed) return null;
|
|
501
|
-
|
|
502
|
-
// Create main popup first
|
|
503
|
-
const popup = blessed.box({
|
|
504
|
-
parent: screen,
|
|
505
|
-
top: "center",
|
|
506
|
-
left: "center",
|
|
507
|
-
width: "85%",
|
|
508
|
-
height: "80%",
|
|
509
|
-
border: "line",
|
|
510
|
-
style: {
|
|
511
|
-
border: { fg: "gray" },
|
|
512
|
-
},
|
|
513
|
-
keys: true,
|
|
514
|
-
tags: true,
|
|
515
|
-
autoPadding: true,
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
// Create background overlay positioned relative to popup
|
|
519
|
-
// Using offset positioning: -2 chars left/right, -1 char top/bottom
|
|
520
|
-
const background = blessed.box({
|
|
521
|
-
parent: screen,
|
|
522
|
-
top: popup.top - 1,
|
|
523
|
-
left: popup.left - 2,
|
|
524
|
-
width: popup.width + 4,
|
|
525
|
-
height: popup.height + 2,
|
|
526
|
-
style: {
|
|
527
|
-
bg: "black",
|
|
528
|
-
},
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
// Move popup to front
|
|
532
|
-
popup.setFront();
|
|
533
|
-
|
|
534
|
-
// Generate enhanced detail content
|
|
535
|
-
const { headerContent, bodyContent } = generateDetailContent(task, content);
|
|
536
|
-
|
|
537
|
-
// Fixed header section with task ID, title, status, date, and tags
|
|
538
|
-
const headerBox = blessed.box({
|
|
539
|
-
parent: popup,
|
|
540
|
-
top: 0,
|
|
541
|
-
left: 0,
|
|
542
|
-
width: "100%-2", // Account for border
|
|
543
|
-
height: 3,
|
|
544
|
-
border: "line",
|
|
545
|
-
style: {
|
|
546
|
-
border: { fg: "blue" },
|
|
547
|
-
},
|
|
548
|
-
tags: true,
|
|
549
|
-
wrap: true,
|
|
550
|
-
scrollable: false, // Header should never scroll
|
|
551
|
-
content: headerContent.join("\n"),
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
// Escape indicator
|
|
555
|
-
const escIndicator = blessed.box({
|
|
556
|
-
parent: popup,
|
|
557
|
-
content: " Esc ",
|
|
558
|
-
top: -1,
|
|
559
|
-
right: 1,
|
|
560
|
-
width: 5,
|
|
561
|
-
height: 1,
|
|
562
|
-
style: {
|
|
563
|
-
fg: "white",
|
|
564
|
-
bg: "blue",
|
|
565
|
-
},
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
// Scrollable body container beneath the header
|
|
569
|
-
const contentArea = blessed.box({
|
|
570
|
-
parent: popup,
|
|
571
|
-
top: 3, // Start below the fixed header
|
|
572
|
-
left: 0,
|
|
573
|
-
width: "100%-2", // Account for border
|
|
574
|
-
height: "100%-5", // Leave more space for bottom border
|
|
575
|
-
scrollable: true,
|
|
576
|
-
alwaysScroll: false,
|
|
577
|
-
keys: true,
|
|
578
|
-
mouse: true,
|
|
579
|
-
tags: true,
|
|
580
|
-
wrap: true,
|
|
581
|
-
padding: { left: 1, right: 1, top: 1 },
|
|
582
|
-
content: bodyContent.join("\n"),
|
|
583
|
-
});
|
|
584
|
-
|
|
585
|
-
// Set up close handler
|
|
586
|
-
const closePopup = () => {
|
|
587
|
-
background.destroy();
|
|
588
|
-
popup.destroy();
|
|
589
|
-
screen.render();
|
|
590
|
-
};
|
|
591
|
-
|
|
592
|
-
// Focus content area for scrolling
|
|
593
|
-
contentArea.focus();
|
|
594
|
-
|
|
595
|
-
return {
|
|
596
|
-
background,
|
|
597
|
-
popup,
|
|
598
|
-
contentArea,
|
|
599
|
-
close: closePopup,
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
function formatTaskPlainText(task: Task, content: string): string {
|
|
604
|
-
const lines = [];
|
|
605
|
-
lines.push(`Task ${task.id} - ${task.title}`);
|
|
606
|
-
lines.push("=".repeat(50));
|
|
607
|
-
lines.push("");
|
|
608
|
-
lines.push(`Status: ${formatStatusWithIcon(task.status)}`);
|
|
609
|
-
if (task.assignee?.length)
|
|
610
|
-
lines.push(`Assignee: ${task.assignee.map((a) => (a.startsWith("@") ? a : `@${a}`)).join(", ")}`);
|
|
611
|
-
if (task.reporter) lines.push(`Reporter: ${task.reporter.startsWith("@") ? task.reporter : `@${task.reporter}`}`);
|
|
612
|
-
lines.push(`Created: ${task.createdDate}`);
|
|
613
|
-
if (task.updatedDate) lines.push(`Updated: ${task.updatedDate}`);
|
|
614
|
-
if (task.labels?.length) lines.push(`Labels: ${task.labels.join(", ")}`);
|
|
615
|
-
if (task.milestone) lines.push(`Milestone: ${task.milestone}`);
|
|
616
|
-
if (task.parentTaskId) lines.push(`Parent: ${task.parentTaskId}`);
|
|
617
|
-
if (task.subtasks?.length) lines.push(`Subtasks: ${task.subtasks.length}`);
|
|
618
|
-
if (task.dependencies?.length) lines.push(`Dependencies: ${task.dependencies.join(", ")}`);
|
|
619
|
-
lines.push("");
|
|
620
|
-
lines.push("Description:");
|
|
621
|
-
lines.push("-".repeat(50));
|
|
622
|
-
lines.push(transformCodePathsPlain(task.description || "No description provided"));
|
|
623
|
-
lines.push("");
|
|
624
|
-
if (task.acceptanceCriteria?.length) {
|
|
625
|
-
lines.push("Acceptance Criteria:");
|
|
626
|
-
lines.push("-".repeat(50));
|
|
627
|
-
for (const c of task.acceptanceCriteria) {
|
|
628
|
-
lines.push(transformCodePathsPlain(c));
|
|
629
|
-
}
|
|
630
|
-
lines.push("");
|
|
631
|
-
}
|
|
632
|
-
lines.push("Content:");
|
|
633
|
-
lines.push("-".repeat(50));
|
|
634
|
-
lines.push(transformCodePathsPlain(content));
|
|
635
|
-
return lines.join("\n");
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
function styleCodePaths(content: string): string {
|
|
639
|
-
return transformCodePaths(content);
|
|
640
|
-
}
|