depth-first-thinking 2.1.1 → 2.1.3

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/README.md CHANGED
@@ -22,7 +22,7 @@ bun add -g depth-first-thinking
22
22
 
23
23
  ### Options
24
24
 
25
- - `dft delete <name> --yes` - Skip confirmation prompt when deleting
25
+ - `dft delete <name> -y, --yes` - Skip confirmation prompt when deleting
26
26
  - `dft tree <name> --show-status` - Show status markers (default: true)
27
27
  - `dft tree <name> --no-status` - Hide status markers
28
28
 
@@ -34,9 +34,12 @@ Running `dft` without any arguments will:
34
34
 
35
35
  ## Navigation
36
36
 
37
- - `↑` `↓` - Select task
38
- - `→` `Space` `Enter` - Enter task / view subtasks
39
- - `←` - Go back to parent
37
+ When you open a project, **Zen Mode** is enabled by default. In Zen Mode, only the currently selected task is displayed, while navigation still works across the full tree:
38
+
39
+ - `↑` `↓` - Move between sibling tasks at the current level
40
+ - `→` `Space` `Enter` - Enter task / view its subtasks (move deeper in the hierarchy)
41
+ - `←` - Go back to parent task (move up in the hierarchy)
42
+ - `m` - Toggle between **Zen Mode** (single-task view) and **List Mode** (full list at current level)
40
43
  - `n` - New subtask
41
44
  - `e` - Edit task title
