@rushinglabs/taskfile 0.2.2 → 0.2.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/dist/index.js CHANGED
@@ -6,7 +6,6 @@ process.on("warning", (warning) => {
6
6
  }
7
7
  console.warn(warning);
8
8
  });
9
- import "./production-env.js"; // must stay first: sets NODE_ENV before react is loaded
10
9
  import { readFileSync } from "node:fs";
11
10
  import { dirname, join } from "node:path";
12
11
  import { fileURLToPath } from "node:url";
package/dist/store.js CHANGED
@@ -156,6 +156,22 @@ export function renameList(id, name) {
156
156
  getDb().prepare("UPDATE lists SET name = ? WHERE id = ?").run(trimmed, id);
157
157
  return { status: "ok", list: { ...rowToList(row), name: trimmed } };
158
158
  }
159
+ // Deletes a list and every task in it. The last remaining list can't be deleted: the app always
160
+ // needs at least one list to land on.
161
+ export function deleteList(id) {
162
+ const row = getDb().prepare("SELECT id, name, createdAt FROM lists WHERE id = ?").get(id);
163
+ if (!row) {
164
+ return { status: "not-found" };
165
+ }
166
+ if (loadLists().length <= 1) {
167
+ return { status: "blocked", reason: "Can't delete the only list." };
168
+ }
169
+ // Sub-items reference their parent task, so they go first to keep the foreign key satisfied.
170
+ getDb().prepare("DELETE FROM tasks WHERE listId = ? AND parentId IS NOT NULL").run(id);
171
+ getDb().prepare("DELETE FROM tasks WHERE listId = ?").run(id);
172
+ getDb().prepare("DELETE FROM lists WHERE id = ?").run(id);
173
+ return { status: "ok", list: rowToList(row) };
174
+ }
159
175
  export function getDefaultListId() {
160
176
  return loadLists()[0].id;
161
177
  }
package/dist/tui/App.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
- import { useApp, useInput, useWindowSize } from "ink";
3
+ import { Text, useApp, useInput, useWindowSize } from "ink";
4
4
  import { Frame } from "./Frame.js";
5
5
  import { TodosPage } from "./TodosPage.js";
6
6
  import { SettingsPage } from "./SettingsPage.js";
@@ -9,9 +9,10 @@ import { SnapshotsModal } from "./SnapshotsModal.js";
9
9
  import { SnapshotDetailView } from "./SnapshotDetailView.js";
10
10
  import { fitFrameToWindow } from "./frameSize.js";
11
11
  import { loadSettings, saveSettings } from "../settings.js";
12
- import { computeProgress, createList, loadLists, loadTasks, renameList } from "../store.js";
12
+ import { computeProgress, createList, deleteList, loadLists, loadTasks, renameList } from "../store.js";
13
13
  import { deleteSnapshot, listSnapshots, loadSnapshotDetail, } from "../snapshots.js";
14
- const NAV_HINT = "↑/↓ cycle pages · ←/→ switch focus · r rename list · Tab switch · Ctrl+N new list · h snapshots · q quit";
14
+ const NAV_HINT = "↑/↓ cycle pages · ←/→ switch focus · r rename list · D delete list · Tab switch · Ctrl+N new list · h snapshots · q quit";
15
+ const CONFIRM_DELETE_LIST_HINT = "y/enter confirm delete · n/esc cancel";
15
16
  const SNAPSHOT_MODAL_HINT = "↑/k ↓/j select · enter/space view · d delete · esc close";
16
17
  const SNAPSHOT_CONFIRM_DELETE_HINT = "y confirm delete · n/esc cancel";
17
18
  const VIEW_SNAPSHOT_HINT = "esc/h back to live view";
