@tomkapa/tayto 0.5.4 → 0.7.0

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.
@@ -8,14 +8,14 @@ import {
8
8
  detectGitRemote,
9
9
  isTerminalStatus,
10
10
  logger
11
- } from "./chunk-74Q55TOV.js";
11
+ } from "./chunk-5V4TBQ5S.js";
12
12
 
13
13
  // src/tui/index.tsx
14
14
  import { render } from "ink";
15
15
 
16
16
  // src/tui/components/App.tsx
17
- import { useReducer, useEffect as useEffect2, useCallback as useCallback2, useMemo as useMemo2, useState as useState7 } from "react";
18
- import { Box as Box18, Text as Text18, useInput as useInput7, useApp, useStdout as useStdout4 } from "ink";
17
+ import { useReducer, useEffect as useEffect4, useCallback as useCallback2, useMemo as useMemo2, useState as useState7, useRef as useRef5 } from "react";
18
+ import { Box as Box19, Text as Text19, useInput as useInput7, useApp, useStdout as useStdout4 } from "ink";
19
19
 
20
20
  // src/tui/types.ts
21
21
  var ViewType = {
@@ -25,6 +25,7 @@ var ViewType = {
25
25
  TaskEdit: "task-edit",
26
26
  ProjectSelector: "project-selector",
27
27
  ProjectCreate: "project-create",
28
+ ProjectEdit: "project-edit",
28
29
  DependencyList: "dependency-list",
29
30
  EpicPicker: "epic-picker",
30
31
  ProjectLink: "project-link",
@@ -177,8 +178,10 @@ var initialState = {
177
178
  epicSelectedIndex: 0,
178
179
  selectedEpicIds: /* @__PURE__ */ new Set(),
179
180
  linkingProject: null,
181
+ editingProject: null,
180
182
  isEpicReordering: false,
181
- epicReorderSnapshot: null
183
+ epicReorderSnapshot: null,
184
+ detectedGitRemote: null
182
185
  };
183
186
  function appReducer(state, action) {
184
187
  switch (action.type) {
@@ -200,6 +203,8 @@ function appReducer(state, action) {
200
203
  isSearchActive: false,
201
204
  formData: null,
202
205
  linkingProject: null,
206
+ editingProject: null,
207
+ detectedGitRemote: null,
203
208
  focusedPanel: "list"
204
209
  };
205
210
  }
@@ -384,6 +389,10 @@ function appReducer(state, action) {
384
389
  }
385
390
  case "SET_LINKING_PROJECT":
386
391
  return { ...state, linkingProject: action.project };
392
+ case "SET_EDITING_PROJECT":
393
+ return { ...state, editingProject: action.project };
394
+ case "SET_DETECTED_GIT_REMOTE":
395
+ return { ...state, detectedGitRemote: action.remote };
387
396
  }
388
397
  }
389
398
 
@@ -425,53 +434,116 @@ var Logo = React.memo(function Logo2() {
425
434
 
426
435
  // src/tui/components/Header.tsx
427
436
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
428
- function getKeyHints(view, isSearchActive, focusedPanel) {
437
+ function getKeyHints(state) {
438
+ const { activeView, isSearchActive, isReordering, isEpicReordering, isAddingDep, focusedPanel } = state;
439
+ if (isAddingDep) {
440
+ return [
441
+ { key: "type", desc: "search" },
442
+ { key: "enter", desc: "confirm" },
443
+ { key: "esc", desc: "cancel" }
444
+ ];
445
+ }
446
+ const REORDER_SUFFIX = [
447
+ { key: "\u2192", desc: "save" },
448
+ { key: "t", desc: "top" },
449
+ { key: "b", desc: "bottom" },
450
+ { key: "esc/\u2190", desc: "cancel" }
451
+ ];
452
+ if (isReordering) return [{ key: "\u2191/\u2193", desc: "move" }, ...REORDER_SUFFIX];
453
+ if (isEpicReordering) return [{ key: "\u2191/\u2193", desc: "move epic" }, ...REORDER_SUFFIX];
429
454
  if (isSearchActive) {
430
455
  return [
456
+ { key: "type", desc: "query" },
431
457
  { key: "enter", desc: "apply" },
432
458
  { key: "esc", desc: "cancel" }
433
459
  ];
434
460
  }
435
- if (view === "task-list" && focusedPanel === "epic") {
461
+ if (activeView === ViewType.TaskList && focusedPanel === "epic") {
436
462
  return [
437
463
  { key: "j/k", desc: "nav" },
438
464
  { key: "space", desc: "toggle" },
439
- { key: "\u2190", desc: "reorder" },
440
465
  { key: "0", desc: "clear" },
441
- { key: "tab", desc: "tasks" },
466
+ { key: "\u2190", desc: "reorder" },
467
+ { key: "tab/S-tab", desc: "panel" },
442
468
  { key: "?", desc: "help" },
443
469
  { key: "q", desc: "quit" }
444
470
  ];
445
471
  }
446
- if (view === "task-list") {
472
+ if (activeView === ViewType.TaskList && focusedPanel === "detail") {
473
+ return [
474
+ { key: "j/k", desc: "scroll" },
475
+ { key: "e", desc: "edit" },
476
+ { key: "s", desc: "status" },
477
+ { key: "d", desc: "del" },
478
+ { key: "m", desc: "mermaid" },
479
+ { key: "D", desc: "deps" },
480
+ { key: "tab/S-tab", desc: "panel" },
481
+ { key: "?", desc: "help" }
482
+ ];
483
+ }
484
+ if (activeView === ViewType.TaskList) {
447
485
  return [
448
486
  { key: "enter", desc: "view" },
449
487
  { key: "c", desc: "create" },
450
488
  { key: "e", desc: "edit" },
451
489
  { key: "d", desc: "del" },
452
490
  { key: "s", desc: "status" },
453
- { key: "a", desc: "assign" },
454
- { key: "A", desc: "unassign" },
491
+ { key: "a/A", desc: "assign" },
455
492
  { key: "\u2190", desc: "reorder" },
456
493
  { key: "/", desc: "search" },
457
494
  { key: "p", desc: "project" },
458
- { key: "f", desc: "status-f" },
459
- { key: "t", desc: "type-f" },
495
+ { key: "f/t", desc: "filter" },
460
496
  { key: "PgDn/Up", desc: "page" },
461
- { key: "tab", desc: "panel" },
497
+ { key: "tab/S-tab", desc: "panel" },
462
498
  { key: "?", desc: "help" },
463
499
  { key: "q", desc: "quit" }
464
500
  ];
465
501
  }
466
- if (view === "task-detail") {
502
+ if (activeView === ViewType.TaskDetail) {
467
503
  return [
468
504
  { key: "e", desc: "edit" },
469
505
  { key: "s", desc: "status" },
470
506
  { key: "d", desc: "del" },
471
507
  { key: "m", desc: "mermaid" },
508
+ { key: "D", desc: "deps" },
509
+ { key: "j/k", desc: "scroll" },
472
510
  { key: "esc", desc: "back" },
473
- { key: "?", desc: "help" },
474
- { key: "q", desc: "quit" }
511
+ { key: "?", desc: "help" }
512
+ ];
513
+ }
514
+ if (activeView === ViewType.DependencyList) {
515
+ return [
516
+ { key: "a", desc: "add blocker" },
517
+ { key: "x", desc: "remove" },
518
+ { key: "enter", desc: "goto task" },
519
+ { key: "esc", desc: "back" },
520
+ { key: "?", desc: "help" }
521
+ ];
522
+ }
523
+ if (activeView === ViewType.TaskCreate || activeView === ViewType.TaskEdit) {
524
+ return [
525
+ { key: "\u2191\u2193/tab", desc: "navigate" },
526
+ { key: "\u2190\u2192", desc: "cursor" },
527
+ { key: "ctrl+s", desc: "save" },
528
+ { key: "esc", desc: "cancel" }
529
+ ];
530
+ }
531
+ if (activeView === ViewType.ProjectSelector) {
532
+ return [
533
+ { key: "j/k", desc: "nav" },
534
+ { key: "enter", desc: "select" },
535
+ { key: "e", desc: "edit" },
536
+ { key: "c", desc: "create" },
537
+ { key: "l", desc: "link" },
538
+ { key: "d", desc: "default" },
539
+ { key: "esc", desc: "back" }
540
+ ];
541
+ }
542
+ if (activeView === ViewType.EpicPicker) {
543
+ return [
544
+ { key: "j/k", desc: "nav" },
545
+ { key: "enter", desc: "select" },
546
+ { key: "esc", desc: "cancel" }
475
547
  ];
476
548
  }
477
549
  return [
@@ -488,10 +560,10 @@ function chunkHints(hints, cols) {
488
560
  }
489
561
  return result;
490
562
  }
491
- function Header({ state }) {
563
+ function Header({ state, latestVersion }) {
492
564
  const projectName = state.activeProject?.name ?? "none";
493
565
  const taskCount = state.tasks.length;
494
- const hints = getKeyHints(state.activeView, state.isSearchActive, state.focusedPanel);
566
+ const hints = getKeyHints(state);
495
567
  const hintCols = hints.length <= 7 ? 2 : hints.length <= 12 ? 3 : 4;
496
568
  const columns = chunkHints(hints, hintCols);
497
569
  return /* @__PURE__ */ jsxs(Box2, { flexDirection: "row", gap: 1, children: [
@@ -505,6 +577,13 @@ function Header({ state }) {
505
577
  /* @__PURE__ */ jsxs(Box2, { gap: 1, children: [
506
578
  /* @__PURE__ */ jsx2(Text2, { color: theme.fg, children: "Tasks:" }),
507
579
  /* @__PURE__ */ jsx2(Text2, { color: theme.titleCounter, bold: true, children: taskCount })
580
+ ] }),
581
+ latestVersion && /* @__PURE__ */ jsxs(Box2, { gap: 1, children: [
582
+ /* @__PURE__ */ jsxs(Text2, { color: theme.flash.warn, children: [
583
+ "Update ",
584
+ latestVersion
585
+ ] }),
586
+ /* @__PURE__ */ jsx2(Text2, { color: theme.fg, children: "\u2014 tayto upgrade" })
508
587
  ] })
509
588
  ] }),
510
589
  /* @__PURE__ */ jsx2(Box2, { flexGrow: 1, justifyContent: "flex-end", children: /* @__PURE__ */ jsx2(Box2, { flexDirection: "row", gap: 2, children: columns.map((col, ci) => /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: col.map((h) => /* @__PURE__ */ jsxs(Box2, { children: [
@@ -514,7 +593,7 @@ function Header({ state }) {
514
593
  ">"
515
594
  ] }),
516
595
  /* @__PURE__ */ jsx2(Text2, { color: theme.menu.desc, children: h.desc })
517
- ] }, h.key)) }, ci)) }) })
596
+ ] }, h.key)) }, col[0]?.key ?? String(ci))) }) })
518
597
  ] });
519
598
  }
520
599
 
