@tomkapa/tayto 0.6.0 → 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
 
@@ -513,7 +522,8 @@ function getKeyHints(state) {
513
522
  }
514
523
  if (activeView === ViewType.TaskCreate || activeView === ViewType.TaskEdit) {
515
524
  return [
516
- { key: "tab", desc: "next field" },
525
+ { key: "\u2191\u2193/tab", desc: "navigate" },
526
+ { key: "\u2190\u2192", desc: "cursor" },
517
527
  { key: "ctrl+s", desc: "save" },
518
528
  { key: "esc", desc: "cancel" }
519
529
  ];
@@ -522,6 +532,7 @@ function getKeyHints(state) {
522
532
  return [
523
533
  { key: "j/k", desc: "nav" },
524
534
  { key: "enter", desc: "select" },
535
+ { key: "e", desc: "edit" },
525
536
  { key: "c", desc: "create" },
526
537
  { key: "l", desc: "link" },
527
538
  { key: "d", desc: "default" },
@@ -986,8 +997,8 @@ ${task.additionalRequirements}`;
986
997
  }
987
998
 
988
999
  // src/tui/components/TaskForm.tsx
989
- import { useState as useState2, useCallback } from "react";
990
- 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";
991
1002
 
992
1003
  // src/tui/editor.ts
993
1004
  import { writeFileSync as writeFileSync2, readFileSync, unlinkSync, mkdtempSync as mkdtempSync2 } from "fs";
@@ -997,15 +1008,17 @@ import { spawnSync } from "child_process";
997
1008
  function getEditor() {
998
1009
  return process.env["EDITOR"] ?? process.env["VISUAL"] ?? "vi";
999
1010
  }
1000
- function openInEditor(content, filename) {
1011
+ function openInEditor(content, filename, options) {
1001
1012
  const dir = mkdtempSync2(join2(tmpdir2(), "task-"));
1002
1013
  const filepath = join2(dir, filename);
1003
1014
  writeFileSync2(filepath, content, "utf-8");
1004
1015
  const editor = getEditor();
1016
+ options?.beforeOpen?.();
1005
1017
  const result = spawnSync(editor, [filepath], {
1006
1018
  stdio: "inherit",
1007
1019
  shell: true
1008
1020
  });
1021
+ options?.afterOpen?.();
1009
1022
  if (result.status !== 0) {
1010
1023
  try {
1011
1024
  unlinkSync(filepath);
@@ -1222,7 +1235,7 @@ function TaskPicker({ tasks, excludeIds, initialSelection, onConfirm, onCancel }
1222
1235
  }
1223
1236
 
1224
1237
  // src/tui/components/TaskForm.tsx
1225
- 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";
1226
1239
  var FIELDS = [
1227
1240
  { label: "Name", key: "name", type: "inline" },
1228
1241
  { label: "Type", key: "type", type: "select", options: TYPE_VALUES },
@@ -1252,23 +1265,42 @@ function TaskForm({ editingTask, allTasks, initialDeps, onSave, onCancel }) {
1252
1265
  technicalNotes: editingTask?.technicalNotes ?? "",
1253
1266
  additionalRequirements: editingTask?.additionalRequirements ?? ""
1254
1267
  });
1268
+ const [cursorPos, setCursorPos] = useState2(() => (editingTask?.name ?? "").length);
1269
+ const cursorRef = useRef(cursorPos);
1270
+ cursorRef.current = cursorPos;
1255
1271
  const [editorActive, setEditorActive] = useState2(false);
1256
1272
  const [pickerActive, setPickerActive] = useState2(false);
1257
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]);
1258
1283
  const currentField = FIELDS[focusIndex];
1259
1284
  const launchEditor = useCallback(
1260
1285
  (field2) => {
1261
1286
  setEditorActive(true);
1262
1287
  setTimeout(() => {
1263
1288
  const content = values[field2.key] ?? "";
1264
- 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
+ });
1265
1297
  if (result !== null) {
1266
1298
  setValues((v) => ({ ...v, [field2.key]: result }));
1267
1299
  }
1268
1300
  setEditorActive(false);
1269
1301
  }, 50);
1270
1302
  },
1271
- [values]
1303
+ [values, setRawMode]
1272
1304
  );
1273
1305
  const handlePickerConfirm = useCallback((selected) => {
1274
1306
  setPickedDeps(selected);
@@ -1308,32 +1340,49 @@ function TaskForm({ editingTask, allTasks, initialDeps, onSave, onCancel }) {
1308
1340
  }
1309
1341
  return;
1310
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
+ }
1311
1351
  if (currentField.type === "inline") {
1312
- const currentValue = values[currentField.key] ?? "";
1313
- if (key.backspace || key.delete) {
1314
- 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
+ }
1315
1366
  } else if (key.return) {
1316
1367
  setFocusIndex((i) => Math.min(FIELDS.length - 1, i + 1));
1317
1368
  } else if (input && !key.ctrl && !key.meta) {
1318
- 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);
1319
1376
  }
1320
1377
  }
1321
1378
  if (currentField.type === "picker") {
1322
1379
  if (key.return) {
1323
1380
  setPickerActive(true);
1324
- } else if (key.downArrow) {
1325
- setFocusIndex((i) => Math.min(FIELDS.length - 1, i + 1));
1326
- } else if (key.upArrow) {
1327
- setFocusIndex((i) => Math.max(0, i - 1));
1328
1381
  }
1329
1382
  }
1330
1383
  if (currentField.type === "editor") {
1331
1384
  if (key.return) {
1332
1385
  launchEditor(currentField);
1333
- } else if (key.downArrow) {
1334
- setFocusIndex((i) => Math.min(FIELDS.length - 1, i + 1));
1335
- } else if (key.upArrow) {
1336
- setFocusIndex((i) => Math.max(0, i - 1));
1337
1386
  }
1338
1387
  }
1339
1388
  if (currentField.type === "select") {
@@ -1374,37 +1423,37 @@ function TaskForm({ editingTask, allTasks, initialDeps, onSave, onCancel }) {
1374
1423
  /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", paddingX: 1, paddingY: 0, children: FIELDS.map((field2, i) => {
1375
1424
  const isFocused = i === focusIndex;
1376
1425
  const value = field2.key === "dependsOn" ? depSummary : values[field2.key] ?? "";
1377
- const displayValue = value;
1378
1426
  return /* @__PURE__ */ jsxs6(Box9, { gap: 1, children: [
1379
1427
  /* @__PURE__ */ jsxs6(Text9, { color: isFocused ? theme.dialog.label : theme.yaml.key, bold: isFocused, children: [
1380
1428
  isFocused ? ">" : " ",
1381
1429
  " ",
1382
1430
  field2.label.padEnd(14)
1383
1431
  ] }),
1384
- field2.type === "inline" && /* @__PURE__ */ jsxs6(Text9, { color: isFocused ? theme.yaml.value : theme.table.fg, children: [
1385
- displayValue,
1386
- isFocused ? /* @__PURE__ */ jsx9(Text9, { color: theme.titleHighlight, children: "_" }) : ""
1387
- ] }),
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 }),
1388
1437
  field2.type === "picker" && /* @__PURE__ */ jsxs6(Text9, { children: [
1389
- 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" }),
1390
1439
  isFocused && /* @__PURE__ */ jsx9(Text9, { color: theme.menu.key, children: " [enter: open picker]" })
1391
1440
  ] }),
1392
1441
  field2.type === "editor" && /* @__PURE__ */ jsxs6(Text9, { children: [
1393
- displayValue ? /* @__PURE__ */ jsxs6(Text9, { color: theme.status.added, children: [
1394
- displayValue.split("\n")[0]?.slice(0, 50),
1395
- 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") ? "..." : ""
1396
1445
  ] }) : /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: isFocused ? "press enter" : "empty" }),
1397
1446
  isFocused && /* @__PURE__ */ jsx9(Text9, { color: theme.menu.key, children: " [enter: $EDITOR]" })
1398
1447
  ] }),
1399
1448
  field2.type === "select" && /* @__PURE__ */ jsxs6(Text9, { color: isFocused ? theme.yaml.value : theme.table.fg, children: [
1400
1449
  isFocused ? "< " : " ",
1401
- displayValue,
1450
+ value,
1402
1451
  isFocused ? " >" : ""
1403
1452
  ] })
1404
1453
  ] }, field2.key);
1405
1454
  }) }),
1406
1455
  /* @__PURE__ */ jsx9(Box9, { flexGrow: 1 }),
1407
- /* @__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" }) }),
1408
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" }) })
1409
1458
  ] });
1410
1459
  }
@@ -1418,6 +1467,7 @@ function ProjectSelector({
1418
1467
  activeProject,
1419
1468
  onSelect,
1420
1469
  onCreate,
1470
+ onEdit,
1421
1471
  onSetDefault,
1422
1472
  onLink,
1423
1473
  onCancel
@@ -1443,6 +1493,13 @@ function ProjectSelector({
1443
1493
  onCreate();
1444
1494
  return;
1445
1495
  }
1496
+ if (input === "e") {
1497
+ const project = projects[selectedIndex];
1498
+ if (project) {
1499
+ onEdit(project);
1500
+ }
1501
+ return;
1502
+ }
1446
1503
  if (input === "d") {
1447
1504
  const project = projects[selectedIndex];
1448
1505
  if (project) {
@@ -1487,7 +1544,7 @@ function ProjectSelector({
1487
1544
  const activeMarker = isActive ? "*" : " ";
1488
1545
  const defaultMarker = project.isDefault ? "D" : " ";
1489
1546
  const marker = `${activeMarker}${defaultMarker}`;
1490
- const remoteDisplay = (project.gitRemote ?? "").slice(0, 38).padEnd(40);
1547
+ const remoteDisplay = (project.gitRemote?.value ?? "").slice(0, 38).padEnd(40);
1491
1548
  if (isSelected) {
1492
1549
  return /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsxs7(Text10, { backgroundColor: theme.table.cursorBg, color: theme.table.cursorFg, bold: true, children: [
1493
1550
  marker,
@@ -1508,28 +1565,53 @@ function ProjectSelector({
1508
1565
  ] }, project.id);
1509
1566
  }),
1510
1567
  /* @__PURE__ */ jsx10(Box10, { flexGrow: 1 }),
1511
- /* @__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" }) })
1512
1569
  ] });
1513
1570
  }
1514
1571
 
1515
1572
  // src/tui/components/ProjectForm.tsx
1516
- import { useState as useState4 } from "react";
1573
+ import { useState as useState4, useEffect as useEffect2, useRef as useRef2 } from "react";
1517
1574
  import { Box as Box11, Text as Text11, useInput as useInput4 } from "ink";
1518
- 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";
1519
1576
  var FIELDS2 = [
1520
1577
  { label: "Name", key: "name", type: "inline" },
1521
1578
  { label: "Key", key: "key", type: "inline" },
1522
1579
  { label: "Description", key: "description", type: "inline" },
1580
+ { label: "Git Remote", key: "gitRemote", type: "inline" },
1523
1581
  { label: "Default", key: "isDefault", type: "toggle" }
1524
1582
  ];
1525
- function ProjectForm({ onSave, onCancel }) {
1583
+ function ProjectForm({ editingProject, initialGitRemote, onSave, onCancel }) {
1584
+ const isEditing = !!editingProject;
1526
1585
  const [focusIndex, setFocusIndex] = useState4(0);
1527
- const [values, setValues] = useState4({
1528
- name: "",
1529
- key: "",
1530
- description: "",
1531
- 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
+ };
1532
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]);
1533
1615
  const currentField = FIELDS2[focusIndex];
1534
1616
  useInput4((input, key) => {
1535
1617
  if (key.escape) {
@@ -1552,19 +1634,46 @@ function ProjectForm({ onSave, onCancel }) {
1552
1634
  name: nameVal,
1553
1635
  key: values["key"] ?? "",
1554
1636
  description: values["description"] ?? "",
1637
+ gitRemote: values["gitRemote"] ?? "",
1555
1638
  isDefault: values["isDefault"] === "yes"
1556
1639
  });
1557
1640
  }
1558
1641
  return;
1559
1642
  }
1560
- if (currentField.type === "inline") {
1561
- const currentValue = values[currentField.key] ?? "";
1562
- if (key.backspace || key.delete) {
1563
- 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
+ }
1564
1667
  } else if (key.return) {
1565
1668
  setFocusIndex((i) => Math.min(FIELDS2.length - 1, i + 1));
1566
1669
  } else if (input && !key.ctrl && !key.meta) {
1567
- 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);
1568
1677
  }
1569
1678
  }
1570
1679
  if (currentField.type === "toggle") {
@@ -1579,7 +1688,7 @@ function ProjectForm({ onSave, onCancel }) {
1579
1688
  return /* @__PURE__ */ jsxs8(Box11, { flexDirection: "column", flexGrow: 1, borderStyle: "bold", borderColor: theme.borderFocus, children: [
1580
1689
  /* @__PURE__ */ jsx11(Box11, { gap: 0, children: /* @__PURE__ */ jsxs8(Text11, { color: theme.title, bold: true, children: [
1581
1690
  " ",
1582
- "new project"
1691
+ isEditing ? "edit project" : "new project"
1583
1692
  ] }) }),
1584
1693
  /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", paddingX: 1, paddingY: 0, children: FIELDS2.map((field2, i) => {
1585
1694
  const isFocused = i === focusIndex;
@@ -1591,9 +1700,13 @@ function ProjectForm({ onSave, onCancel }) {
1591
1700
  field2.label.padEnd(14)
1592
1701
  ] }),
1593
1702
  field2.type === "inline" && /* @__PURE__ */ jsxs8(Text11, { color: isFocused ? theme.yaml.value : theme.table.fg, children: [
1594
- value,
1595
- isFocused ? /* @__PURE__ */ jsx11(Text11, { color: theme.titleHighlight, children: "_" }) : "",
1596
- 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)" })
1597
1710
  ] }),
1598
1711
  field2.type === "toggle" && /* @__PURE__ */ jsxs8(Text11, { color: isFocused ? theme.yaml.value : theme.table.fg, children: [
1599
1712
  isFocused ? "< " : " ",
@@ -1603,16 +1716,19 @@ function ProjectForm({ onSave, onCancel }) {
1603
1716
  ] }, field2.key);
1604
1717
  }) }),
1605
1718
  /* @__PURE__ */ jsx11(Box11, { flexGrow: 1 }),
1606
- /* @__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" }) })
1607
1720
  ] });
1608
1721
  }
1609
1722
 
1610
1723
  // src/tui/components/ProjectLinkForm.tsx
1611
- import { useState as useState5 } from "react";
1724
+ import { useState as useState5, useRef as useRef3 } from "react";
1612
1725
  import { Box as Box12, Text as Text12, useInput as useInput5 } from "ink";
1613
1726
  import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1614
1727
  function ProjectLinkForm({ project, onSave, onUnlink, onDetect, onCancel }) {
1615
- 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;
1616
1732
  useInput5((input, key) => {
1617
1733
  if (key.escape) {
1618
1734
  onCancel();
@@ -1629,6 +1745,8 @@ function ProjectLinkForm({ project, onSave, onUnlink, onDetect, onCancel }) {
1629
1745
  const detected = onDetect();
1630
1746
  if (detected) {
1631
1747
  setRemoteUrl(detected);
1748
+ cursorRef.current = detected.length;
1749
+ setCursorPos(detected.length);
1632
1750
  }
1633
1751
  return;
1634
1752
  }
@@ -1638,12 +1756,28 @@ function ProjectLinkForm({ project, onSave, onUnlink, onDetect, onCancel }) {
1638
1756
  }
1639
1757
  return;
1640
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
+ }
1641
1767
  if (key.backspace || key.delete) {
1642
- 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
+ }
1643
1774
  return;
1644
1775
  }
1645
1776
  if (input && !key.ctrl && !key.meta) {
1646
- 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);
1647
1781
  }
1648
1782
  });
1649
1783
  return /* @__PURE__ */ jsxs9(Box12, { flexDirection: "column", flexGrow: 1, borderStyle: "bold", borderColor: theme.borderFocus, children: [
@@ -1667,15 +1801,16 @@ function ProjectLinkForm({ project, onSave, onUnlink, onDetect, onCancel }) {
1667
1801
  {
1668
1802
  color: project.gitRemote ? theme.yaml.value : theme.table.fg,
1669
1803
  dimColor: !project.gitRemote,
1670
- children: project.gitRemote ?? "(none)"
1804
+ children: project.gitRemote?.value ?? "(none)"
1671
1805
  }
1672
1806
  )
1673
1807
  ] }),
1674
1808
  /* @__PURE__ */ jsxs9(Box12, { gap: 1, marginTop: 1, children: [
1675
1809
  /* @__PURE__ */ jsx12(Text12, { color: theme.dialog.label, bold: true, children: "Remote URL: " }),
1676
1810
  /* @__PURE__ */ jsxs9(Text12, { color: theme.yaml.value, children: [
1677
- remoteUrl,
1678
- /* @__PURE__ */ jsx12(Text12, { color: theme.titleHighlight, children: "_" })
1811
+ remoteUrl.slice(0, cursorPos),
1812
+ /* @__PURE__ */ jsx12(Text12, { color: theme.titleHighlight, children: "_" }),
1813
+ remoteUrl.slice(cursorPos)
1679
1814
  ] })
1680
1815
  ] })
1681
1816
  ] }),
@@ -1684,6 +1819,38 @@ function ProjectLinkForm({ project, onSave, onUnlink, onDetect, onCancel }) {
1684
1819
  ] });
1685
1820
  }
1686
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
+
1687
1854
  // src/tui/components/HelpOverlay.tsx
1688
1855
  import { Box as Box13, Text as Text13 } from "ink";
1689
1856
  import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
@@ -1755,10 +1922,10 @@ var ROW2 = [
1755
1922
  {
1756
1923
  title: "FORMS",
1757
1924
  keys: [
1758
- ["tab", "Next field"],
1759
- ["shift+tab", "Prev field"],
1925
+ ["\u2191\u2193/tab", "Navigate fields"],
1926
+ ["\u2190\u2192", "Move cursor"],
1760
1927
  ["ctrl+s", "Save"],
1761
- ["enter", "Open editor"],
1928
+ ["enter", "Open editor / next"],
1762
1929
  ["esc", "Cancel"]
1763
1930
  ]
1764
1931
  },
@@ -1849,9 +2016,41 @@ function ConfirmDialog({ task }) {
1849
2016
  );
1850
2017
  }
1851
2018
 
1852
- // src/tui/components/DependencyList.tsx
2019
+ // src/tui/components/DetectedProjectDialog.tsx
1853
2020
  import { Box as Box15, Text as Text15 } from "ink";
1854
- 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";
1855
2054
  function TaskRow({
1856
2055
  task,
1857
2056
  globalIndex,
@@ -1859,16 +2058,16 @@ function TaskRow({
1859
2058
  }) {
1860
2059
  const isSelected = globalIndex === selectedIndex;
1861
2060
  const statusColor = STATUS_COLOR[task.status] ?? theme.table.fg;
1862
- 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: [
1863
2062
  "> ",
1864
2063
  task.id.padEnd(12),
1865
2064
  task.status.padEnd(14),
1866
2065
  task.name
1867
- ] }) : /* @__PURE__ */ jsxs12(Fragment2, { children: [
1868
- /* @__PURE__ */ jsx15(Text15, { children: " " }),
1869
- /* @__PURE__ */ jsx15(Text15, { color: theme.yaml.value, children: task.id.padEnd(12) }),
1870
- /* @__PURE__ */ jsx15(Text15, { color: statusColor, children: task.status.padEnd(14) }),
1871
- /* @__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 })
1872
2071
  ] }) }, task.id);
1873
2072
  }
1874
2073
  function DependencyList({
@@ -1889,23 +2088,23 @@ function DependencyList({
1889
2088
  const relatedOffset = offset;
1890
2089
  offset += related.length;
1891
2090
  const duplicatesOffset = offset;
1892
- return /* @__PURE__ */ jsxs12(Box15, { flexDirection: "column", flexGrow: 1, borderStyle: "bold", borderColor: theme.borderFocus, children: [
1893
- /* @__PURE__ */ jsxs12(Box15, { gap: 0, children: [
1894
- /* @__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: [
1895
2094
  " ",
1896
2095
  "dependencies"
1897
2096
  ] }),
1898
- /* @__PURE__ */ jsx15(Text15, { color: theme.fg, children: "(" }),
1899
- /* @__PURE__ */ jsx15(Text15, { color: theme.titleHighlight, bold: true, children: task.name }),
1900
- /* @__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: ")" })
1901
2100
  ] }),
1902
- /* @__PURE__ */ jsxs12(Box15, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
1903
- /* @__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: [
1904
2103
  "BLOCKED BY (",
1905
2104
  blockers.length,
1906
2105
  ")"
1907
2106
  ] }),
1908
- 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(
1909
2108
  TaskRow,
1910
2109
  {
1911
2110
  task: t,
@@ -1915,13 +2114,13 @@ function DependencyList({
1915
2114
  t.id
1916
2115
  ))
1917
2116
  ] }),
1918
- /* @__PURE__ */ jsxs12(Box15, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
1919
- /* @__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: [
1920
2119
  "BLOCKS (",
1921
2120
  dependents.length,
1922
2121
  ")"
1923
2122
  ] }),
1924
- 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(
1925
2124
  TaskRow,
1926
2125
  {
1927
2126
  task: t,
@@ -1931,13 +2130,13 @@ function DependencyList({
1931
2130
  t.id
1932
2131
  ))
1933
2132
  ] }),
1934
- /* @__PURE__ */ jsxs12(Box15, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
1935
- /* @__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: [
1936
2135
  "RELATES TO (",
1937
2136
  related.length,
1938
2137
  ")"
1939
2138
  ] }),
1940
- 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(
1941
2140
  TaskRow,
1942
2141
  {
1943
2142
  task: t,
@@ -1947,13 +2146,13 @@ function DependencyList({
1947
2146
  t.id
1948
2147
  ))
1949
2148
  ] }),
1950
- /* @__PURE__ */ jsxs12(Box15, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
1951
- /* @__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: [
1952
2151
  "DUPLICATES (",
1953
2152
  duplicates.length,
1954
2153
  ")"
1955
2154
  ] }),
1956
- 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(
1957
2156
  TaskRow,
1958
2157
  {
1959
2158
  task: t,
@@ -1963,19 +2162,19 @@ function DependencyList({
1963
2162
  t.id
1964
2163
  ))
1965
2164
  ] }),
1966
- /* @__PURE__ */ jsx15(Box15, { flexGrow: 1 }),
1967
- isAddingDep && /* @__PURE__ */ jsxs12(Box15, { borderStyle: "round", borderColor: theme.prompt, paddingX: 1, children: [
1968
- /* @__PURE__ */ jsx15(Text15, { color: theme.prompt, children: "depends on (id or id:type): " }),
1969
- /* @__PURE__ */ jsx15(Text15, { color: theme.prompt, children: addDepInput }),
1970
- /* @__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: "_" })
1971
2170
  ] }),
1972
- /* @__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" }) })
1973
2172
  ] });
1974
2173
  }
1975
2174
 
1976
2175
  // src/tui/components/EpicPanel.tsx
1977
- import { Box as Box16, Text as Text16 } from "ink";
1978
- 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";
1979
2178
  var PAGE_SIZE2 = 20;
1980
2179
  function EpicPanel({
1981
2180
  epics,
@@ -1988,34 +2187,34 @@ function EpicPanel({
1988
2187
  const currentPage = Math.floor(selectedIndex / PAGE_SIZE2);
1989
2188
  const viewStart = currentPage * PAGE_SIZE2;
1990
2189
  const visibleEpics = epics.slice(viewStart, viewStart + PAGE_SIZE2);
1991
- return /* @__PURE__ */ jsxs13(
1992
- Box16,
2190
+ return /* @__PURE__ */ jsxs14(
2191
+ Box17,
1993
2192
  {
1994
2193
  flexDirection: "column",
1995
2194
  width: 48,
1996
2195
  borderStyle: "bold",
1997
2196
  borderColor: isFocused ? theme.borderFocus : theme.border,
1998
2197
  children: [
1999
- /* @__PURE__ */ jsxs13(Box16, { children: [
2000
- /* @__PURE__ */ jsxs13(Text16, { color: theme.title, bold: true, children: [
2198
+ /* @__PURE__ */ jsxs14(Box17, { children: [
2199
+ /* @__PURE__ */ jsxs14(Text17, { color: theme.title, bold: true, children: [
2001
2200
  " ",
2002
2201
  "epics"
2003
2202
  ] }),
2004
- /* @__PURE__ */ jsxs13(Text16, { color: theme.titleCounter, bold: true, children: [
2203
+ /* @__PURE__ */ jsxs14(Text17, { color: theme.titleCounter, bold: true, children: [
2005
2204
  "[",
2006
2205
  epics.length,
2007
2206
  "]"
2008
2207
  ] }),
2009
- isReordering && /* @__PURE__ */ jsxs13(Text16, { color: theme.flash.warn, bold: true, children: [
2208
+ isReordering && /* @__PURE__ */ jsxs14(Text17, { color: theme.flash.warn, bold: true, children: [
2010
2209
  " ",
2011
2210
  "REORDER"
2012
2211
  ] }),
2013
- filterActive && /* @__PURE__ */ jsxs13(Text16, { color: theme.titleFilter, children: [
2212
+ filterActive && /* @__PURE__ */ jsxs14(Text17, { color: theme.titleFilter, children: [
2014
2213
  " *",
2015
2214
  selectedEpicIds.size
2016
2215
  ] })
2017
2216
  ] }),
2018
- /* @__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) => {
2019
2218
  const actualIndex = viewStart + i;
2020
2219
  const isSelected = actualIndex === selectedIndex && isFocused;
2021
2220
  const isChecked = selectedEpicIds.has(epic.id);
@@ -2023,21 +2222,21 @@ function EpicPanel({
2023
2222
  const statusColor = STATUS_COLOR[epic.status] ?? theme.table.fg;
2024
2223
  if (isSelected) {
2025
2224
  const cursorBg = isReordering ? theme.flash.warn : theme.table.cursorBg;
2026
- 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: [
2027
2226
  isReordering ? "~ " : " ",
2028
2227
  marker,
2029
2228
  " ",
2030
2229
  epic.name
2031
2230
  ] }) }, epic.id);
2032
2231
  }
2033
- 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: [
2034
2233
  " ",
2035
2234
  marker,
2036
2235
  " ",
2037
2236
  epic.name
2038
2237
  ] }) }, epic.id);
2039
2238
  }) }),
2040
- 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: [
2041
2240
  "[",
2042
2241
  viewStart + 1,
2043
2242
  "-",
@@ -2053,8 +2252,8 @@ function EpicPanel({
2053
2252
 
2054
2253
  // src/tui/components/EpicPicker.tsx
2055
2254
  import { useState as useState6 } from "react";
2056
- import { Box as Box17, Text as Text17, useInput as useInput6, useStdout as useStdout3 } from "ink";
2057
- 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";
2058
2257
  function EpicPicker({ epics, currentEpicId, onSelect, onCancel }) {
2059
2258
  const { stdout } = useStdout3();
2060
2259
  const termHeight = stdout.rows > 0 ? stdout.rows : 24;
@@ -2127,53 +2326,53 @@ function EpicPicker({ epics, currentEpicId, onSelect, onCancel }) {
2127
2326
  return;
2128
2327
  }
2129
2328
  });
2130
- return /* @__PURE__ */ jsxs14(Box17, { flexDirection: "column", borderStyle: "bold", borderColor: theme.borderFocus, flexGrow: 1, children: [
2131
- /* @__PURE__ */ jsxs14(Box17, { gap: 0, children: [
2132
- /* @__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: [
2133
2332
  " ",
2134
2333
  "assign to epic"
2135
2334
  ] }),
2136
- /* @__PURE__ */ jsxs14(Text17, { color: theme.titleCounter, bold: true, children: [
2335
+ /* @__PURE__ */ jsxs15(Text18, { color: theme.titleCounter, bold: true, children: [
2137
2336
  " ",
2138
2337
  "[",
2139
2338
  epics.length,
2140
2339
  "]"
2141
2340
  ] })
2142
2341
  ] }),
2143
- isSearching ? /* @__PURE__ */ jsxs14(Box17, { borderStyle: "round", borderColor: theme.prompt, paddingX: 1, children: [
2144
- /* @__PURE__ */ jsx17(Text17, { color: theme.prompt, children: "/" }),
2145
- /* @__PURE__ */ jsx17(Text17, { color: theme.prompt, children: searchQuery }),
2146
- /* @__PURE__ */ jsx17(Text17, { color: theme.promptSuggest, children: "_" })
2147
- ] }) : 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: [
2148
2347
  "/",
2149
2348
  searchQuery
2150
2349
  ] }) }) : null,
2151
- /* @__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: [
2152
2351
  " ",
2153
2352
  "ID".padEnd(14),
2154
2353
  "STATUS".padEnd(14),
2155
2354
  "NAME"
2156
2355
  ] }) }),
2157
- 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) => {
2158
2357
  const actualIndex = viewStart + i;
2159
2358
  const isCursor = actualIndex === cursorIndex;
2160
2359
  const isCurrent = epic.id === currentEpicId;
2161
2360
  const marker = isCurrent ? "* " : " ";
2162
2361
  const statusColor = STATUS_COLOR[epic.status] ?? theme.table.fg;
2163
- 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: [
2164
2363
  "> ",
2165
2364
  epic.id.padEnd(14),
2166
2365
  epic.status.padEnd(14),
2167
2366
  epic.name
2168
- ] }) : /* @__PURE__ */ jsxs14(Fragment3, { children: [
2169
- /* @__PURE__ */ jsx17(Text17, { color: isCurrent ? theme.titleHighlight : theme.table.fg, children: marker }),
2170
- /* @__PURE__ */ jsx17(Text17, { color: theme.yaml.value, children: epic.id.padEnd(14) }),
2171
- /* @__PURE__ */ jsx17(Text17, { color: statusColor, children: epic.status.padEnd(14) }),
2172
- /* @__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 })
2173
2372
  ] }) }, epic.id);
2174
2373
  }),
2175
- /* @__PURE__ */ jsx17(Box17, { flexGrow: 1 }),
2176
- 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: [
2177
2376
  "[",
2178
2377
  viewStart + 1,
2179
2378
  "-",
@@ -2182,19 +2381,19 @@ function EpicPicker({ epics, currentEpicId, onSelect, onCancel }) {
2182
2381
  filtered.length,
2183
2382
  "]"
2184
2383
  ] }) }),
2185
- /* @__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" }) })
2186
2385
  ] });
2187
2386
  }
2188
2387
 
2189
2388
  // src/tui/useAutoRefetch.ts
2190
- import { useEffect, useRef } from "react";
2389
+ import { useEffect as useEffect3, useRef as useRef4 } from "react";
2191
2390
  import { watchFile, unwatchFile } from "fs";
2192
2391
  var POLL_INTERVAL_MS = 1e3;
2193
2392
  var DEBOUNCE_MS = 200;
2194
2393
  function useAutoRefetch(dbPath, onRefetch) {
2195
- const callbackRef = useRef(onRefetch);
2394
+ const callbackRef = useRef4(onRefetch);
2196
2395
  callbackRef.current = onRefetch;
2197
- useEffect(() => {
2396
+ useEffect3(() => {
2198
2397
  let debounceTimer = null;
2199
2398
  const handleChange = () => {
2200
2399
  if (debounceTimer) clearTimeout(debounceTimer);
@@ -2222,7 +2421,7 @@ function useAutoRefetch(dbPath, onRefetch) {
2222
2421
  }
2223
2422
 
2224
2423
  // src/tui/components/App.tsx
2225
- import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
2424
+ import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
2226
2425
  var STATUS_CYCLE = [
2227
2426
  TaskStatus.Backlog,
2228
2427
  TaskStatus.Todo,
@@ -2236,7 +2435,7 @@ function App({ container, initialProject, latestVersion }) {
2236
2435
  const { stdout } = useStdout4();
2237
2436
  const [state, dispatch] = useReducer(appReducer, initialState);
2238
2437
  const [, setResizeTick] = useState7(0);
2239
- useEffect2(() => {
2438
+ useEffect4(() => {
2240
2439
  const onResize = () => {
2241
2440
  setResizeTick((n) => n + 1);
2242
2441
  };
@@ -2401,10 +2600,10 @@ function App({ container, initialProject, latestVersion }) {
2401
2600
  loadEpics();
2402
2601
  }, [loadProjects, loadTasks, loadEpics]);
2403
2602
  useAutoRefetch(container.dbPath, refetchAll);
2404
- useEffect2(() => {
2603
+ useEffect4(() => {
2405
2604
  loadProjects();
2406
2605
  }, [loadProjects]);
2407
- useEffect2(() => {
2606
+ useEffect4(() => {
2408
2607
  if (state.projects.length > 0 && !state.activeProject) {
2409
2608
  logger.info(`TUI.resolveProject: resolving initialProject=${initialProject ?? "(default)"}`);
2410
2609
  const result = container.projectService.resolveProject(initialProject);
@@ -2421,13 +2620,29 @@ function App({ container, initialProject, latestVersion }) {
2421
2620
  }
2422
2621
  }
2423
2622
  }, [state.projects, state.activeProject, initialProject, container]);
2424
- 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(() => {
2425
2640
  if (state.activeProject) {
2426
2641
  loadTasks();
2427
2642
  loadEpics();
2428
2643
  }
2429
2644
  }, [state.activeProject, state.filter, loadTasks, loadEpics]);
2430
- useEffect2(() => {
2645
+ useEffect4(() => {
2431
2646
  if (state.flash) {
2432
2647
  const timer = setTimeout(() => {
2433
2648
  dispatch({ type: "CLEAR_FLASH" });
@@ -2459,11 +2674,20 @@ function App({ container, initialProject, latestVersion }) {
2459
2674
  }
2460
2675
  return;
2461
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
+ }
2462
2686
  if (state.activeView === ViewType.Help) {
2463
2687
  dispatch({ type: "GO_BACK" });
2464
2688
  return;
2465
2689
  }
2466
- 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) {
2467
2691
  return;
2468
2692
  }
2469
2693
  if (state.activeView === ViewType.DependencyList && state.isAddingDep) {
@@ -3037,35 +3261,57 @@ ${state.selectedTask.additionalRequirements}`;
3037
3261
  const handleProjectCreate = useCallback2(() => {
3038
3262
  dispatch({ type: "NAVIGATE_TO", view: ViewType.ProjectCreate });
3039
3263
  }, []);
3264
+ const handleProjectEdit = useCallback2((project) => {
3265
+ dispatch({ type: "SET_EDITING_PROJECT", project });
3266
+ dispatch({ type: "NAVIGATE_TO", view: ViewType.ProjectEdit });
3267
+ }, []);
3040
3268
  const handleProjectFormSave = useCallback2(
3041
3269
  (data) => {
3042
- 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({
3043
3277
  name: data.name,
3044
3278
  key: data.key || void 0,
3045
3279
  description: data.description || void 0,
3046
- isDefault: data.isDefault
3280
+ isDefault: data.isDefault,
3281
+ gitRemote: data.gitRemote || void 0
3047
3282
  });
3048
3283
  if (result.ok) {
3049
- 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
+ );
3050
3288
  dispatch({
3051
3289
  type: "FLASH",
3052
- message: `Project created: ${result.value.name}`,
3290
+ message: `Project ${verb}: ${result.value.name}`,
3053
3291
  level: "info"
3054
3292
  });
3055
- dispatch({ type: "SET_ACTIVE_PROJECT", project: result.value });
3056
- 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
+ }
3057
3300
  dispatch({ type: "GO_BACK" });
3058
3301
  loadProjects();
3059
3302
  } else {
3060
- logger.error("TUI.createProject: failed", result.error);
3303
+ logger.error(`TUI.${editing ? "editProject" : "createProject"}: failed`, result.error);
3061
3304
  dispatch({ type: "FLASH", message: result.error.message, level: "error" });
3062
3305
  }
3063
3306
  },
3064
- [container, loadProjects]
3307
+ [container, state.activeView, state.editingProject, state.activeProject, loadProjects]
3065
3308
  );
3066
3309
  const handleProjectFormCancel = useCallback2(() => {
3310
+ if (state.activeView === ViewType.ProjectCreate && state.detectedGitRemote) {
3311
+ dismissRemote(container.dismissedGitRemotesPath, state.detectedGitRemote);
3312
+ }
3067
3313
  dispatch({ type: "GO_BACK" });
3068
- }, []);
3314
+ }, [state.activeView, state.detectedGitRemote, container]);
3069
3315
  const handleProjectLink = useCallback2((project) => {
3070
3316
  dispatch({ type: "SET_LINKING_PROJECT", project });
3071
3317
  dispatch({ type: "NAVIGATE_TO", view: ViewType.ProjectLink });
@@ -3076,11 +3322,11 @@ ${state.selectedTask.additionalRequirements}`;
3076
3322
  const result = container.projectService.linkGitRemote(state.linkingProject.id, remote);
3077
3323
  if (result.ok) {
3078
3324
  logger.info(
3079
- `TUI.linkGitRemote: linked project=${state.linkingProject.id} remote=${result.value.gitRemote}`
3325
+ `TUI.linkGitRemote: linked project=${state.linkingProject.id} remote=${result.value.gitRemote?.value}`
3080
3326
  );
3081
3327
  dispatch({
3082
3328
  type: "FLASH",
3083
- message: `Linked to: ${result.value.gitRemote}`,
3329
+ message: `Linked to: ${result.value.gitRemote?.value}`,
3084
3330
  level: "info"
3085
3331
  });
3086
3332
  dispatch({ type: "GO_BACK" });
@@ -3108,8 +3354,8 @@ ${state.selectedTask.additionalRequirements}`;
3108
3354
  const handleLinkDetect = useCallback2(() => {
3109
3355
  const result = detectGitRemote();
3110
3356
  if (result.ok && result.value) {
3111
- dispatch({ type: "FLASH", message: `Detected: ${result.value}`, level: "info" });
3112
- return result.value;
3357
+ dispatch({ type: "FLASH", message: `Detected: ${result.value.value}`, level: "info" });
3358
+ return result.value.value;
3113
3359
  }
3114
3360
  dispatch({ type: "FLASH", message: "No git remote detected in cwd", level: "warn" });
3115
3361
  return null;
@@ -3138,17 +3384,17 @@ ${state.selectedTask.additionalRequirements}`;
3138
3384
  return result.ok ? result.value : [];
3139
3385
  }, [container, state.activeProject, state.tasks]);
3140
3386
  const previewTaskId = previewTask?.id ?? null;
3141
- useEffect2(() => {
3387
+ useEffect4(() => {
3142
3388
  if (state.activeView === ViewType.TaskList && previewTaskId) {
3143
3389
  loadDeps(previewTaskId);
3144
3390
  }
3145
3391
  }, [state.activeView, previewTaskId, loadDeps]);
3146
- return /* @__PURE__ */ jsxs15(Box18, { flexDirection: "column", height: stdout.rows, children: [
3147
- /* @__PURE__ */ jsx18(Header, { state, latestVersion }),
3148
- /* @__PURE__ */ jsxs15(Box18, { flexDirection: "column", flexGrow: 1, overflowY: "hidden", children: [
3149
- state.confirmDelete && /* @__PURE__ */ jsx18(ConfirmDialog, { task: state.confirmDelete }),
3150
- !state.confirmDelete && state.activeView === ViewType.TaskList && /* @__PURE__ */ jsxs15(Box18, { flexDirection: "row", flexGrow: 1, children: [
3151
- /* @__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(
3152
3398
  EpicPanel,
3153
3399
  {
3154
3400
  epics: state.epics,
@@ -3158,7 +3404,7 @@ ${state.selectedTask.additionalRequirements}`;
3158
3404
  isReordering: state.isEpicReordering
3159
3405
  }
3160
3406
  ),
3161
- /* @__PURE__ */ jsx18(Box18, { width: taskListWidth, children: /* @__PURE__ */ jsx18(
3407
+ /* @__PURE__ */ jsx19(Box19, { width: taskListWidth, children: /* @__PURE__ */ jsx19(
3162
3408
  TaskList,
3163
3409
  {
3164
3410
  tasks: state.tasks,
@@ -3179,7 +3425,7 @@ ${state.selectedTask.additionalRequirements}`;
3179
3425
  epicFilterActive: state.selectedEpicIds.size > 0
3180
3426
  }
3181
3427
  ) }),
3182
- /* @__PURE__ */ jsx18(Box18, { width: taskDetailWidth, children: previewTask ? /* @__PURE__ */ jsx18(
3428
+ /* @__PURE__ */ jsx19(Box19, { width: taskDetailWidth, children: previewTask ? /* @__PURE__ */ jsx19(
3183
3429
  TaskDetail,
3184
3430
  {
3185
3431
  task: previewTask,
@@ -3190,24 +3436,24 @@ ${state.selectedTask.additionalRequirements}`;
3190
3436
  isFocused: state.focusedPanel === "detail",
3191
3437
  scrollOffset: state.detailScrollOffset
3192
3438
  }
3193
- ) : /* @__PURE__ */ jsxs15(
3194
- Box18,
3439
+ ) : /* @__PURE__ */ jsxs16(
3440
+ Box19,
3195
3441
  {
3196
3442
  flexDirection: "column",
3197
3443
  flexGrow: 1,
3198
3444
  borderStyle: "bold",
3199
3445
  borderColor: theme.border,
3200
3446
  children: [
3201
- /* @__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: [
3202
3448
  " ",
3203
3449
  "detail"
3204
3450
  ] }) }),
3205
- /* @__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" }) })
3206
3452
  ]
3207
3453
  }
3208
3454
  ) })
3209
- ] }),
3210
- !state.confirmDelete && state.activeView === ViewType.TaskDetail && state.selectedTask && /* @__PURE__ */ jsx18(
3455
+ ] })),
3456
+ !state.confirmDelete && state.activeView === ViewType.TaskDetail && state.selectedTask && /* @__PURE__ */ jsx19(
3211
3457
  TaskDetail,
3212
3458
  {
3213
3459
  task: state.selectedTask,
@@ -3218,7 +3464,7 @@ ${state.selectedTask.additionalRequirements}`;
3218
3464
  scrollOffset: state.detailScrollOffset
3219
3465
  }
3220
3466
  ),
3221
- !state.confirmDelete && state.activeView === ViewType.DependencyList && state.selectedTask && /* @__PURE__ */ jsx18(
3467
+ !state.confirmDelete && state.activeView === ViewType.DependencyList && state.selectedTask && /* @__PURE__ */ jsx19(
3222
3468
  DependencyList,
3223
3469
  {
3224
3470
  task: state.selectedTask,
@@ -3231,7 +3477,7 @@ ${state.selectedTask.additionalRequirements}`;
3231
3477
  addDepInput: state.addDepInput
3232
3478
  }
3233
3479
  ),
3234
- !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(
3235
3481
  TaskForm,
3236
3482
  {
3237
3483
  editingTask: state.activeView === ViewType.TaskEdit ? state.selectedTask : null,
@@ -3241,7 +3487,7 @@ ${state.selectedTask.additionalRequirements}`;
3241
3487
  onCancel: handleFormCancel
3242
3488
  }
3243
3489
  ),
3244
- !state.confirmDelete && state.activeView === ViewType.EpicPicker && state.selectedTask && /* @__PURE__ */ jsx18(
3490
+ !state.confirmDelete && state.activeView === ViewType.EpicPicker && state.selectedTask && /* @__PURE__ */ jsx19(
3245
3491
  EpicPicker,
3246
3492
  {
3247
3493
  epics: state.epics,
@@ -3250,20 +3496,36 @@ ${state.selectedTask.additionalRequirements}`;
3250
3496
  onCancel: handleEpicPickerCancel
3251
3497
  }
3252
3498
  ),
3253
- !state.confirmDelete && state.activeView === ViewType.ProjectSelector && /* @__PURE__ */ jsx18(
3499
+ !state.confirmDelete && state.activeView === ViewType.ProjectSelector && /* @__PURE__ */ jsx19(
3254
3500
  ProjectSelector,
3255
3501
  {
3256
3502
  projects: state.projects,
3257
3503
  activeProject: state.activeProject,
3258
3504
  onSelect: handleProjectSelect,
3259
3505
  onCreate: handleProjectCreate,
3506
+ onEdit: handleProjectEdit,
3260
3507
  onSetDefault: handleSetDefault,
3261
3508
  onLink: handleProjectLink,
3262
3509
  onCancel: handleProjectCancel
3263
3510
  }
3264
3511
  ),
3265
- !state.confirmDelete && state.activeView === ViewType.ProjectCreate && /* @__PURE__ */ jsx18(ProjectForm, { onSave: handleProjectFormSave, onCancel: handleProjectFormCancel }),
3266
- !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(
3267
3529
  ProjectLinkForm,
3268
3530
  {
3269
3531
  project: state.linkingProject,
@@ -3273,18 +3535,18 @@ ${state.selectedTask.additionalRequirements}`;
3273
3535
  onCancel: handleLinkCancel
3274
3536
  }
3275
3537
  ),
3276
- !state.confirmDelete && state.activeView === ViewType.Help && /* @__PURE__ */ jsx18(HelpOverlay, {})
3538
+ !state.confirmDelete && state.activeView === ViewType.Help && /* @__PURE__ */ jsx19(HelpOverlay, {})
3277
3539
  ] }),
3278
- /* @__PURE__ */ jsx18(Crumbs, { breadcrumbs: state.breadcrumbs }),
3279
- 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 })
3280
3542
  ] });
3281
3543
  }
3282
3544
 
3283
3545
  // src/tui/index.tsx
3284
- import { jsx as jsx19 } from "react/jsx-runtime";
3546
+ import { jsx as jsx20 } from "react/jsx-runtime";
3285
3547
  async function launchTUI(container, initialProject, latestVersion) {
3286
3548
  const instance = render(
3287
- /* @__PURE__ */ jsx19(App, { container, initialProject, latestVersion }),
3549
+ /* @__PURE__ */ jsx20(App, { container, initialProject, latestVersion }),
3288
3550
  {
3289
3551
  exitOnCtrlC: true
3290
3552
  }
@@ -3294,4 +3556,4 @@ async function launchTUI(container, initialProject, latestVersion) {
3294
3556
  export {
3295
3557
  launchTUI
3296
3558
  };
3297
- //# sourceMappingURL=tui-WMESKCRD.js.map
3559
+ //# sourceMappingURL=tui-NCL4RFFD.js.map