@@ -49,6 +50,7 @@ export function App() {
49
50
  const [snapshotModalMode, setSnapshotModalMode] = useState("list");
50
51
  const [pendingDeleteSnapshotId, setPendingDeleteSnapshotId] = useState(undefined);
51
52
  const [viewingSnapshot, setViewingSnapshot] = useState(undefined);
53
+ const [pendingDeleteListId, setPendingDeleteListId] = useState(undefined);
52
54
  useEffect(() => {
53
55
  if (page.type === "list") {
54
56
  setSettings(saveSettings({ lastActiveListId: page.listId }));
@@ -58,6 +60,22 @@ export function App() {
58
60
  setLists(loadLists());
59
61
  }
60
62
  const renamingListId = mode === "nav-rename-list" && page.type === "list" ? page.listId : undefined;
63
+ const pendingDeleteList = mode === "confirm-delete-list" ? lists.find((list) => list.id === pendingDeleteListId) : undefined;
64
+ const pendingDeleteTaskCount = pendingDeleteList ? loadTasks(pendingDeleteList.id).length : 0;
65
+ function confirmDeleteList(listId) {
66
+ const index = lists.findIndex((list) => list.id === listId);
67
+ const result = deleteList(listId);
68
+ if (result.status === "blocked") {
69
+ setMessage(result.reason);
70
+ }
71
+ else if (result.status === "ok") {
72
+ const nextLists = loadLists();
73
+ setLists(nextLists);
74
+ // Land on the list that took the deleted one's place (or the new last list).
75
+ const neighbor = nextLists[Math.min(Math.max(index, 0), nextLists.length - 1)];
76
+ setPage({ type: "list", listId: neighbor.id });
77
+ }
78
+ }
61
79
  function cyclePage(delta) {
62
80
  const pages = buildPages(lists);
63
81
  const currentIndex = pages.findIndex((candidate) => pagesEqual(candidate, page));
@@ -132,6 +150,20 @@ export function App() {
132
150
  }
133
151
  return;
134
152
  }
153
+ if (mode === "confirm-delete-list") {
154
+ if (input === "y" || key.return) {
155
+ if (pendingDeleteListId !== undefined) {
156
+ confirmDeleteList(pendingDeleteListId);
157
+ }
158
+ }
159
+ else if (!(input === "n" || key.escape)) {
160
+ return;
161
+ }
162
+ setPendingDeleteListId(undefined);
163
+ setMode("normal");
164
+ setHint(NAV_HINT);
165
+ return;
166
+ }
135
167
  if (mode === "new-list" || mode === "nav-rename-list") {
136
168
  if (key.escape) {
137
169
  setMode("normal");
@@ -151,6 +183,17 @@ export function App() {
151
183
  setHint(SNAPSHOT_MODAL_HINT);
152
184
  return;
153
185
  }
186
+ if (input === "D" && page.type === "list") {
187
+ setMessage(undefined);
188
+ if (lists.length <= 1) {
189
+ setMessage("Can't delete the only list.");
190
+ return;
191
+ }
192
+ setPendingDeleteListId(page.listId);
193
+ setMode("confirm-delete-list");
194
+ setHint(CONFIRM_DELETE_LIST_HINT);
195
+ return;
196
+ }
154
197
  if (key.ctrl && input === "n") {
155
198
  setMessage(undefined);
156
199
  setHint("enter save · esc cancel");
@@ -198,7 +241,7 @@ export function App() {
198
241
  setLists(loadLists());
199
242
  setPage({ type: "list", listId: list.id });
200
243
  setMode("normal");
201
- } })), renamingListId !== undefined && (_jsx(AddTaskInput, { label: "Rename list: ", initialValue: lists.find((l) => l.id === renamingListId)?.name ?? "", onSubmit: (text) => {
244
+ } })), pendingDeleteList && (_jsxs(Text, { children: ["Delete list \"", pendingDeleteList.name, "\" and its ", pendingDeleteTaskCount, " task(s)? (y/n)"] })), renamingListId !== undefined && (_jsx(AddTaskInput, { label: "Rename list: ", initialValue: lists.find((l) => l.id === renamingListId)?.name ?? "", onSubmit: (text) => {
202
245
  const result = renameList(renamingListId, text);
203
246
  if (result.status === "blocked") {
204
247
  setMessage(result.reason);
@@ -5,7 +5,7 @@ import { addTask, computeProgress, hasSubItems, loadTasks, removeTask, renameLis
5
5
  import { TaskList } from "./TaskList.js";
6
6
  import { AddTaskInput } from "./AddTaskInput.js";
7
7
  import { EditTaskModal } from "./EditTaskModal.js";
8
- const LIST_HINT = "↑/k ↓/j move · space/enter toggle · a add · s add sub-item · e edit · d delete · r rename list · ←/→ nav focus · Tab switch · Ctrl+N new list · q quit";
8
+ const LIST_HINT = "↑/k ↓/j move · space/enter toggle · a add · s add sub-item · e edit · d delete · r rename list · D delete list · ←/→ nav focus · Tab switch · Ctrl+N new list · q quit";
9
9
  const EDIT_HINT = "tab cycle · space toggle priority · enter save · esc cancel";
10
10
  const RENAME_LIST_HINT = "enter save · esc cancel";
11
11
  function resolveGroupParentId(tasks, index) {
package/dist/tui/index.js CHANGED
@@ -5,6 +5,8 @@ import { loadSettings } from "../settings.js";
5
5
  import { runStartupSnapshotMaintenance } from "../snapshots.js";
6
6
  export async function runTui() {
7
7
  runStartupSnapshotMaintenance(loadSettings());
8
- const { waitUntilExit } = render(_jsx(App, {}));
8
+ // Use the terminal's alternate screen (like vim/htop) so earlier shell output
9
+ // isn't visible around the app, and is restored when the TUI exits.
10
+ const { waitUntilExit } = render(_jsx(App, {}), { alternateScreen: true });
9
11
  await waitUntilExit();
10
12
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rushinglabs/taskfile",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "A simple todo list CLI, runnable via npx",
5
5
  "main": "./dist/index.js",
6
6
  "bin": {
@@ -32,13 +32,13 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "commander": "^15.0.0",
35
- "ink": "^7.1.1",
35
+ "ink": "^8.0.0",
36
36
  "ink-text-input": "^6.0.0",
37
- "react": "^19.2.0"
37
+ "react": "^19.3.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^26.2.0",
41
- "@types/react": "^19.2.0",
41
+ "@types/react": "^19.3.0",
42
42
  "ink-testing-library": "^4.0.0",
43
43
  "tsx": "^4.23.12",
44
44
  "typescript": "^7.0.2",