@@ -918,8 +997,8 @@ ${task.additionalRequirements}`;
918
997
  }
919
998
 
920
999
  // src/tui/components/TaskForm.tsx
921
- import { useState as useState2, useCallback } from "react";
922
- import { Box as Box9, Text as Text9, useInput as useInput2 } from "ink";
1000
+ import { useState as useState2, useCallback, useEffect, useRef } from "react";
1001
+ import { Box as Box9, Text as Text9, useInput as useInput2, useStdin } from "ink";
923
1002
 
924
1003
  // src/tui/editor.ts
925
1004
  import { writeFileSync as writeFileSync2, readFileSync, unlinkSync, mkdtempSync as mkdtempSync2 } from "fs";
@@ -929,15 +1008,17 @@ import { spawnSync } from "child_process";
929
1008
  function getEditor() {
930
1009
  return process.env["EDITOR"] ?? process.env["VISUAL"] ?? "vi";
931
1010
  }
932
- function openInEditor(content, filename) {
1011
+ function openInEditor(content, filename, options) {
933
1012
  const dir = mkdtempSync2(join2(tmpdir2(), "task-"));
934
1013
  const filepath = join2(dir, filename);
935
1014
  writeFileSync2(filepath, content, "utf-8");
936
1015
  const editor = getEditor();
1016
+ options?.beforeOpen?.();
937
1017
  const result = spawnSync(editor, [filepath], {
938
1018
  stdio: "inherit",
939
1019
  shell: true
940
1020
  });
1021
+ options?.afterOpen?.();
941
1022
  if (result.status !== 0) {
942
1023
  try {
943
1024
  unlinkSync(filepath);
@@ -1154,7 +1235,7 @@ function TaskPicker({ tasks, excludeIds, initialSelection, onConfirm, onCancel }
1154
1235
  }
1155
1236
 
1156
1237
  // src/tui/components/TaskForm.tsx
1157
- import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1238
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1158
1239
  var FIELDS = [
1159
1240
  { label: "Name", key: "name", type: "inline" },
1160
1241
  { label: "Type", key: "type", type: "select", options: TYPE_VALUES },
@@ -1184,23 +1265,42 @@ function TaskForm({ editingTask, allTasks, initialDeps, onSave, onCancel }) {
1184
1265
  technicalNotes: editingTask?.technicalNotes ?? "",
1185
1266
  additionalRequirements: editingTask?.additionalRequirements ?? ""
1186
1267
  });
1268
+ const [cursorPos, setCursorPos] = useState2(() => (editingTask?.name ?? "").length);
1269
+ const cursorRef = useRef(cursorPos);
1270
+ cursorRef.current = cursorPos;
1187
1271
  const [editorActive, setEditorActive] = useState2(false);
1188
1272
  const [pickerActive, setPickerActive] = useState2(false);
1189
1273
  const [pickedDeps, setPickedDeps] = useState2(initialDeps ?? []);
1274
+ const { setRawMode } = useStdin();
1275
+ useEffect(() => {
1276
+ const field2 = FIELDS[focusIndex];
1277
+ if (field2?.type === "inline") {
1278
+ const pos = values[field2.key]?.length ?? 0;
1279
+ setCursorPos(pos);
1280
+ cursorRef.current = pos;
1281
+ }
1282
+ }, [focusIndex]);
1190
1283
  const currentField = FIELDS[focusIndex];
1191
1284
  const launchEditor = useCallback(
1192
1285
  (field2) => {
1193
1286
  setEditorActive(true);
1194
1287
  setTimeout(() => {
1195
1288
  const content = values[field2.key] ?? "";
1196
- const result = openInEditor(content, field2.editorFilename ?? `${field2.key}.md`);
1289
+ const result = openInEditor(content, field2.editorFilename ?? `${field2.key}.md`, {
1290
+ beforeOpen: () => {
1291
+ setRawMode(false);
1292
+ },
1293
+ afterOpen: () => {
1294
+ setRawMode(true);
1295
+ }
1296
+ });
1197
1297
  if (result !== null) {
1198
1298
  setValues((v) => ({ ...v, [field2.key]: result }));
1199
1299
  }
1200
1300
  setEditorActive(false);
1201
1301
  }, 50);
1202
1302
  },
1203
- [values]
1303
+ [values, setRawMode]
1204
1304
  );
1205
1305
  const handlePickerConfirm = useCallback((selected) => {
1206
1306
  setPickedDeps(selected);
@@ -1240,32 +1340,49 @@ function TaskForm({ editingTask, allTasks, initialDeps, onSave, onCancel }) {
1240
1340
  }
1241
1341
  return;
1242
1342
  }
1343
+ if (key.upArrow) {
1344
+ setFocusIndex((i) => Math.max(0, i - 1));
1345
+ return;
1346
+ }
1347
+ if (key.downArrow) {
1348
+ setFocusIndex((i) => Math.min(FIELDS.length - 1, i + 1));
1349
+ return;
1350
+ }
1243
1351
  if (currentField.type === "inline") {
1244
- const currentValue = values[currentField.key] ?? "";
1245
- if (key.backspace || key.delete) {
1246
- setValues((v) => ({ ...v, [currentField.key]: currentValue.slice(0, -1) }));
1352
+ if (key.leftArrow) {
1353
+ setCursorPos((p) => Math.max(0, p - 1));
1354
+ } else if (key.rightArrow) {
1355
+ setCursorPos((p) => Math.min((values[currentField.key] ?? "").length, p + 1));
1356
+ } else if (key.backspace || key.delete) {
1357
+ const pos = cursorRef.current;
1358
+ if (pos > 0) {
1359
+ setValues((v) => {
1360
+ const cur = v[currentField.key] ?? "";
1361
+ return { ...v, [currentField.key]: cur.slice(0, pos - 1) + cur.slice(pos) };
1362
+ });
1363
+ cursorRef.current = pos - 1;
1364
+ setCursorPos(pos - 1);
1365
+ }
1247
1366
  } else if (key.return) {
1248
1367
  setFocusIndex((i) => Math.min(FIELDS.length - 1, i + 1));
1249
1368
  } else if (input && !key.ctrl && !key.meta) {
1250
- setValues((v) => ({ ...v, [currentField.key]: currentValue + input }));
1369
+ const pos = cursorRef.current;
1370
+ setValues((v) => {
1371
+ const cur = v[currentField.key] ?? "";
1372
+ return { ...v, [currentField.key]: cur.slice(0, pos) + input + cur.slice(pos) };
1373
+ });
1374
+ cursorRef.current = pos + input.length;
1375
+ setCursorPos(pos + input.length);
1251
1376
  }
1252
1377
  }
1253
1378
  if (currentField.type === "picker") {
1254
1379
  if (key.return) {
1255
1380
  setPickerActive(true);
1256
- } else if (key.downArrow) {
1257
- setFocusIndex((i) => Math.min(FIELDS.length - 1, i + 1));
1258
- } else if (key.upArrow) {
1259
- setFocusIndex((i) => Math.max(0, i - 1));
1260
1381
  }
1261
1382
  }
1262
1383
  if (currentField.type === "editor") {
1263
1384
  if (key.return) {
1264
1385
  launchEditor(currentField);
1265
- } else if (key.downArrow) {
1266
- setFocusIndex((i) => Math.min(FIELDS.length - 1, i + 1));
1267
- } else if (key.upArrow) {
1268
- setFocusIndex((i) => Math.max(0, i - 1));
1269
1386
  }
1270
1387
  }
1271
1388
  if (currentField.type === "select") {
@@ -1306,37 +1423,37 @@ function TaskForm({ editingTask, allTasks, initialDeps, onSave, onCancel }) {
1306
1423
  /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", paddingX: 1, paddingY: 0, children: FIELDS.map((field2, i) => {
1307
1424
  const isFocused = i === focusIndex;
1308
1425
  const value = field2.key === "dependsOn" ? depSummary : values[field2.key] ?? "";
1309
- const displayValue = value;
1310
1426
  return /* @__PURE__ */ jsxs6(Box9, { gap: 1, children: [
1311
1427
  /* @__PURE__ */ jsxs6(Text9, { color: isFocused ? theme.dialog.label : theme.yaml.key, bold: isFocused, children: [
1312
1428
  isFocused ? ">" : " ",
1313
1429
  " ",
1314
1430
  field2.label.padEnd(14)
1315
1431
  ] }),
1316
- field2.type === "inline" && /* @__PURE__ */ jsxs6(Text9, { color: isFocused ? theme.yaml.value : theme.table.fg, children: [
1317
- displayValue,
1318
- isFocused ? /* @__PURE__ */ jsx9(Text9, { color: theme.titleHighlight, children: "_" }) : ""
1319
- ] }),
1432
+ field2.type === "inline" && /* @__PURE__ */ jsx9(Text9, { color: isFocused ? theme.yaml.value : theme.table.fg, children: isFocused ? /* @__PURE__ */ jsxs6(Fragment2, { children: [
1433
+ value.slice(0, cursorPos),
1434
+ /* @__PURE__ */ jsx9(Text9, { color: theme.titleHighlight, children: "_" }),
1435
+ value.slice(cursorPos)
1436
+ ] }) : value }),
1320
1437
  field2.type === "picker" && /* @__PURE__ */ jsxs6(Text9, { children: [
1321
- displayValue ? /* @__PURE__ */ jsx9(Text9, { color: theme.status.added, children: displayValue.length > 60 ? displayValue.slice(0, 60) + "..." : displayValue }) : /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: isFocused ? "press enter to select" : "none" }),
1438
+ value ? /* @__PURE__ */ jsx9(Text9, { color: theme.status.added, children: value.length > 60 ? value.slice(0, 60) + "..." : value }) : /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: isFocused ? "press enter to select" : "none" }),
1322
1439
  isFocused && /* @__PURE__ */ jsx9(Text9, { color: theme.menu.key, children: " [enter: open picker]" })
1323
1440
  ] }),
1324
1441
  field2.type === "editor" && /* @__PURE__ */ jsxs6(Text9, { children: [
1325
- displayValue ? /* @__PURE__ */ jsxs6(Text9, { color: theme.status.added, children: [
1326
- displayValue.split("\n")[0]?.slice(0, 50),
1327
- displayValue.length > 50 || displayValue.includes("\n") ? "..." : ""
1442
+ value ? /* @__PURE__ */ jsxs6(Text9, { color: theme.status.added, children: [
1443
+ value.split("\n")[0]?.slice(0, 50),
1444
+ value.length > 50 || value.includes("\n") ? "..." : ""
1328
1445
  ] }) : /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: isFocused ? "press enter" : "empty" }),
1329
1446
  isFocused && /* @__PURE__ */ jsx9(Text9, { color: theme.menu.key, children: " [enter: $EDITOR]" })
1330
1447
  ] }),
1331
1448
  field2.type === "select" && /* @__PURE__ */ jsxs6(Text9, { color: isFocused ? theme.yaml.value : theme.table.fg, children: [
1332
1449
  isFocused ? "< " : " ",
1333
- displayValue,
1450
+ value,
1334
1451
  isFocused ? " >" : ""
1335
1452
  ] })
1336
1453
  ] }, field2.key);
1337
1454
  }) }),
1338
1455
  /* @__PURE__ */ jsx9(Box9, { flexGrow: 1 }),
1339
- /* @__PURE__ */ jsx9(Box9, { paddingX: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "tab: next | shift+tab: prev | ctrl+s: save | esc: cancel" }) }),
1456
+ /* @__PURE__ */ jsx9(Box9, { paddingX: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "\u2191\u2193/tab: navigate | \u2190\u2192: cursor | ctrl+s: save | esc: cancel" }) }),
1340
1457
  editorActive && /* @__PURE__ */ jsx9(Box9, { paddingX: 1, children: /* @__PURE__ */ jsx9(Text9, { color: theme.flash.warn, bold: true, children: "Editor open... save and close to return" }) })
1341
1458
  ] });
1342
1459
  }
@@ -1350,6 +1467,7 @@ function ProjectSelector({
1350
1467
  activeProject,
1351
1468
  onSelect,
1352
1469
  onCreate,
1470
+ onEdit,
1353
1471
  onSetDefault,
1354
1472
  onLink,
1355
1473
  onCancel
@@ -1375,6 +1493,13 @@ function ProjectSelector({
1375
1493
  onCreate();
1376
1494
  return;
1377
1495
  }
1496
+ if (input === "e") {
1497
+ const project = projects[selectedIndex];
1498
+ if (project) {
1499
+ onEdit(project);
1500
+ }
1501
+ return;
1502
+ }
1378
1503
  if (input === "d") {
1379
1504
  const project = projects[selectedIndex];
1380
1505
  if (project) {
@@ -1419,7 +1544,7 @@ function ProjectSelector({
1419
1544
  const activeMarker = isActive ? "*" : " ";
1420
1545
  const defaultMarker = project.isDefault ? "D" : " ";
1421
1546
  const marker = `${activeMarker}${defaultMarker}`;
1422
- const remoteDisplay = (project.gitRemote ?? "").slice(0, 38).padEnd(40);
1547
+ const remoteDisplay = (project.gitRemote?.value ?? "").slice(0, 38).padEnd(40);
1423
1548
  if (isSelected) {
1424
1549
  return /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsxs7(Text10, { backgroundColor: theme.table.cursorBg, color: theme.table.cursorFg, bold: true, children: [
1425
1550
  marker,
@@ -1440,28 +1565,53 @@ function ProjectSelector({
1440
1565
  ] }, project.id);
1441
1566
  }),
1442
1567
  /* @__PURE__ */ jsx10(Box10, { flexGrow: 1 }),
1443
- /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "enter: select | d: set default | l: link git | c: create | esc: back" }) })
1568
+ /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "enter: select | e: edit | d: set default | l: link git | c: create | esc: back" }) })
1444
1569
  ] });
1445
1570
  }
1446
1571
 
1447
1572
  // src/tui/components/ProjectForm.tsx
1448
- import { useState as useState4 } from "react";
1573
+ import { useState as useState4, useEffect as useEffect2, useRef as useRef2 } from "react";
1449
1574
  import { Box as Box11, Text as Text11, useInput as useInput4 } from "ink";
1450
- import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
1575
+ import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
1451
1576
  var FIELDS2 = [
1452
1577
  { label: "Name", key: "name", type: "inline" },
1453
1578
  { label: "Key", key: "key", type: "inline" },
1454
1579
  { label: "Description", key: "description", type: "inline" },
1580
+ { label: "Git Remote", key: "gitRemote", type: "inline" },
1455
1581
  { label: "Default", key: "isDefault", type: "toggle" }
1456
1582
  ];
1457
- function ProjectForm({ onSave, onCancel }) {
1583
+ function ProjectForm({ editingProject, initialGitRemote, onSave, onCancel }) {
1584
+ const isEditing = !!editingProject;
1458
1585
  const [focusIndex, setFocusIndex] = useState4(0);
1459
- const [values, setValues] = useState4({
1460
- name: "",
1461
- key: "",
1462
- description: "",
1463
- isDefault: "no"
1586
+ const [values, setValues] = useState4(() => {
1587
+ if (editingProject) {
1588
+ return {
1589
+ name: editingProject.name,
1590
+ key: editingProject.key,
1591
+ description: editingProject.description,
1592
+ gitRemote: editingProject.gitRemote?.value ?? "",
1593
+ isDefault: editingProject.isDefault ? "yes" : "no"
1594
+ };
1595
+ }
1596
+ return {
1597
+ name: "",
1598
+ key: "",
1599
+ description: "",
1600
+ gitRemote: initialGitRemote?.value ?? "",
1601
+ isDefault: "no"
1602
+ };
1464
1603
  });
1604
+ const [cursorPos, setCursorPos] = useState4(0);
1605
+ const cursorRef = useRef2(cursorPos);
1606
+ cursorRef.current = cursorPos;
1607
+ useEffect2(() => {
1608
+ const field2 = FIELDS2[focusIndex];
1609
+ if (field2?.type === "inline") {
1610
+ const pos = values[field2.key]?.length ?? 0;
1611
+ setCursorPos(pos);
1612
+ cursorRef.current = pos;
1613
+ }
1614
+ }, [focusIndex]);
1465
1615
  const currentField = FIELDS2[focusIndex];
1466
1616
  useInput4((input, key) => {
1467
1617
  if (key.escape) {
@@ -1484,19 +1634,46 @@ function ProjectForm({ onSave, onCancel }) {
1484
1634
  name: nameVal,
1485
1635
  key: values["key"] ?? "",
1486
1636
  description: values["description"] ?? "",
1637
+ gitRemote: values["gitRemote"] ?? "",
1487
1638
  isDefault: values["isDefault"] === "yes"
1488
1639
  });
1489
1640
  }
1490
1641
  return;
1491
1642
  }
1492
- if (currentField.type === "inline") {
1493
- const currentValue = values[currentField.key] ?? "";
1494
- if (key.backspace || key.delete) {
1495
- setValues((v) => ({ ...v, [currentField.key]: currentValue.slice(0, -1) }));
1643
+ if (key.upArrow) {
1644
+ setFocusIndex((i) => Math.max(0, i - 1));
1645
+ return;
1646
+ }
1647
+ if (key.downArrow) {
1648
+ setFocusIndex((i) => Math.min(FIELDS2.length - 1, i + 1));
1649
+ return;
1650
+ }
1651
+ const isReadOnly = isEditing && currentField.key === "key";
1652
+ if (currentField.type === "inline" && !isReadOnly) {
1653
+ if (key.leftArrow) {
1654
+ setCursorPos((p) => Math.max(0, p - 1));
1655
+ } else if (key.rightArrow) {
1656
+ setCursorPos((p) => Math.min((values[currentField.key] ?? "").length, p + 1));
1657
+ } else if (key.backspace || key.delete) {
1658
+ const pos = cursorRef.current;
1659
+ if (pos > 0) {
1660
+ setValues((v) => {
1661
+ const cur = v[currentField.key] ?? "";
1662
+ return { ...v, [currentField.key]: cur.slice(0, pos - 1) + cur.slice(pos) };
1663
+ });
1664
+ cursorRef.current = pos - 1;
1665
+ setCursorPos(pos - 1);
1666
+ }
1496
1667
  } else if (key.return) {
1497
1668
  setFocusIndex((i) => Math.min(FIELDS2.length - 1, i + 1));
1498
1669
  } else if (input && !key.ctrl && !key.meta) {
1499
- setValues((v) => ({ ...v, [currentField.key]: currentValue + input }));
1670
+ const pos = cursorRef.current;
1671
+ setValues((v) => {
1672
+ const cur = v[currentField.key] ?? "";
1673
+ return { ...v, [currentField.key]: cur.slice(0, pos) + input + cur.slice(pos) };
1674
+ });
1675
+ cursorRef.current = pos + input.length;
1676
+ setCursorPos(pos + input.length);
1500
1677
  }
1501
1678
  }
1502
1679
  if (currentField.type === "toggle") {
@@ -1511,7 +1688,7 @@ function ProjectForm({ onSave, onCancel }) {
1511
1688
  return /* @__PURE__ */ jsxs8(Box11, { flexDirection: "column", flexGrow: 1, borderStyle: "bold", borderColor: theme.borderFocus, children: [
1512
1689
  /* @__PURE__ */ jsx11(Box11, { gap: 0, children: /* @__PURE__ */ jsxs8(Text11, { color: theme.title, bold: true, children: [
1513
1690
  " ",
1514
- "new project"
1691
+ isEditing ? "edit project" : "new project"
1515
1692
  ] }) }),
1516
1693
  /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", paddingX: 1, paddingY: 0, children: FIELDS2.map((field2, i) => {
1517
1694
  const isFocused = i === focusIndex;
@@ -1523,9 +1700,13 @@ function ProjectForm({ onSave, onCancel }) {
1523
1700
  field2.label.padEnd(14)
1524
1701
  ] }),
1525
1702
  field2.type === "inline" && /* @__PURE__ */ jsxs8(Text11, { color: isFocused ? theme.yaml.value : theme.table.fg, children: [
1526
- value,
1527
- isFocused ? /* @__PURE__ */ jsx11(Text11, { color: theme.titleHighlight, children: "_" }) : "",
1528
- field2.key === "key" && !value && /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: isFocused ? " (auto from name)" : "" })
1703
+ isFocused ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
1704
+ value.slice(0, cursorPos),
1705
+ /* @__PURE__ */ jsx11(Text11, { color: theme.titleHighlight, children: "_" }),
1706
+ value.slice(cursorPos)
1707
+ ] }) : value,
1708
+ field2.key === "key" && !value && !isEditing && /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: isFocused ? " (auto from name)" : "" }),
1709
+ field2.key === "key" && isEditing && isFocused && /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: " (read-only)" })
1529
1710
  ] }),
1530
1711
  field2.type === "toggle" && /* @__PURE__ */ jsxs8(Text11, { color: isFocused ? theme.yaml.value : theme.table.fg, children: [
1531
1712
  isFocused ? "< " : " ",
@@ -1535,16 +1716,19 @@ function ProjectForm({ onSave, onCancel }) {
1535
1716
  ] }, field2.key);
1536
1717
  }) }),
1537
1718
  /* @__PURE__ */ jsx11(Box11, { flexGrow: 1 }),
1538
- /* @__PURE__ */ jsx11(Box11, { paddingX: 1, children: /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "tab: next | shift+tab: prev | ctrl+s: save | esc: cancel" }) })
1719
+ /* @__PURE__ */ jsx11(Box11, { paddingX: 1, children: /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "\u2191\u2193/tab: navigate | \u2190\u2192: cursor | ctrl+s: save | esc: cancel" }) })
1539
1720
  ] });
1540
1721
  }
1541
1722
 
1542
1723
  // src/tui/components/ProjectLinkForm.tsx
1543
- import { useState as useState5 } from "react";
1724
+ import { useState as useState5, useRef as useRef3 } from "react";
1544
1725
  import { Box as Box12, Text as Text12, useInput as useInput5 } from "ink";
1545
1726
  import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1546
1727
  function ProjectLinkForm({ project, onSave, onUnlink, onDetect, onCancel }) {
1547
- const [remoteUrl, setRemoteUrl] = useState5(project.gitRemote ?? "");
1728
+ const [remoteUrl, setRemoteUrl] = useState5(project.gitRemote?.value ?? "");
1729
+ const [cursorPos, setCursorPos] = useState5(() => (project.gitRemote?.value ?? "").length);
1730
+ const cursorRef = useRef3(cursorPos);
1731
+ cursorRef.current = cursorPos;
1548
1732
  useInput5((input, key) => {
1549
1733
  if (key.escape) {
1550
1734
  onCancel();
@@ -1561,6 +1745,8 @@ function ProjectLinkForm({ project, onSave, onUnlink, onDetect, onCancel }) {
1561
1745
  const detected = onDetect();
1562
1746
  if (detected) {
1563
1747
  setRemoteUrl(detected);
1748
+ cursorRef.current = detected.length;
1749
+ setCursorPos(detected.length);
1564
1750
  }
1565
1751
  return;
1566
1752
  }
@@ -1570,12 +1756,28 @@ function ProjectLinkForm({ project, onSave, onUnlink, onDetect, onCancel }) {
1570
1756
  }
1571
1757
  return;
1572
1758
  }
1759
+ if (key.leftArrow) {
1760
+ setCursorPos((p) => Math.max(0, p - 1));
1761
+ return;
1762
+ }
1763
+ if (key.rightArrow) {
1764
+ setCursorPos((p) => Math.min(remoteUrl.length, p + 1));
1765
+ return;
1766
+ }
1573
1767
  if (key.backspace || key.delete) {
1574
- setRemoteUrl((v) => v.slice(0, -1));
1768
+ const pos = cursorRef.current;
1769
+ if (pos > 0) {
1770
+ setRemoteUrl((v) => v.slice(0, pos - 1) + v.slice(pos));
1771
+ cursorRef.current = pos - 1;
1772
+ setCursorPos(pos - 1);
1773
+ }
1575
1774
  return;
1576
1775
  }
1577
1776
  if (input && !key.ctrl && !key.meta) {
1578
- setRemoteUrl((v) => v + input);
1777
+ const pos = cursorRef.current;
1778
+ setRemoteUrl((v) => v.slice(0, pos) + input + v.slice(pos));
1779
+ cursorRef.current = pos + input.length;
1780
+ setCursorPos(pos + input.length);
1579
1781
  }
1580
1782
  });
1581
1783
  return /* @__PURE__ */ jsxs9(Box12, { flexDirection: "column", flexGrow: 1, borderStyle: "bold", borderColor: theme.borderFocus, children: [
@@ -1599,15 +1801,16 @@ function ProjectLinkForm({ project, onSave, onUnlink, onDetect, onCancel }) {
1599
1801
  {
1600
1802
  color: project.gitRemote ? theme.yaml.value : theme.table.fg,
1601
1803
  dimColor: !project.gitRemote,
1602
- children: project.gitRemote ?? "(none)"
1804
+ children: project.gitRemote?.value ?? "(none)"
1603
1805
  }
1604
1806
  )
1605
1807
  ] }),
1606
1808
  /* @__PURE__ */ jsxs9(Box12, { gap: 1, marginTop: 1, children: [
1607
1809
  /* @__PURE__ */ jsx12(Text12, { color: theme.dialog.label, bold: true, children: "Remote URL: " }),
1608
1810
  /* @__PURE__ */ jsxs9(Text12, { color: theme.yaml.value, children: [
1609
- remoteUrl,
1610
- /* @__PURE__ */ jsx12(Text12, { color: theme.titleHighlight, children: "_" })
1811
+ remoteUrl.slice(0, cursorPos),
1812
+ /* @__PURE__ */ jsx12(Text12, { color: theme.titleHighlight, children: "_" }),
1813
+ remoteUrl.slice(cursorPos)
1611
1814
  ] })
1612
1815
  ] })
1613
1816
  ] }),
@@ -1616,18 +1819,51 @@ function ProjectLinkForm({ project, onSave, onUnlink, onDetect, onCancel }) {
1616
1819
  ] });
1617
1820
  }
1618
1821
 
1822
+ // src/utils/dismissed-remotes.ts
1823
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
1824
+ function isDismissedRemote(filePath, remote) {
1825
+ try {
1826
+ const content = readFileSync2(filePath, "utf-8");
1827
+ const list = JSON.parse(content);
1828
+ if (!Array.isArray(list)) return false;
1829
+ return list.includes(remote.value);
1830
+ } catch {
1831
+ return false;
1832
+ }
1833
+ }
1834
+ function dismissRemote(filePath, remote) {
1835
+ let list = [];
1836
+ try {
1837
+ const content = readFileSync2(filePath, "utf-8");
1838
+ const parsed = JSON.parse(content);
1839
+ if (Array.isArray(parsed)) {
1840
+ list = parsed.filter((v) => typeof v === "string");
1841
+ }
1842
+ } catch {
1843
+ }
1844
+ if (list.includes(remote.value)) return;
1845
+ list.push(remote.value);
1846
+ try {
1847
+ writeFileSync3(filePath, JSON.stringify(list), "utf-8");
1848
+ logger.info(`dismissRemote: persisted ${remote.value} to ${filePath}`);
1849
+ } catch (e) {
1850
+ logger.error(`dismissRemote: failed to write ${filePath}`, e instanceof Error ? e : new Error(String(e)));
1851
+ }
1852
+ }
1853
+
1619
1854
  // src/tui/components/HelpOverlay.tsx
1620
1855
  import { Box as Box13, Text as Text13 } from "ink";
1621
1856
  import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1622
- var SECTIONS = [
1857
+ var ROW1 = [
1623
1858
  {
1624
1859
  title: "NAVIGATION",
1625
1860
  keys: [
1626
1861
  ["j/k", "Up/Down"],
1627
1862
  ["g/G", "Top/Bottom"],
1628
- ["PgDn", "Page down"],
1629
- ["PgUp", "Page up"],
1630
- ["enter", "View"],
1863
+ ["PgDn/Up", "Page"],
1864
+ ["tab", "Next panel"],
1865
+ ["S-tab", "Prev panel"],
1866
+ ["enter", "View/select"],
1631
1867
  ["esc", "Back"]
1632
1868
  ]
1633
1869
  },
@@ -1638,14 +1874,16 @@ var SECTIONS = [
1638
1874
  ["e", "Edit"],
1639
1875
  ["d", "Delete"],
1640
1876
  ["s", "Status cycle"],
1641
- ["D", "Dependencies"]
1877
+ ["a/A", "Assign/unassign"],
1878
+ ["D", "Dependencies"],
1879
+ ["m", "Mermaid"]
1642
1880
  ]
1643
1881
  },
1644
1882
  {
1645
1883
  title: "REORDER",
1646
1884
  keys: [
1647
1885
  ["\u2190", "Enter reorder"],
1648
- ["\u2191\u2193", "Move task"],
1886
+ ["\u2191\u2193", "Move item"],
1649
1887
  ["t", "Jump to top"],
1650
1888
  ["b", "Jump to bottom"],
1651
1889
  ["\u2192", "Save position"],
@@ -1660,6 +1898,17 @@ var SECTIONS = [
1660
1898
  ["t", "Type filter"],
1661
1899
  ["0", "Clear filters"]
1662
1900
  ]
1901
+ }
1902
+ ];
1903
+ var ROW2 = [
1904
+ {
1905
+ title: "EPIC PANEL",
1906
+ keys: [
1907
+ ["j/k", "Navigate"],
1908
+ ["space", "Toggle filter"],
1909
+ ["0", "Clear filter"],
1910
+ ["\u2190", "Reorder epics"]
1911
+ ]
1663
1912
  },
1664
1913
  {
1665
1914
  title: "DEPS VIEW",
@@ -1670,6 +1919,16 @@ var SECTIONS = [
1670
1919
  ["esc", "Back"]
1671
1920
  ]
1672
1921
  },
1922
+ {
1923
+ title: "FORMS",
1924
+ keys: [
1925
+ ["\u2191\u2193/tab", "Navigate fields"],
1926
+ ["\u2190\u2192", "Move cursor"],
1927
+ ["ctrl+s", "Save"],
1928
+ ["enter", "Open editor / next"],
1929
+ ["esc", "Cancel"]
1930
+ ]
1931
+ },
1673
1932
  {
1674
1933
  title: "GENERAL",
1675
1934
  keys: [
@@ -1679,6 +1938,19 @@ var SECTIONS = [
1679
1938
  ]
1680
1939
  }
1681
1940
  ];
1941
+ function SectionRow({ sections }) {
1942
+ return /* @__PURE__ */ jsx13(Box13, { flexDirection: "row", gap: 4, children: sections.map((section) => /* @__PURE__ */ jsxs10(Box13, { flexDirection: "column", children: [
1943
+ /* @__PURE__ */ jsx13(Text13, { color: theme.table.headerFg, bold: true, children: section.title }),
1944
+ section.keys.map(([key, desc]) => /* @__PURE__ */ jsxs10(Box13, { gap: 1, children: [
1945
+ /* @__PURE__ */ jsxs10(Text13, { color: theme.menu.key, bold: true, children: [
1946
+ "<",
1947
+ key.padEnd(5),
1948
+ ">"
1949
+ ] }),
1950
+ /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: desc })
1951
+ ] }, key))
1952
+ ] }, section.title)) });
1953
+ }
1682
1954
  function HelpOverlay() {
1683
1955
  return /* @__PURE__ */ jsxs10(
1684
1956
  Box13,
@@ -1694,17 +1966,10 @@ function HelpOverlay() {
1694
1966
  "Help"
1695
1967
  ] }),
1696
1968
  /* @__PURE__ */ jsx13(Text13, { children: " " }),
1697
- /* @__PURE__ */ jsx13(Box13, { flexDirection: "row", gap: 4, children: SECTIONS.map((section) => /* @__PURE__ */ jsxs10(Box13, { flexDirection: "column", children: [
1698
- /* @__PURE__ */ jsx13(Text13, { color: theme.table.headerFg, bold: true, children: section.title }),
1699
- section.keys.map(([key, desc]) => /* @__PURE__ */ jsxs10(Box13, { gap: 1, children: [
1700
- /* @__PURE__ */ jsxs10(Text13, { color: theme.menu.key, bold: true, children: [
1701
- "<",
1702
- (key ?? "").padEnd(5),
1703
- ">"
1704
- ] }),
1705
- /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: desc })
1706
- ] }, key))
1707
- ] }, section.title)) }),
1969
+ /* @__PURE__ */ jsxs10(Box13, { flexDirection: "column", gap: 1, children: [
1970
+ /* @__PURE__ */ jsx13(SectionRow, { sections: ROW1 }),
1971
+ /* @__PURE__ */ jsx13(SectionRow, { sections: ROW2 })
1972
+ ] }),
1708
1973
  /* @__PURE__ */ jsx13(Text13, { children: " " }),
1709
1974
  /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: "Press any key to close" })
1710
1975
  ]
@@ -1751,9 +2016,41 @@ function ConfirmDialog({ task }) {
1751
2016
  );
1752
2017
  }
1753
2018
 
1754
- // src/tui/components/DependencyList.tsx
2019
+ // src/tui/components/DetectedProjectDialog.tsx
1755
2020
  import { Box as Box15, Text as Text15 } from "ink";
1756
- import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
2021
+ import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
2022
+ function DetectedProjectDialog({ remote }) {
2023
+ return /* @__PURE__ */ jsxs12(
2024
+ Box15,
2025
+ {
2026
+ flexDirection: "column",
2027
+ borderStyle: "bold",
2028
+ borderColor: theme.borderFocus,
2029
+ paddingX: 3,
2030
+ paddingY: 1,
2031
+ alignSelf: "center",
2032
+ children: [
2033
+ /* @__PURE__ */ jsx15(Text15, { color: theme.dialog.label, bold: true, children: "<New Repo Detected>" }),
2034
+ /* @__PURE__ */ jsx15(Text15, { children: " " }),
2035
+ /* @__PURE__ */ jsxs12(Text15, { color: theme.dialog.fg, children: [
2036
+ "Git remote: ",
2037
+ /* @__PURE__ */ jsx15(Text15, { bold: true, children: remote.value })
2038
+ ] }),
2039
+ /* @__PURE__ */ jsx15(Text15, { color: theme.dialog.fg, children: "No project is linked to this repo yet." }),
2040
+ /* @__PURE__ */ jsx15(Text15, { color: theme.dialog.fg, children: "Would you like to create one and link it?" }),
2041
+ /* @__PURE__ */ jsx15(Text15, { children: " " }),
2042
+ /* @__PURE__ */ jsxs12(Box15, { gap: 3, children: [
2043
+ /* @__PURE__ */ jsx15(Text15, { backgroundColor: theme.dialog.buttonFocusBg, color: theme.dialog.buttonFocusFg, bold: true, children: " y: Create " }),
2044
+ /* @__PURE__ */ jsx15(Text15, { backgroundColor: theme.dialog.buttonBg, color: theme.dialog.buttonFg, children: " n: Skip " })
2045
+ ] })
2046
+ ]
2047
+ }
2048
+ );
2049
+ }
2050
+
2051
+ // src/tui/components/DependencyList.tsx
2052
+ import { Box as Box16, Text as Text16 } from "ink";
2053
+ import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1757
2054
  function TaskRow({
1758
2055
  task,
1759
2056
  globalIndex,
@@ -1761,16 +2058,16 @@ function TaskRow({
1761
2058
  }) {
1762
2059
  const isSelected = globalIndex === selectedIndex;
1763
2060
  const statusColor = STATUS_COLOR[task.status] ?? theme.table.fg;
1764
- return /* @__PURE__ */ jsx15(Box15, { children: isSelected ? /* @__PURE__ */ jsxs12(Text15, { backgroundColor: theme.table.cursorBg, color: theme.table.cursorFg, bold: true, children: [
2061
+ return /* @__PURE__ */ jsx16(Box16, { children: isSelected ? /* @__PURE__ */ jsxs13(Text16, { backgroundColor: theme.table.cursorBg, color: theme.table.cursorFg, bold: true, children: [
1765
2062
  "> ",
1766
2063
  task.id.padEnd(12),
1767
2064
  task.status.padEnd(14),
1768
2065
  task.name
1769
- ] }) : /* @__PURE__ */ jsxs12(Fragment2, { children: [
1770
- /* @__PURE__ */ jsx15(Text15, { children: " " }),
1771
- /* @__PURE__ */ jsx15(Text15, { color: theme.yaml.value, children: task.id.padEnd(12) }),
1772
- /* @__PURE__ */ jsx15(Text15, { color: statusColor, children: task.status.padEnd(14) }),
1773
- /* @__PURE__ */ jsx15(Text15, { color: theme.table.fg, children: task.name })
2066
+ ] }) : /* @__PURE__ */ jsxs13(Fragment4, { children: [
2067
+ /* @__PURE__ */ jsx16(Text16, { children: " " }),
2068
+ /* @__PURE__ */ jsx16(Text16, { color: theme.yaml.value, children: task.id.padEnd(12) }),
2069
+ /* @__PURE__ */ jsx16(Text16, { color: statusColor, children: task.status.padEnd(14) }),
2070
+ /* @__PURE__ */ jsx16(Text16, { color: theme.table.fg, children: task.name })
1774
2071
  ] }) }, task.id);
1775
2072
  }
1776
2073
  function DependencyList({
@@ -1791,23 +2088,23 @@ function DependencyList({
1791
2088
  const relatedOffset = offset;
1792
2089
  offset += related.length;
1793
2090
  const duplicatesOffset = offset;
1794
- return /* @__PURE__ */ jsxs12(Box15, { flexDirection: "column", flexGrow: 1, borderStyle: "bold", borderColor: theme.borderFocus, children: [
1795
- /* @__PURE__ */ jsxs12(Box15, { gap: 0, children: [
1796
- /* @__PURE__ */ jsxs12(Text15, { color: theme.title, bold: true, children: [
2091
+ return /* @__PURE__ */ jsxs13(Box16, { flexDirection: "column", flexGrow: 1, borderStyle: "bold", borderColor: theme.borderFocus, children: [
2092
+ /* @__PURE__ */ jsxs13(Box16, { gap: 0, children: [
2093
+ /* @__PURE__ */ jsxs13(Text16, { color: theme.title, bold: true, children: [
1797
2094
  " ",
1798
2095
  "dependencies"
1799
2096
  ] }),
1800
- /* @__PURE__ */ jsx15(Text15, { color: theme.fg, children: "(" }),
1801
- /* @__PURE__ */ jsx15(Text15, { color: theme.titleHighlight, bold: true, children: task.name }),
1802
- /* @__PURE__ */ jsx15(Text15, { color: theme.fg, children: ")" })
2097
+ /* @__PURE__ */ jsx16(Text16, { color: theme.fg, children: "(" }),
2098
+ /* @__PURE__ */ jsx16(Text16, { color: theme.titleHighlight, bold: true, children: task.name }),
2099
+ /* @__PURE__ */ jsx16(Text16, { color: theme.fg, children: ")" })
1803
2100
  ] }),
1804
- /* @__PURE__ */ jsxs12(Box15, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
1805
- /* @__PURE__ */ jsxs12(Text15, { color: theme.table.headerFg, bold: true, children: [
2101
+ /* @__PURE__ */ jsxs13(Box16, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
2102
+ /* @__PURE__ */ jsxs13(Text16, { color: theme.table.headerFg, bold: true, children: [
1806
2103
  "BLOCKED BY (",
1807
2104
  blockers.length,
1808
2105
  ")"
1809
2106
  ] }),
1810
- blockers.length === 0 ? /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: " No blockers" }) : blockers.map((t, i) => /* @__PURE__ */ jsx15(
2107
+ blockers.length === 0 ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: " No blockers" }) : blockers.map((t, i) => /* @__PURE__ */ jsx16(
1811
2108
  TaskRow,
1812
2109
  {
1813
2110
  task: t,
@@ -1817,13 +2114,13 @@ function DependencyList({
1817
2114
  t.id
1818
2115
  ))
1819
2116
  ] }),
1820
- /* @__PURE__ */ jsxs12(Box15, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
1821
- /* @__PURE__ */ jsxs12(Text15, { color: theme.table.headerFg, bold: true, children: [
2117
+ /* @__PURE__ */ jsxs13(Box16, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
2118
+ /* @__PURE__ */ jsxs13(Text16, { color: theme.table.headerFg, bold: true, children: [
1822
2119
  "BLOCKS (",
1823
2120
  dependents.length,
1824
2121
  ")"
1825
2122
  ] }),
1826
- dependents.length === 0 ? /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: " No dependents" }) : dependents.map((t, i) => /* @__PURE__ */ jsx15(
2123
+ dependents.length === 0 ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: " No dependents" }) : dependents.map((t, i) => /* @__PURE__ */ jsx16(
1827
2124
  TaskRow,
1828
2125
  {
1829
2126
  task: t,
@@ -1833,13 +2130,13 @@ function DependencyList({
1833
2130
  t.id
1834
2131
  ))
1835
2132
  ] }),
1836
- /* @__PURE__ */ jsxs12(Box15, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
1837
- /* @__PURE__ */ jsxs12(Text15, { color: theme.table.headerFg, bold: true, children: [
2133
+ /* @__PURE__ */ jsxs13(Box16, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
2134
+ /* @__PURE__ */ jsxs13(Text16, { color: theme.table.headerFg, bold: true, children: [
1838
2135
  "RELATES TO (",
1839
2136
  related.length,
1840
2137
  ")"
1841
2138
  ] }),
1842
- related.length === 0 ? /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: " No related tasks" }) : related.map((t, i) => /* @__PURE__ */ jsx15(
2139
+ related.length === 0 ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: " No related tasks" }) : related.map((t, i) => /* @__PURE__ */ jsx16(
1843
2140
  TaskRow,
1844
2141
  {
1845
2142
  task: t,
@@ -1849,13 +2146,13 @@ function DependencyList({
1849
2146
  t.id
1850
2147
  ))
1851
2148
  ] }),
1852
- /* @__PURE__ */ jsxs12(Box15, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
1853
- /* @__PURE__ */ jsxs12(Text15, { color: theme.table.headerFg, bold: true, children: [
2149
+ /* @__PURE__ */ jsxs13(Box16, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
2150
+ /* @__PURE__ */ jsxs13(Text16, { color: theme.table.headerFg, bold: true, children: [
1854
2151
  "DUPLICATES (",
1855
2152
  duplicates.length,
1856
2153
  ")"
1857
2154
  ] }),
1858
- duplicates.length === 0 ? /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: " No duplicate tasks" }) : duplicates.map((t, i) => /* @__PURE__ */ jsx15(
2155
+ duplicates.length === 0 ? /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: " No duplicate tasks" }) : duplicates.map((t, i) => /* @__PURE__ */ jsx16(
1859
2156
  TaskRow,
1860
2157
  {
1861
2158
  task: t,
@@ -1865,19 +2162,19 @@ function DependencyList({
1865
2162
  t.id
1866
2163
  ))
1867
2164
  ] }),
1868
- /* @__PURE__ */ jsx15(Box15, { flexGrow: 1 }),
1869
- isAddingDep && /* @__PURE__ */ jsxs12(Box15, { borderStyle: "round", borderColor: theme.prompt, paddingX: 1, children: [
1870
- /* @__PURE__ */ jsx15(Text15, { color: theme.prompt, children: "depends on (id or id:type): " }),
1871
- /* @__PURE__ */ jsx15(Text15, { color: theme.prompt, children: addDepInput }),
1872
- /* @__PURE__ */ jsx15(Text15, { color: theme.promptSuggest, children: "_" })
2165
+ /* @__PURE__ */ jsx16(Box16, { flexGrow: 1 }),
2166
+ isAddingDep && /* @__PURE__ */ jsxs13(Box16, { borderStyle: "round", borderColor: theme.prompt, paddingX: 1, children: [
2167
+ /* @__PURE__ */ jsx16(Text16, { color: theme.prompt, children: "depends on (id or id:type): " }),
2168
+ /* @__PURE__ */ jsx16(Text16, { color: theme.prompt, children: addDepInput }),
2169
+ /* @__PURE__ */ jsx16(Text16, { color: theme.promptSuggest, children: "_" })
1873
2170
  ] }),
1874
- /* @__PURE__ */ jsx15(Box15, { paddingX: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "a: add dep (id or id:relates-to) | x: remove selected | enter: go to task | esc: back" }) })
2171
+ /* @__PURE__ */ jsx16(Box16, { paddingX: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "a: add dep (id or id:relates-to) | x: remove selected | enter: go to task | esc: back" }) })
1875
2172
  ] });
1876
2173
  }
1877
2174
 
1878
2175
  // src/tui/components/EpicPanel.tsx
1879
- import { Box as Box16, Text as Text16 } from "ink";
1880
- import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
2176
+ import { Box as Box17, Text as Text17 } from "ink";
2177
+ import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
1881
2178
  var PAGE_SIZE2 = 20;
1882
2179
  function EpicPanel({
1883
2180
  epics,
@@ -1890,34 +2187,34 @@ function EpicPanel({
1890
2187
  const currentPage = Math.floor(selectedIndex / PAGE_SIZE2);
1891
2188
  const viewStart = currentPage * PAGE_SIZE2;
1892
2189
  const visibleEpics = epics.slice(viewStart, viewStart + PAGE_SIZE2);
1893
- return /* @__PURE__ */ jsxs13(
1894
- Box16,
2190
+ return /* @__PURE__ */ jsxs14(
2191
+ Box17,
1895
2192
  {
1896
2193
  flexDirection: "column",
1897
2194
  width: 48,
1898
2195
  borderStyle: "bold",
1899
2196
  borderColor: isFocused ? theme.borderFocus : theme.border,
1900
2197
  children: [
1901
- /* @__PURE__ */ jsxs13(Box16, { children: [
1902
- /* @__PURE__ */ jsxs13(Text16, { color: theme.title, bold: true, children: [
2198
+ /* @__PURE__ */ jsxs14(Box17, { children: [
2199
+ /* @__PURE__ */ jsxs14(Text17, { color: theme.title, bold: true, children: [
1903
2200
  " ",
1904
2201
  "epics"
1905
2202
  ] }),
1906
- /* @__PURE__ */ jsxs13(Text16, { color: theme.titleCounter, bold: true, children: [
2203
+ /* @__PURE__ */ jsxs14(Text17, { color: theme.titleCounter, bold: true, children: [
1907
2204
  "[",
1908
2205
  epics.length,
1909
2206
  "]"
1910
2207
  ] }),
1911
- isReordering && /* @__PURE__ */ jsxs13(Text16, { color: theme.flash.warn, bold: true, children: [
2208
+ isReordering && /* @__PURE__ */ jsxs14(Text17, { color: theme.flash.warn, bold: true, children: [
1912
2209
  " ",
1913
2210
  "REORDER"
1914
2211
  ] }),
1915
- filterActive && /* @__PURE__ */ jsxs13(Text16, { color: theme.titleFilter, children: [
2212
+ filterActive && /* @__PURE__ */ jsxs14(Text17, { color: theme.titleFilter, children: [
1916
2213
  " *",
1917
2214
  selectedEpicIds.size
1918
2215
  ] })
1919
2216
  ] }),
1920
- /* @__PURE__ */ jsx16(Box16, { flexDirection: "column", flexGrow: 1, overflowY: "hidden", children: epics.length === 0 ? /* @__PURE__ */ jsx16(Box16, { paddingX: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "No epics" }) }) : visibleEpics.map((epic, i) => {
2217
+ /* @__PURE__ */ jsx17(Box17, { flexDirection: "column", flexGrow: 1, overflowY: "hidden", children: epics.length === 0 ? /* @__PURE__ */ jsx17(Box17, { paddingX: 1, children: /* @__PURE__ */ jsx17(Text17, { dimColor: true, children: "No epics" }) }) : visibleEpics.map((epic, i) => {
1921
2218
  const actualIndex = viewStart + i;
1922
2219
  const isSelected = actualIndex === selectedIndex && isFocused;
1923
2220
  const isChecked = selectedEpicIds.has(epic.id);
@@ -1925,21 +2222,21 @@ function EpicPanel({
1925
2222
  const statusColor = STATUS_COLOR[epic.status] ?? theme.table.fg;
1926
2223
  if (isSelected) {
1927
2224
  const cursorBg = isReordering ? theme.flash.warn : theme.table.cursorBg;
1928
- return /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsxs13(Text16, { backgroundColor: cursorBg, color: theme.table.cursorFg, bold: true, children: [
2225
+ return /* @__PURE__ */ jsx17(Box17, { children: /* @__PURE__ */ jsxs14(Text17, { backgroundColor: cursorBg, color: theme.table.cursorFg, bold: true, children: [
1929
2226
  isReordering ? "~ " : " ",
1930
2227
  marker,
1931
2228
  " ",
1932
2229
  epic.name
1933
2230
  ] }) }, epic.id);
1934
2231
  }
1935
- return /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsxs13(Text16, { color: isChecked ? theme.titleHighlight : statusColor, children: [
2232
+ return /* @__PURE__ */ jsx17(Box17, { children: /* @__PURE__ */ jsxs14(Text17, { color: isChecked ? theme.titleHighlight : statusColor, children: [
1936
2233
  " ",
1937
2234
  marker,
1938
2235
  " ",
1939
2236
  epic.name
1940
2237
  ] }) }, epic.id);
1941
2238
  }) }),
1942
- epics.length > PAGE_SIZE2 && /* @__PURE__ */ jsx16(Box16, { justifyContent: "flex-end", paddingRight: 1, children: /* @__PURE__ */ jsxs13(Text16, { dimColor: true, children: [
2239
+ epics.length > PAGE_SIZE2 && /* @__PURE__ */ jsx17(Box17, { justifyContent: "flex-end", paddingRight: 1, children: /* @__PURE__ */ jsxs14(Text17, { dimColor: true, children: [
1943
2240
  "[",
1944
2241
  viewStart + 1,
1945
2242
  "-",
@@ -1955,8 +2252,8 @@ function EpicPanel({
1955
2252
 
1956
2253
  // src/tui/components/EpicPicker.tsx
1957
2254
  import { useState as useState6 } from "react";
1958
- import { Box as Box17, Text as Text17, useInput as useInput6, useStdout as useStdout3 } from "ink";
1959
- import { Fragment as Fragment3, jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
2255
+ import { Box as Box18, Text as Text18, useInput as useInput6, useStdout as useStdout3 } from "ink";
2256
+ import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
1960
2257
  function EpicPicker({ epics, currentEpicId, onSelect, onCancel }) {
1961
2258
  const { stdout } = useStdout3();
1962
2259
  const termHeight = stdout.rows > 0 ? stdout.rows : 24;
@@ -2029,53 +2326,53 @@ function EpicPicker({ epics, currentEpicId, onSelect, onCancel }) {
2029
2326
  return;
2030
2327
  }
2031
2328
  });
2032
- return /* @__PURE__ */ jsxs14(Box17, { flexDirection: "column", borderStyle: "bold", borderColor: theme.borderFocus, flexGrow: 1, children: [
2033
- /* @__PURE__ */ jsxs14(Box17, { gap: 0, children: [
2034
- /* @__PURE__ */ jsxs14(Text17, { color: theme.title, bold: true, children: [
2329
+ return /* @__PURE__ */ jsxs15(Box18, { flexDirection: "column", borderStyle: "bold", borderColor: theme.borderFocus, flexGrow: 1, children: [
2330
+ /* @__PURE__ */ jsxs15(Box18, { gap: 0, children: [
2331
+ /* @__PURE__ */ jsxs15(Text18, { color: theme.title, bold: true, children: [
2035
2332
  " ",
2036
2333
  "assign to epic"
2037
2334
  ] }),
2038
- /* @__PURE__ */ jsxs14(Text17, { color: theme.titleCounter, bold: true, children: [
2335
+ /* @__PURE__ */ jsxs15(Text18, { color: theme.titleCounter, bold: true, children: [
2039
2336
  " ",
2040
2337
  "[",
2041
2338
  epics.length,
2042
2339
  "]"
2043
2340
  ] })
2044
2341
  ] }),
2045
- isSearching ? /* @__PURE__ */ jsxs14(Box17, { borderStyle: "round", borderColor: theme.prompt, paddingX: 1, children: [
2046
- /* @__PURE__ */ jsx17(Text17, { color: theme.prompt, children: "/" }),
2047
- /* @__PURE__ */ jsx17(Text17, { color: theme.prompt, children: searchQuery }),
2048
- /* @__PURE__ */ jsx17(Text17, { color: theme.promptSuggest, children: "_" })
2049
- ] }) : searchQuery ? /* @__PURE__ */ jsx17(Box17, { paddingX: 1, children: /* @__PURE__ */ jsxs14(Text17, { color: theme.titleFilter, children: [
2342
+ isSearching ? /* @__PURE__ */ jsxs15(Box18, { borderStyle: "round", borderColor: theme.prompt, paddingX: 1, children: [
2343
+ /* @__PURE__ */ jsx18(Text18, { color: theme.prompt, children: "/" }),
2344
+ /* @__PURE__ */ jsx18(Text18, { color: theme.prompt, children: searchQuery }),
2345
+ /* @__PURE__ */ jsx18(Text18, { color: theme.promptSuggest, children: "_" })
2346
+ ] }) : searchQuery ? /* @__PURE__ */ jsx18(Box18, { paddingX: 1, children: /* @__PURE__ */ jsxs15(Text18, { color: theme.titleFilter, children: [
2050
2347
  "/",
2051
2348
  searchQuery
2052
2349
  ] }) }) : null,
2053
- /* @__PURE__ */ jsx17(Box17, { paddingX: 1, children: /* @__PURE__ */ jsxs14(Text17, { color: theme.table.headerFg, bold: true, children: [
2350
+ /* @__PURE__ */ jsx18(Box18, { paddingX: 1, children: /* @__PURE__ */ jsxs15(Text18, { color: theme.table.headerFg, bold: true, children: [
2054
2351
  " ",
2055
2352
  "ID".padEnd(14),
2056
2353
  "STATUS".padEnd(14),
2057
2354
  "NAME"
2058
2355
  ] }) }),
2059
- filtered.length === 0 ? /* @__PURE__ */ jsx17(Box17, { paddingX: 2, paddingY: 1, children: /* @__PURE__ */ jsx17(Text17, { dimColor: true, children: "No epics match the filter" }) }) : visible.map((epic, i) => {
2356
+ filtered.length === 0 ? /* @__PURE__ */ jsx18(Box18, { paddingX: 2, paddingY: 1, children: /* @__PURE__ */ jsx18(Text18, { dimColor: true, children: "No epics match the filter" }) }) : visible.map((epic, i) => {
2060
2357
  const actualIndex = viewStart + i;
2061
2358
  const isCursor = actualIndex === cursorIndex;
2062
2359
  const isCurrent = epic.id === currentEpicId;
2063
2360
  const marker = isCurrent ? "* " : " ";
2064
2361
  const statusColor = STATUS_COLOR[epic.status] ?? theme.table.fg;
2065
- return /* @__PURE__ */ jsx17(Box17, { paddingX: 1, children: isCursor ? /* @__PURE__ */ jsxs14(Text17, { backgroundColor: theme.table.cursorBg, color: theme.table.cursorFg, bold: true, children: [
2362
+ return /* @__PURE__ */ jsx18(Box18, { paddingX: 1, children: isCursor ? /* @__PURE__ */ jsxs15(Text18, { backgroundColor: theme.table.cursorBg, color: theme.table.cursorFg, bold: true, children: [
2066
2363
  "> ",
2067
2364
  epic.id.padEnd(14),
2068
2365
  epic.status.padEnd(14),
2069
2366
  epic.name
2070
- ] }) : /* @__PURE__ */ jsxs14(Fragment3, { children: [
2071
- /* @__PURE__ */ jsx17(Text17, { color: isCurrent ? theme.titleHighlight : theme.table.fg, children: marker }),
2072
- /* @__PURE__ */ jsx17(Text17, { color: theme.yaml.value, children: epic.id.padEnd(14) }),
2073
- /* @__PURE__ */ jsx17(Text17, { color: statusColor, children: epic.status.padEnd(14) }),
2074
- /* @__PURE__ */ jsx17(Text17, { color: isCurrent ? theme.titleHighlight : theme.table.fg, children: epic.name })
2367
+ ] }) : /* @__PURE__ */ jsxs15(Fragment5, { children: [
2368
+ /* @__PURE__ */ jsx18(Text18, { color: isCurrent ? theme.titleHighlight : theme.table.fg, children: marker }),
2369
+ /* @__PURE__ */ jsx18(Text18, { color: theme.yaml.value, children: epic.id.padEnd(14) }),
2370
+ /* @__PURE__ */ jsx18(Text18, { color: statusColor, children: epic.status.padEnd(14) }),
2371
+ /* @__PURE__ */ jsx18(Text18, { color: isCurrent ? theme.titleHighlight : theme.table.fg, children: epic.name })
2075
2372
  ] }) }, epic.id);
2076
2373
  }),
2077
- /* @__PURE__ */ jsx17(Box17, { flexGrow: 1 }),
2078
- filtered.length > maxVisible && /* @__PURE__ */ jsx17(Box17, { justifyContent: "flex-end", paddingRight: 1, children: /* @__PURE__ */ jsxs14(Text17, { dimColor: true, children: [
2374
+ /* @__PURE__ */ jsx18(Box18, { flexGrow: 1 }),
2375
+ filtered.length > maxVisible && /* @__PURE__ */ jsx18(Box18, { justifyContent: "flex-end", paddingRight: 1, children: /* @__PURE__ */ jsxs15(Text18, { dimColor: true, children: [
2079
2376
  "[",
2080
2377
  viewStart + 1,
2081
2378
  "-",
@@ -2084,19 +2381,19 @@ function EpicPicker({ epics, currentEpicId, onSelect, onCancel }) {
2084
2381
  filtered.length,
2085
2382
  "]"
2086
2383
  ] }) }),
2087
- /* @__PURE__ */ jsx17(Box17, { paddingX: 1, children: /* @__PURE__ */ jsx17(Text17, { dimColor: true, children: "enter: assign | x: unassign | /: search | esc: cancel" }) })
2384
+ /* @__PURE__ */ jsx18(Box18, { paddingX: 1, children: /* @__PURE__ */ jsx18(Text18, { dimColor: true, children: "enter: assign | x: unassign | /: search | esc: cancel" }) })
2088
2385
  ] });
2089
2386
  }
2090
2387
 
2091
2388
  // src/tui/useAutoRefetch.ts
2092
- import { useEffect, useRef } from "react";
2389
+ import { useEffect as useEffect3, useRef as useRef4 } from "react";
2093
2390
  import { watchFile, unwatchFile } from "fs";
2094
2391
  var POLL_INTERVAL_MS = 1e3;
2095
2392
  var DEBOUNCE_MS = 200;
2096
2393
  function useAutoRefetch(dbPath, onRefetch) {
2097
- const callbackRef = useRef(onRefetch);
2394
+ const callbackRef = useRef4(onRefetch);
2098
2395
  callbackRef.current = onRefetch;
2099
- useEffect(() => {
2396
+ useEffect3(() => {
2100
2397
  let debounceTimer = null;
2101
2398
  const handleChange = () => {
2102
2399
  if (debounceTimer) clearTimeout(debounceTimer);
@@ -2124,7 +2421,7 @@ function useAutoRefetch(dbPath, onRefetch) {
2124
2421
  }
2125
2422
 
2126
2423
  // src/tui/components/App.tsx
2127
- import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
2424
+ import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
2128
2425
  var STATUS_CYCLE = [
2129
2426
  TaskStatus.Backlog,
2130
2427
  TaskStatus.Todo,
@@ -2133,12 +2430,12 @@ var STATUS_CYCLE = [
2133
2430
  TaskStatus.Done
2134
2431
  ];
2135
2432
  var EPIC_PANEL_WIDTH = 48;
2136
- function App({ container, initialProject }) {
2433
+ function App({ container, initialProject, latestVersion }) {
2137
2434
  const { exit } = useApp();
2138
2435
  const { stdout } = useStdout4();
2139
2436
  const [state, dispatch] = useReducer(appReducer, initialState);
2140
2437
  const [, setResizeTick] = useState7(0);
2141
- useEffect2(() => {
2438
+ useEffect4(() => {
2142
2439
  const onResize = () => {
2143
2440
  setResizeTick((n) => n + 1);
2144
2441
  };
@@ -2303,10 +2600,10 @@ function App({ container, initialProject }) {
2303
2600
  loadEpics();
2304
2601
  }, [loadProjects, loadTasks, loadEpics]);
2305
2602
  useAutoRefetch(container.dbPath, refetchAll);
2306
- useEffect2(() => {
2603
+ useEffect4(() => {
2307
2604
  loadProjects();
2308
2605
  }, [loadProjects]);
2309
- useEffect2(() => {
2606
+ useEffect4(() => {
2310
2607
  if (state.projects.length > 0 && !state.activeProject) {
2311
2608
  logger.info(`TUI.resolveProject: resolving initialProject=${initialProject ?? "(default)"}`);
2312
2609
  const result = container.projectService.resolveProject(initialProject);
@@ -2323,13 +2620,29 @@ function App({ container, initialProject }) {
2323
2620
  }
2324
2621
  }
2325
2622
  }, [state.projects, state.activeProject, initialProject, container]);
2326
- useEffect2(() => {
2623
+ const gitRemoteCheckedRef = useRef5(false);
2624
+ useEffect4(() => {
2625
+ if (state.projects.length > 0 && !gitRemoteCheckedRef.current && !initialProject) {
2626
+ gitRemoteCheckedRef.current = true;
2627
+ const remoteResult = detectGitRemote();
2628
+ if (remoteResult.ok && remoteResult.value) {
2629
+ const remote = remoteResult.value;
2630
+ const alreadyLinked = state.projects.some((p) => p.gitRemote?.equals(remote));
2631
+ const dismissed = isDismissedRemote(container.dismissedGitRemotesPath, remote);
2632
+ if (!alreadyLinked && !dismissed) {
2633
+ logger.info(`TUI.detectGitRemote: unlinked remote detected: ${remote.value}`);
2634
+ dispatch({ type: "SET_DETECTED_GIT_REMOTE", remote });
2635
+ }
2636
+ }
2637
+ }
2638
+ }, [state.projects, initialProject]);
2639
+ useEffect4(() => {
2327
2640
  if (state.activeProject) {
2328
2641
  loadTasks();
2329
2642
  loadEpics();
2330
2643
  }
2331
2644
  }, [state.activeProject, state.filter, loadTasks, loadEpics]);
2332
- useEffect2(() => {
2645
+ useEffect4(() => {
2333
2646
  if (state.flash) {
2334
2647
  const timer = setTimeout(() => {
2335
2648
  dispatch({ type: "CLEAR_FLASH" });
@@ -2361,11 +2674,20 @@ function App({ container, initialProject }) {
2361
2674
  }
2362
2675
  return;
2363
2676
  }
2677
+ if (state.detectedGitRemote && state.activeView === ViewType.TaskList) {
2678
+ if (input === "y") {
2679
+ dispatch({ type: "NAVIGATE_TO", view: ViewType.ProjectCreate });
2680
+ } else if (input === "n" || key.escape) {
2681
+ dismissRemote(container.dismissedGitRemotesPath, state.detectedGitRemote);
2682
+ dispatch({ type: "SET_DETECTED_GIT_REMOTE", remote: null });
2683
+ }
2684
+ return;
2685
+ }
2364
2686
  if (state.activeView === ViewType.Help) {
2365
2687
  dispatch({ type: "GO_BACK" });
2366
2688
  return;
2367
2689
  }
2368
- if (state.activeView === ViewType.TaskCreate || state.activeView === ViewType.TaskEdit || state.activeView === ViewType.ProjectSelector || state.activeView === ViewType.ProjectCreate || state.activeView === ViewType.ProjectLink || state.activeView === ViewType.EpicPicker) {
2690
+ if (state.activeView === ViewType.TaskCreate || state.activeView === ViewType.TaskEdit || state.activeView === ViewType.ProjectSelector || state.activeView === ViewType.ProjectCreate || state.activeView === ViewType.ProjectEdit || state.activeView === ViewType.ProjectLink || state.activeView === ViewType.EpicPicker) {
2369
2691
  return;
2370
2692
  }
2371
2693
  if (state.activeView === ViewType.DependencyList && state.isAddingDep) {
@@ -2511,7 +2833,8 @@ function App({ container, initialProject }) {
2511
2833
  if (key.tab && state.activeView === ViewType.TaskList) {
2512
2834
  const panels = previewTask ? ["epic", "list", "detail"] : ["epic", "list"];
2513
2835
  const curIdx = panels.indexOf(state.focusedPanel);
2514
- const nextPanel = panels[(curIdx + 1) % panels.length] ?? "list";
2836
+ const delta = key.shift ? -1 : 1;
2837
+ const nextPanel = panels[(curIdx + delta + panels.length) % panels.length] ?? "list";
2515
2838
  dispatch({ type: "SET_PANEL_FOCUS", panel: nextPanel });
2516
2839
  return;
2517
2840
  }
@@ -2938,35 +3261,57 @@ ${state.selectedTask.additionalRequirements}`;
2938
3261
  const handleProjectCreate = useCallback2(() => {
2939
3262
  dispatch({ type: "NAVIGATE_TO", view: ViewType.ProjectCreate });
2940
3263
  }, []);