42
45
  - `d` - Toggle done status
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "depth-first-thinking",
3
- "version": "2.1.1",
4
- "description": "A terminal-based task manager with depth-first navigation. Break down tasks into subtasks, navigate recursively, and track progress.",
3
+ "version": "2.1.3",
4
+ "description": "A terminal-based task manager with depth-first navigation.",
5
5
  "keywords": [
6
6
  "cli",
7
7
  "tui",
package/src/data/types.ts CHANGED
@@ -27,10 +27,13 @@ export interface ModalState {
27
27
  selectedButton?: number;
28
28
  }
29
29
 
30
+ export type ViewMode = "list" | "zen";
31
+
30
32
  export interface AppState {
31
33
  project: Project;
32
34
  navigationStack: string[];
33
35
  selectedIndex: number;
36
+ viewMode: ViewMode;
34
37
  modalState: ModalState | null;
35
38
  feedbackMessage: string | null;
36
39
  feedbackTimeout?: ReturnType<typeof setTimeout>;
package/src/index.ts CHANGED
@@ -15,7 +15,7 @@ const program = new Command();
15
15
 
16
16
  program
17
17
  .name("dft")
18
- .description("Depth-First Thinking - Solve problems the depth-first way")
18
+ .description("A terminal-based task manager with depth-first navigation.")
19
19
  .version(VERSION);
20
20
 
21
21
  program
@@ -0,0 +1,109 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { AppState, Project } from "../data/types";
3
+ import {
4
+ diveIn,
5
+ ensureValidSelection,
6
+ getCurrentList,
7
+ goBack,
8
+ initializeNavigation,
9
+ } from "./navigation";
10
+
11
+ function createTestProject(): Project {
12
+ const now = new Date().toISOString();
13
+ return {
14
+ project_name: "Test Project",
15
+ version: "1.0.0",
16
+ created_at: now,
17
+ modified_at: now,
18
+ root: {
19
+ id: "root",
20
+ title: "Root",
21
+ status: "open",
22
+ children: [
23
+ {
24
+ id: "a",
25
+ title: "Task A",
26
+ status: "open",
27
+ children: [],
28
+ created_at: now,
29
+ completed_at: null,
30
+ },
31
+ {
32
+ id: "b",
33
+ title: "Task B",
34
+ status: "open",
35
+ children: [],
36
+ created_at: now,
37
+ completed_at: null,
38
+ },
39
+ ],
40
+ created_at: now,
41
+ completed_at: null,
42
+ },
43
+ };
44
+ }
45
+
46
+ describe("viewMode and navigation state", () => {
47
+ test("initial state uses zen mode and selection is valid", () => {
48
+ const project = createTestProject();
49
+ const state: AppState = {
50
+ project,
51
+ navigationStack: [],
52
+ selectedIndex: 0,
53
+ viewMode: "zen",
54
+ modalState: null,
55
+ feedbackMessage: null,
56
+ };
57
+
58
+ initializeNavigation(state);
59
+ ensureValidSelection(state);
60
+
61
+ expect(state.viewMode).toBe("zen");
62
+ expect(getCurrentList(state)).toHaveLength(2);
63
+ expect(state.selectedIndex).toBe(0);
64
+ });
65
+
66
+ test("selection survives mode changes", () => {
67
+ const project = createTestProject();
68
+ const state: AppState = {
69
+ project,
70
+ navigationStack: [],
71
+ selectedIndex: 1,
72
+ viewMode: "zen",
73
+ modalState: null,
74
+ feedbackMessage: null,
75
+ };
76
+
77
+ // simulate toggling between modes
78
+ state.viewMode = "list";
79
+ expect(state.selectedIndex).toBe(1);
80
+
81
+ state.viewMode = "zen";
82
+ expect(state.selectedIndex).toBe(1);
83
+ });
84
+
85
+ test("going back restores parent selection", () => {
86
+ const project = createTestProject();
87
+ const state: AppState = {
88
+ project,
89
+ navigationStack: [],
90
+ selectedIndex: 1,
91
+ viewMode: "zen",
92
+ modalState: null,
93
+ feedbackMessage: null,
94
+ };
95
+
96
+ // Dive into Task B
97
+ const diveResult = diveIn(state);
98
+ expect(diveResult.success).toBe(true);
99
+ expect(state.navigationStack).toEqual(["b"]);
100
+
101
+ // Go back to parent list and expect Task B to be selected again
102
+ const backResult = goBack(state);
103
+ expect(backResult.success).toBe(true);
104
+ expect(state.navigationStack).toEqual([]);
105
+
106
+ const list = getCurrentList(state);
107
+ expect(list[state.selectedIndex]?.id).toBe("b");
108
+ });
109
+ });
package/src/tui/app.ts CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  toggleNodeStatus,
17
17
  } from "../data/operations";
18
18
  import { saveProject } from "../data/storage";
19
- import type { AppState, ModalState, Node, Project } from "../data/types";
19
+ import type { AppState, ModalState, Node, Project, ViewMode } from "../data/types";
20
20
  import { truncate } from "../utils/formatting";
21
21
  import { validateTitle } from "../utils/validation";
22
22
  import {
@@ -62,6 +62,7 @@ export class TUIApp {
62
62
  project,
63
63
  navigationStack: [],
64
64
  selectedIndex: 0,
65
+ viewMode: "zen",
65
66
  modalState: null,
66
67
  feedbackMessage: null,
67
68
  };
@@ -110,7 +111,7 @@ export class TUIApp {
110
111
 
111
112
  this.hintsText = new TextRenderable(this.renderer, {
112
113
  id: "hints",
113
- content: "↑↓ select →/space enter ← back n new e edit d done x del q quit",
114
+ content: "↑↓ select →/space enter ← back n new e edit d done x del m mode q quit",
114
115
  fg: colors.keyHints,
115
116
  position: "absolute",
116
117
  left: 1,
@@ -145,7 +146,12 @@ export class TUIApp {
145
146
  const width = process.stdout.columns || 80;
146
147
  ensureValidSelection(this.state);
147
148
  this.breadcrumbText.content = this.formatBreadcrumb(width);
148
- this.listText.content = this.formatList(width);
149
+ const height = process.stdout.rows || 24;
150
+ if (this.state.viewMode === "zen") {
151
+ this.listText.content = this.formatZen(width, height);
152
+ } else {
153
+ this.listText.content = this.formatList(width);
154
+ }
149
155
  this.feedbackText.content = this.state.feedbackMessage || "";
150
156
  this.updateModal();
151
157
  }
@@ -200,6 +206,28 @@ export class TUIApp {
200
206
  return ` [${count}]`;
201
207
  }
202
208
 
209
+ private formatZen(width: number, height: number): string {
210
+ const selected = getSelectedNode(this.state);
211
+ if (!selected) {
212
+ return "No items. Press 'n' to create one.";
213
+ }
214
+
215
+ const lines: string[] = [];
216
+
217
+ const title = truncate(selected.title, width - 4);
218
+ const status = this.getStatusDisplay(selected);
219
+ const childCount = this.getChildCountDisplay(selected);
220
+
221
+ lines.push(`> ${title}${childCount}${status}`);
222
+
223
+ const availableLines = (height || 24) - 6;
224
+ for (let i = 1; i < availableLines; i++) {
225
+ lines.push("");
226
+ }
227
+
228
+ return lines.join("\n");
229
+ }
230
+
203
231
  private formatList(width: number): string {
204
232
  const list = getCurrentList(this.state);
205
233
 
@@ -388,9 +416,19 @@ export class TUIApp {
388
416
  case "q":
389
417
  this.quit();
390
418
  break;
419
+
420
+ case "m":
421
+ this.toggleViewMode();
422
+ break;
391
423
  }
392
424
  }
393
425
 
426
+ private toggleViewMode(): void {
427
+ const currentMode: ViewMode = this.state.viewMode;
428
+ this.state.viewMode = currentMode === "zen" ? "list" : "zen";
429
+ this.showFeedback(this.state.viewMode === "zen" ? "Zen Mode" : "List Mode");
430
+ }
431
+
394
432
  private handleNavigation(result: { success: boolean; feedbackMessage?: string }): void {
395
433
  if (!result.success && result.feedbackMessage) {
396
434
  this.showFeedback(result.feedbackMessage);
@@ -98,8 +98,15 @@ export function goBack(state: AppState): NavigationResult {
98
98
  return { success: false, feedbackMessage: "At root" };
99
99
  }
100
100
 
101
+ // Remember the node we are leaving so we can re-select it
102
+ // when we return to its parent list.
103
+ const lastId = state.navigationStack[state.navigationStack.length - 1];
101
104
  state.navigationStack.pop();
102
- state.selectedIndex = 0;
105
+
106
+ const list = getCurrentList(state);
107
+ const previousIndex = list.findIndex((node) => node.id === lastId);
108
+
109
+ state.selectedIndex = previousIndex >= 0 ? previousIndex : 0;
103
110
  return { success: true };
104
111
  }
105
112
 
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION = "2.1.1";
1
+ export const VERSION = "2.1.3";