3264
+ const handleProjectEdit = useCallback2((project) => {
3265
+ dispatch({ type: "SET_EDITING_PROJECT", project });
3266
+ dispatch({ type: "NAVIGATE_TO", view: ViewType.ProjectEdit });
3267
+ }, []);
2941
3268
  const handleProjectFormSave = useCallback2(
2942
3269
  (data) => {
2943
- const result = container.projectService.createProject({
3270
+ const editing = state.activeView === ViewType.ProjectEdit ? state.editingProject : null;
3271
+ const result = editing ? container.projectService.updateProject(editing.id, {
3272
+ name: data.name,
3273
+ description: data.description,
3274
+ isDefault: data.isDefault,
3275
+ gitRemote: data.gitRemote || null
3276
+ }) : container.projectService.createProject({
2944
3277
  name: data.name,
2945
3278
  key: data.key || void 0,
2946
3279
  description: data.description || void 0,
2947
- isDefault: data.isDefault
3280
+ isDefault: data.isDefault,
3281
+ gitRemote: data.gitRemote || void 0
2948
3282
  });
2949
3283
  if (result.ok) {
2950
- logger.info(`TUI.createProject: created key=${result.value.key} name=${result.value.name}`);
3284
+ const verb = editing ? "updated" : "created";
3285
+ logger.info(
3286
+ `TUI.${editing ? "editProject" : "createProject"}: ${verb} key=${result.value.key} name=${result.value.name}`
3287
+ );
2951
3288
  dispatch({
2952
3289
  type: "FLASH",
2953
- message: `Project created: ${result.value.name}`,
3290
+ message: `Project ${verb}: ${result.value.name}`,
2954
3291
  level: "info"
2955
3292
  });
2956
- dispatch({ type: "SET_ACTIVE_PROJECT", project: result.value });
2957
- dispatch({ type: "GO_BACK" });
3293
+ if (editing && state.activeProject?.id === result.value.id) {
3294
+ dispatch({ type: "SET_ACTIVE_PROJECT", project: result.value });
3295
+ }
3296
+ if (!editing) {
3297
+ dispatch({ type: "SET_ACTIVE_PROJECT", project: result.value });
3298
+ dispatch({ type: "GO_BACK" });
3299
+ }
2958
3300
  dispatch({ type: "GO_BACK" });
2959
3301
  loadProjects();
2960
3302
  } else {
2961
- logger.error("TUI.createProject: failed", result.error);
3303
+ logger.error(`TUI.${editing ? "editProject" : "createProject"}: failed`, result.error);
2962
3304
  dispatch({ type: "FLASH", message: result.error.message, level: "error" });
2963
3305
  }
2964
3306
  },
2965
- [container, loadProjects]
3307
+ [container, state.activeView, state.editingProject, state.activeProject, loadProjects]
2966
3308
  );
2967
3309
  const handleProjectFormCancel = useCallback2(() => {
3310
+ if (state.activeView === ViewType.ProjectCreate && state.detectedGitRemote) {
3311
+ dismissRemote(container.dismissedGitRemotesPath, state.detectedGitRemote);
3312
+ }
2968
3313
  dispatch({ type: "GO_BACK" });
2969
- }, []);
3314
+ }, [state.activeView, state.detectedGitRemote, container]);
2970
3315
  const handleProjectLink = useCallback2((project) => {
2971
3316
  dispatch({ type: "SET_LINKING_PROJECT", project });
2972
3317
  dispatch({ type: "NAVIGATE_TO", view: ViewType.ProjectLink });
@@ -2977,11 +3322,11 @@ ${state.selectedTask.additionalRequirements}`;
2977
3322
  const result = container.projectService.linkGitRemote(state.linkingProject.id, remote);
2978
3323
  if (result.ok) {
2979
3324
  logger.info(
2980
- `TUI.linkGitRemote: linked project=${state.linkingProject.id} remote=${result.value.gitRemote}`
3325
+ `TUI.linkGitRemote: linked project=${state.linkingProject.id} remote=${result.value.gitRemote?.value}`
2981
3326
  );
2982
3327
  dispatch({
2983
3328
  type: "FLASH",
2984
- message: `Linked to: ${result.value.gitRemote}`,
3329
+ message: `Linked to: ${result.value.gitRemote?.value}`,
2985
3330
  level: "info"
2986
3331
  });
2987
3332
  dispatch({ type: "GO_BACK" });
@@ -3009,8 +3354,8 @@ ${state.selectedTask.additionalRequirements}`;
3009
3354
  const handleLinkDetect = useCallback2(() => {
3010
3355
  const result = detectGitRemote();
3011
3356
  if (result.ok && result.value) {
3012
- dispatch({ type: "FLASH", message: `Detected: ${result.value}`, level: "info" });
3013
- return result.value;
3357
+ dispatch({ type: "FLASH", message: `Detected: ${result.value.value}`, level: "info" });
3358
+ return result.value.value;
3014
3359
  }
3015
3360
  dispatch({ type: "FLASH", message: "No git remote detected in cwd", level: "warn" });
3016
3361
  return null;
@@ -3039,17 +3384,17 @@ ${state.selectedTask.additionalRequirements}`;
3039
3384
  return result.ok ? result.value : [];
3040
3385
  }, [container, state.activeProject, state.tasks]);
3041
3386
  const previewTaskId = previewTask?.id ?? null;
3042
- useEffect2(() => {
3387
+ useEffect4(() => {
3043
3388
  if (state.activeView === ViewType.TaskList && previewTaskId) {
3044
3389
  loadDeps(previewTaskId);
3045
3390
  }
3046
3391
  }, [state.activeView, previewTaskId, loadDeps]);
3047
- return /* @__PURE__ */ jsxs15(Box18, { flexDirection: "column", height: stdout.rows, children: [
3048
- /* @__PURE__ */ jsx18(Header, { state }),
3049
- /* @__PURE__ */ jsxs15(Box18, { flexDirection: "column", flexGrow: 1, overflowY: "hidden", children: [
3050
- state.confirmDelete && /* @__PURE__ */ jsx18(ConfirmDialog, { task: state.confirmDelete }),
3051
- !state.confirmDelete && state.activeView === ViewType.TaskList && /* @__PURE__ */ jsxs15(Box18, { flexDirection: "row", flexGrow: 1, children: [
3052
- /* @__PURE__ */ jsx18(
3392
+ return /* @__PURE__ */ jsxs16(Box19, { flexDirection: "column", height: stdout.rows, children: [
3393
+ /* @__PURE__ */ jsx19(Header, { state, latestVersion }),
3394
+ /* @__PURE__ */ jsxs16(Box19, { flexDirection: "column", flexGrow: 1, overflowY: "hidden", children: [
3395
+ state.confirmDelete && /* @__PURE__ */ jsx19(ConfirmDialog, { task: state.confirmDelete }),
3396
+ !state.confirmDelete && state.activeView === ViewType.TaskList && (state.detectedGitRemote ? /* @__PURE__ */ jsx19(DetectedProjectDialog, { remote: state.detectedGitRemote }) : /* @__PURE__ */ jsxs16(Box19, { flexDirection: "row", flexGrow: 1, children: [
3397
+ /* @__PURE__ */ jsx19(
3053
3398
  EpicPanel,
3054
3399
  {
3055
3400
  epics: state.epics,
@@ -3059,7 +3404,7 @@ ${state.selectedTask.additionalRequirements}`;
3059
3404
  isReordering: state.isEpicReordering
3060
3405
  }
3061
3406
  ),
3062
- /* @__PURE__ */ jsx18(Box18, { width: taskListWidth, children: /* @__PURE__ */ jsx18(
3407
+ /* @__PURE__ */ jsx19(Box19, { width: taskListWidth, children: /* @__PURE__ */ jsx19(
3063
3408
  TaskList,
3064
3409
  {
3065
3410
  tasks: state.tasks,
@@ -3080,7 +3425,7 @@ ${state.selectedTask.additionalRequirements}`;
3080
3425
  epicFilterActive: state.selectedEpicIds.size > 0
3081
3426
  }
3082
3427
  ) }),
3083
- /* @__PURE__ */ jsx18(Box18, { width: taskDetailWidth, children: previewTask ? /* @__PURE__ */ jsx18(
3428
+ /* @__PURE__ */ jsx19(Box19, { width: taskDetailWidth, children: previewTask ? /* @__PURE__ */ jsx19(
3084
3429
  TaskDetail,
3085
3430
  {
3086
3431
  task: previewTask,
@@ -3091,24 +3436,24 @@ ${state.selectedTask.additionalRequirements}`;
3091
3436
  isFocused: state.focusedPanel === "detail",
3092
3437
  scrollOffset: state.detailScrollOffset
3093
3438
  }
3094
- ) : /* @__PURE__ */ jsxs15(
3095
- Box18,
3439
+ ) : /* @__PURE__ */ jsxs16(
3440
+ Box19,
3096
3441
  {
3097
3442
  flexDirection: "column",
3098
3443
  flexGrow: 1,
3099
3444
  borderStyle: "bold",
3100
3445
  borderColor: theme.border,
3101
3446
  children: [
3102
- /* @__PURE__ */ jsx18(Box18, { children: /* @__PURE__ */ jsxs15(Text18, { color: theme.title, bold: true, children: [
3447
+ /* @__PURE__ */ jsx19(Box19, { children: /* @__PURE__ */ jsxs16(Text19, { color: theme.title, bold: true, children: [
3103
3448
  " ",
3104
3449
  "detail"
3105
3450
  ] }) }),
3106
- /* @__PURE__ */ jsx18(Box18, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsx18(Text18, { dimColor: true, children: "No task selected" }) })
3451
+ /* @__PURE__ */ jsx19(Box19, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsx19(Text19, { dimColor: true, children: "No task selected" }) })
3107
3452
  ]
3108
3453
  }
3109
3454
  ) })
3110
- ] }),
3111
- !state.confirmDelete && state.activeView === ViewType.TaskDetail && state.selectedTask && /* @__PURE__ */ jsx18(
3455
+ ] })),
3456
+ !state.confirmDelete && state.activeView === ViewType.TaskDetail && state.selectedTask && /* @__PURE__ */ jsx19(
3112
3457
  TaskDetail,
3113
3458
  {
3114
3459
  task: state.selectedTask,
@@ -3119,7 +3464,7 @@ ${state.selectedTask.additionalRequirements}`;
3119
3464
  scrollOffset: state.detailScrollOffset
3120
3465
  }
3121
3466
  ),
3122
- !state.confirmDelete && state.activeView === ViewType.DependencyList && state.selectedTask && /* @__PURE__ */ jsx18(
3467
+ !state.confirmDelete && state.activeView === ViewType.DependencyList && state.selectedTask && /* @__PURE__ */ jsx19(
3123
3468
  DependencyList,
3124
3469
  {
3125
3470
  task: state.selectedTask,
@@ -3132,7 +3477,7 @@ ${state.selectedTask.additionalRequirements}`;
3132
3477
  addDepInput: state.addDepInput
3133
3478
  }
3134
3479
  ),
3135
- !state.confirmDelete && (state.activeView === ViewType.TaskCreate || state.activeView === ViewType.TaskEdit) && /* @__PURE__ */ jsx18(
3480
+ !state.confirmDelete && (state.activeView === ViewType.TaskCreate || state.activeView === ViewType.TaskEdit) && /* @__PURE__ */ jsx19(
3136
3481
  TaskForm,
3137
3482
  {
3138
3483
  editingTask: state.activeView === ViewType.TaskEdit ? state.selectedTask : null,
@@ -3142,7 +3487,7 @@ ${state.selectedTask.additionalRequirements}`;
3142
3487
  onCancel: handleFormCancel
3143
3488
  }
3144
3489
  ),
3145
- !state.confirmDelete && state.activeView === ViewType.EpicPicker && state.selectedTask && /* @__PURE__ */ jsx18(
3490
+ !state.confirmDelete && state.activeView === ViewType.EpicPicker && state.selectedTask && /* @__PURE__ */ jsx19(
3146
3491
  EpicPicker,
3147
3492
  {
3148
3493
  epics: state.epics,
@@ -3151,20 +3496,36 @@ ${state.selectedTask.additionalRequirements}`;
3151
3496
  onCancel: handleEpicPickerCancel
3152
3497
  }
3153
3498
  ),
3154
- !state.confirmDelete && state.activeView === ViewType.ProjectSelector && /* @__PURE__ */ jsx18(
3499
+ !state.confirmDelete && state.activeView === ViewType.ProjectSelector && /* @__PURE__ */ jsx19(
3155
3500
  ProjectSelector,
3156
3501
  {
3157
3502
  projects: state.projects,
3158
3503
  activeProject: state.activeProject,
3159
3504
  onSelect: handleProjectSelect,
3160
3505
  onCreate: handleProjectCreate,
3506
+ onEdit: handleProjectEdit,
3161
3507
  onSetDefault: handleSetDefault,
3162
3508
  onLink: handleProjectLink,
3163
3509
  onCancel: handleProjectCancel
3164
3510
  }
3165
3511
  ),
3166
- !state.confirmDelete && state.activeView === ViewType.ProjectCreate && /* @__PURE__ */ jsx18(ProjectForm, { onSave: handleProjectFormSave, onCancel: handleProjectFormCancel }),
3167
- !state.confirmDelete && state.activeView === ViewType.ProjectLink && state.linkingProject && /* @__PURE__ */ jsx18(
3512
+ !state.confirmDelete && state.activeView === ViewType.ProjectCreate && /* @__PURE__ */ jsx19(
3513
+ ProjectForm,
3514
+ {
3515
+ initialGitRemote: state.detectedGitRemote ?? void 0,
3516
+ onSave: handleProjectFormSave,
3517
+ onCancel: handleProjectFormCancel
3518
+ }
3519
+ ),
3520
+ !state.confirmDelete && state.activeView === ViewType.ProjectEdit && state.editingProject && /* @__PURE__ */ jsx19(
3521
+ ProjectForm,
3522
+ {
3523
+ editingProject: state.editingProject,
3524
+ onSave: handleProjectFormSave,
3525
+ onCancel: handleProjectFormCancel
3526
+ }
3527
+ ),
3528
+ !state.confirmDelete && state.activeView === ViewType.ProjectLink && state.linkingProject && /* @__PURE__ */ jsx19(
3168
3529
  ProjectLinkForm,
3169
3530
  {
3170
3531
  project: state.linkingProject,
@@ -3174,22 +3535,25 @@ ${state.selectedTask.additionalRequirements}`;
3174
3535
  onCancel: handleLinkCancel
3175
3536
  }
3176
3537
  ),
3177
- !state.confirmDelete && state.activeView === ViewType.Help && /* @__PURE__ */ jsx18(HelpOverlay, {})
3538
+ !state.confirmDelete && state.activeView === ViewType.Help && /* @__PURE__ */ jsx19(HelpOverlay, {})
3178
3539
  ] }),
3179
- /* @__PURE__ */ jsx18(Crumbs, { breadcrumbs: state.breadcrumbs }),
3180
- state.flash && /* @__PURE__ */ jsx18(FlashMessage, { message: state.flash.message, level: state.flash.level })
3540
+ /* @__PURE__ */ jsx19(Crumbs, { breadcrumbs: state.breadcrumbs }),
3541
+ state.flash && /* @__PURE__ */ jsx19(FlashMessage, { message: state.flash.message, level: state.flash.level })
3181
3542
  ] });
3182
3543
  }
3183
3544
 
3184
3545
  // src/tui/index.tsx
3185
- import { jsx as jsx19 } from "react/jsx-runtime";
3186
- async function launchTUI(container, initialProject) {
3187
- const instance = render(/* @__PURE__ */ jsx19(App, { container, initialProject }), {
3188
- exitOnCtrlC: true
3189
- });
3546
+ import { jsx as jsx20 } from "react/jsx-runtime";
3547
+ async function launchTUI(container, initialProject, latestVersion) {
3548
+ const instance = render(
3549
+ /* @__PURE__ */ jsx20(App, { container, initialProject, latestVersion }),
3550
+ {
3551
+ exitOnCtrlC: true
3552
+ }
3553
+ );
3190
3554
  await instance.waitUntilExit();
3191
3555
  }
3192
3556
  export {
3193
3557
  launchTUI
3194
3558
  };
3195
- //# sourceMappingURL=tui-24ZW56Q6.js.map
3559
+ //# sourceMappingURL=tui-NCL4RFFD.js.map