@tomkapa/tayto 0.3.1 → 0.4.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.
@@ -5,16 +5,17 @@ import {
5
5
  TaskStatus,
6
6
  TaskType,
7
7
  UIDependencyType,
8
+ detectGitRemote,
8
9
  isTerminalStatus,
9
10
  logger
10
- } from "./chunk-FUNYPBWJ.js";
11
+ } from "./chunk-74Q55TOV.js";
11
12
 
12
13
  // src/tui/index.tsx
13
14
  import { render } from "ink";
14
15
 
15
16
  // src/tui/components/App.tsx
16
- import { useReducer, useEffect as useEffect2, useCallback as useCallback2, useMemo as useMemo2, useState as useState6 } from "react";
17
- import { Box as Box16, Text as Text16, useInput as useInput6, useApp, useStdout as useStdout4 } from "ink";
17
+ import { useReducer, useEffect as useEffect2, useCallback as useCallback2, useMemo as useMemo2, useState as useState7 } from "react";
18
+ import { Box as Box17, Text as Text17, useInput as useInput7, useApp, useStdout as useStdout4 } from "ink";
18
19
 
19
20
  // src/tui/types.ts
20
21
  var ViewType = {
@@ -26,6 +27,7 @@ var ViewType = {
26
27
  ProjectCreate: "project-create",
27
28
  DependencyList: "dependency-list",
28
29
  EpicPicker: "epic-picker",
30
+ ProjectLink: "project-link",
29
31
  Help: "help"
30
32
  };
31
33
 
@@ -192,6 +194,7 @@ var initialState = {
192
194
  epics: [],
193
195
  epicSelectedIndex: 0,
194
196
  selectedEpicIds: /* @__PURE__ */ new Set(),
197
+ linkingProject: null,
195
198
  isEpicReordering: false,
196
199
  epicReorderSnapshot: null
197
200
  };
@@ -214,6 +217,7 @@ function appReducer(state, action) {
214
217
  confirmDelete: null,
215
218
  isSearchActive: false,
216
219
  formData: null,
220
+ linkingProject: null,
217
221
  focusedPanel: "list"
218
222
  };
219
223
  }
@@ -396,6 +400,8 @@ function appReducer(state, action) {
396
400
  epicReorderSnapshot: null
397
401
  };
398
402
  }
403
+ case "SET_LINKING_PROJECT":
404
+ return { ...state, linkingProject: action.project };
399
405
  }
400
406
  }
401
407
 
@@ -1314,6 +1320,7 @@ function ProjectSelector({
1314
1320
  onSelect,
1315
1321
  onCreate,
1316
1322
  onSetDefault,
1323
+ onLink,
1317
1324
  onCancel
1318
1325
  }) {
1319
1326
  const [selectedIndex, setSelectedIndex] = useState3(() => {
@@ -1344,6 +1351,13 @@ function ProjectSelector({
1344
1351
  }
1345
1352
  return;
1346
1353
  }
1354
+ if (input === "l") {
1355
+ const project = projects[selectedIndex];
1356
+ if (project) {
1357
+ onLink(project);
1358
+ }
1359
+ return;
1360
+ }
1347
1361
  if (key.upArrow || input === "k") {
1348
1362
  setSelectedIndex((i) => Math.max(0, i - 1));
1349
1363
  }
@@ -1365,6 +1379,7 @@ function ProjectSelector({
1365
1379
  ] }),
1366
1380
  /* @__PURE__ */ jsxs7(Box9, { paddingX: 1, children: [
1367
1381
  /* @__PURE__ */ jsx9(Text9, { color: theme.table.headerFg, bold: true, children: " NAME".padEnd(30) }),
1382
+ /* @__PURE__ */ jsx9(Text9, { color: theme.table.headerFg, bold: true, children: "GIT REMOTE".padEnd(40) }),
1368
1383
  /* @__PURE__ */ jsx9(Text9, { color: theme.table.headerFg, bold: true, children: "DESCRIPTION" })
1369
1384
  ] }),
1370
1385
  projects.length === 0 ? /* @__PURE__ */ jsx9(Box9, { paddingX: 1, paddingY: 1, children: /* @__PURE__ */ jsx9(Text9, { color: theme.fg, children: "No projects. Press 'c' to create one." }) }) : projects.map((project, i) => {
@@ -1373,11 +1388,13 @@ function ProjectSelector({
1373
1388
  const activeMarker = isActive ? "*" : " ";
1374
1389
  const defaultMarker = project.isDefault ? "D" : " ";
1375
1390
  const marker = `${activeMarker}${defaultMarker}`;
1391
+ const remoteDisplay = (project.gitRemote ?? "").slice(0, 38).padEnd(40);
1376
1392
  if (isSelected) {
1377
1393
  return /* @__PURE__ */ jsx9(Box9, { paddingX: 1, children: /* @__PURE__ */ jsxs7(Text9, { backgroundColor: theme.table.cursorBg, color: theme.table.cursorFg, bold: true, children: [
1378
1394
  marker,
1379
1395
  " ",
1380
1396
  project.name.padEnd(27),
1397
+ remoteDisplay,
1381
1398
  project.description
1382
1399
  ] }) }, project.id);
1383
1400
  }
@@ -1387,11 +1404,12 @@ function ProjectSelector({
1387
1404
  " ",
1388
1405
  project.name.padEnd(27)
1389
1406
  ] }),
1407
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: remoteDisplay }),
1390
1408
  /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: project.description })
1391
1409
  ] }, project.id);
1392
1410
  }),
1393
1411
  /* @__PURE__ */ jsx9(Box9, { flexGrow: 1 }),
1394
- /* @__PURE__ */ jsx9(Box9, { paddingX: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "enter: select | d: set default | c: create | esc: back" }) })
1412
+ /* @__PURE__ */ jsx9(Box9, { paddingX: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "enter: select | d: set default | l: link git | c: create | esc: back" }) })
1395
1413
  ] });
1396
1414
  }
1397
1415
 
@@ -1490,9 +1508,86 @@ function ProjectForm({ onSave, onCancel }) {
1490
1508
  ] });
1491
1509
  }
1492
1510
 
1493
- // src/tui/components/HelpOverlay.tsx
1494
- import { Box as Box11, Text as Text11 } from "ink";
1511
+ // src/tui/components/ProjectLinkForm.tsx
1512
+ import { useState as useState5 } from "react";
1513
+ import { Box as Box11, Text as Text11, useInput as useInput5 } from "ink";
1495
1514
  import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
1515
+ function ProjectLinkForm({ project, onSave, onUnlink, onDetect, onCancel }) {
1516
+ const [remoteUrl, setRemoteUrl] = useState5(project.gitRemote ?? "");
1517
+ useInput5((input, key) => {
1518
+ if (key.escape) {
1519
+ onCancel();
1520
+ return;
1521
+ }
1522
+ if (input === "s" && key.ctrl) {
1523
+ const trimmed = remoteUrl.trim();
1524
+ if (trimmed) {
1525
+ onSave(trimmed);
1526
+ }
1527
+ return;
1528
+ }
1529
+ if (input === "d" && key.ctrl) {
1530
+ const detected = onDetect();
1531
+ if (detected) {
1532
+ setRemoteUrl(detected);
1533
+ }
1534
+ return;
1535
+ }
1536
+ if (input === "u" && key.ctrl) {
1537
+ if (project.gitRemote) {
1538
+ onUnlink();
1539
+ }
1540
+ return;
1541
+ }
1542
+ if (key.backspace || key.delete) {
1543
+ setRemoteUrl((v) => v.slice(0, -1));
1544
+ return;
1545
+ }
1546
+ if (input && !key.ctrl && !key.meta) {
1547
+ setRemoteUrl((v) => v + input);
1548
+ }
1549
+ });
1550
+ return /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", flexGrow: 1, borderStyle: "bold", borderColor: theme.borderFocus, children: [
1551
+ /* @__PURE__ */ jsxs9(Box11, { gap: 0, children: [
1552
+ /* @__PURE__ */ jsxs9(Text11, { color: theme.title, bold: true, children: [
1553
+ " ",
1554
+ "link git remote"
1555
+ ] }),
1556
+ /* @__PURE__ */ jsxs9(Text11, { color: theme.titleCounter, bold: true, children: [
1557
+ " ",
1558
+ "[",
1559
+ project.name,
1560
+ "]"
1561
+ ] })
1562
+ ] }),
1563
+ /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [
1564
+ /* @__PURE__ */ jsxs9(Box11, { gap: 1, children: [
1565
+ /* @__PURE__ */ jsx11(Text11, { color: theme.dialog.label, bold: true, children: "Current:" }),
1566
+ /* @__PURE__ */ jsx11(
1567
+ Text11,
1568
+ {
1569
+ color: project.gitRemote ? theme.yaml.value : theme.table.fg,
1570
+ dimColor: !project.gitRemote,
1571
+ children: project.gitRemote ?? "(none)"
1572
+ }
1573
+ )
1574
+ ] }),
1575
+ /* @__PURE__ */ jsxs9(Box11, { gap: 1, marginTop: 1, children: [
1576
+ /* @__PURE__ */ jsx11(Text11, { color: theme.dialog.label, bold: true, children: "Remote URL: " }),
1577
+ /* @__PURE__ */ jsxs9(Text11, { color: theme.yaml.value, children: [
1578
+ remoteUrl,
1579
+ /* @__PURE__ */ jsx11(Text11, { color: theme.titleHighlight, children: "_" })
1580
+ ] })
1581
+ ] })
1582
+ ] }),
1583
+ /* @__PURE__ */ jsx11(Box11, { flexGrow: 1 }),
1584
+ /* @__PURE__ */ jsx11(Box11, { paddingX: 1, children: /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "ctrl+s: save | ctrl+d: detect from cwd | ctrl+u: unlink | esc: cancel" }) })
1585
+ ] });
1586
+ }
1587
+
1588
+ // src/tui/components/HelpOverlay.tsx
1589
+ import { Box as Box12, Text as Text12 } from "ink";
1590
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1496
1591
  var SECTIONS = [
1497
1592
  {
1498
1593
  title: "NAVIGATION",
@@ -1520,6 +1615,8 @@ var SECTIONS = [
1520
1615
  keys: [
1521
1616
  ["\u2190", "Enter reorder"],
1522
1617
  ["\u2191\u2193", "Move task"],
1618
+ ["t", "Jump to top"],
1619
+ ["b", "Jump to bottom"],
1523
1620
  ["\u2192", "Save position"],
1524
1621
  ["esc/\u2190", "Cancel"]
1525
1622
  ]
@@ -1552,8 +1649,8 @@ var SECTIONS = [
1552
1649
  }
1553
1650
  ];
1554
1651
  function HelpOverlay() {
1555
- return /* @__PURE__ */ jsxs9(
1556
- Box11,
1652
+ return /* @__PURE__ */ jsxs10(
1653
+ Box12,
1557
1654
  {
1558
1655
  flexDirection: "column",
1559
1656
  borderStyle: "bold",
@@ -1561,35 +1658,35 @@ function HelpOverlay() {
1561
1658
  paddingX: 2,
1562
1659
  paddingY: 1,
1563
1660
  children: [
1564
- /* @__PURE__ */ jsxs9(Text11, { color: theme.title, bold: true, children: [
1661
+ /* @__PURE__ */ jsxs10(Text12, { color: theme.title, bold: true, children: [
1565
1662
  " ",
1566
1663
  "Help"
1567
1664
  ] }),
1568
- /* @__PURE__ */ jsx11(Text11, { children: " " }),
1569
- /* @__PURE__ */ jsx11(Box11, { flexDirection: "row", gap: 4, children: SECTIONS.map((section) => /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", children: [
1570
- /* @__PURE__ */ jsx11(Text11, { color: theme.table.headerFg, bold: true, children: section.title }),
1571
- section.keys.map(([key, desc]) => /* @__PURE__ */ jsxs9(Box11, { gap: 1, children: [
1572
- /* @__PURE__ */ jsxs9(Text11, { color: theme.menu.key, bold: true, children: [
1665
+ /* @__PURE__ */ jsx12(Text12, { children: " " }),
1666
+ /* @__PURE__ */ jsx12(Box12, { flexDirection: "row", gap: 4, children: SECTIONS.map((section) => /* @__PURE__ */ jsxs10(Box12, { flexDirection: "column", children: [
1667
+ /* @__PURE__ */ jsx12(Text12, { color: theme.table.headerFg, bold: true, children: section.title }),
1668
+ section.keys.map(([key, desc]) => /* @__PURE__ */ jsxs10(Box12, { gap: 1, children: [
1669
+ /* @__PURE__ */ jsxs10(Text12, { color: theme.menu.key, bold: true, children: [
1573
1670
  "<",
1574
1671
  (key ?? "").padEnd(5),
1575
1672
  ">"
1576
1673
  ] }),
1577
- /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: desc })
1674
+ /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: desc })
1578
1675
  ] }, key))
1579
1676
  ] }, section.title)) }),
1580
- /* @__PURE__ */ jsx11(Text11, { children: " " }),
1581
- /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Press any key to close" })
1677
+ /* @__PURE__ */ jsx12(Text12, { children: " " }),
1678
+ /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "Press any key to close" })
1582
1679
  ]
1583
1680
  }
1584
1681
  );
1585
1682
  }
1586
1683
 
1587
1684
  // src/tui/components/ConfirmDialog.tsx
1588
- import { Box as Box12, Text as Text12 } from "ink";
1589
- import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1685
+ import { Box as Box13, Text as Text13 } from "ink";
1686
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1590
1687
  function ConfirmDialog({ task }) {
1591
- return /* @__PURE__ */ jsxs10(
1592
- Box12,
1688
+ return /* @__PURE__ */ jsxs11(
1689
+ Box13,
1593
1690
  {
1594
1691
  flexDirection: "column",
1595
1692
  borderStyle: "bold",
@@ -1598,17 +1695,17 @@ function ConfirmDialog({ task }) {
1598
1695
  paddingY: 1,
1599
1696
  alignSelf: "center",
1600
1697
  children: [
1601
- /* @__PURE__ */ jsx12(Text12, { color: theme.dialog.label, bold: true, children: "<Delete>" }),
1602
- /* @__PURE__ */ jsx12(Text12, { children: " " }),
1603
- /* @__PURE__ */ jsxs10(Text12, { color: theme.dialog.fg, children: [
1698
+ /* @__PURE__ */ jsx13(Text13, { color: theme.dialog.label, bold: true, children: "<Delete>" }),
1699
+ /* @__PURE__ */ jsx13(Text13, { children: " " }),
1700
+ /* @__PURE__ */ jsxs11(Text13, { color: theme.dialog.fg, children: [
1604
1701
  'Delete task "',
1605
1702
  task.name,
1606
1703
  '"?'
1607
1704
  ] }),
1608
- /* @__PURE__ */ jsx12(Text12, { children: " " }),
1609
- /* @__PURE__ */ jsxs10(Box12, { gap: 3, children: [
1610
- /* @__PURE__ */ jsx12(Box12, { children: /* @__PURE__ */ jsx12(
1611
- Text12,
1705
+ /* @__PURE__ */ jsx13(Text13, { children: " " }),
1706
+ /* @__PURE__ */ jsxs11(Box13, { gap: 3, children: [
1707
+ /* @__PURE__ */ jsx13(Box13, { children: /* @__PURE__ */ jsx13(
1708
+ Text13,
1612
1709
  {
1613
1710
  backgroundColor: theme.dialog.buttonFocusBg,
1614
1711
  color: theme.dialog.buttonFocusFg,
@@ -1616,7 +1713,7 @@ function ConfirmDialog({ task }) {
1616
1713
  children: " y: OK "
1617
1714
  }
1618
1715
  ) }),
1619
- /* @__PURE__ */ jsx12(Box12, { children: /* @__PURE__ */ jsx12(Text12, { backgroundColor: theme.dialog.buttonBg, color: theme.dialog.buttonFg, children: " n: Cancel " }) })
1716
+ /* @__PURE__ */ jsx13(Box13, { children: /* @__PURE__ */ jsx13(Text13, { backgroundColor: theme.dialog.buttonBg, color: theme.dialog.buttonFg, children: " n: Cancel " }) })
1620
1717
  ] })
1621
1718
  ]
1622
1719
  }
@@ -1624,8 +1721,8 @@ function ConfirmDialog({ task }) {
1624
1721
  }
1625
1722
 
1626
1723
  // src/tui/components/DependencyList.tsx
1627
- import { Box as Box13, Text as Text13 } from "ink";
1628
- import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1724
+ import { Box as Box14, Text as Text14 } from "ink";
1725
+ import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1629
1726
  function TaskRow({
1630
1727
  task,
1631
1728
  globalIndex,
@@ -1633,16 +1730,16 @@ function TaskRow({
1633
1730
  }) {
1634
1731
  const isSelected = globalIndex === selectedIndex;
1635
1732
  const statusColor = STATUS_COLOR[task.status] ?? theme.table.fg;
1636
- return /* @__PURE__ */ jsx13(Box13, { children: isSelected ? /* @__PURE__ */ jsxs11(Text13, { backgroundColor: theme.table.cursorBg, color: theme.table.cursorFg, bold: true, children: [
1733
+ return /* @__PURE__ */ jsx14(Box14, { children: isSelected ? /* @__PURE__ */ jsxs12(Text14, { backgroundColor: theme.table.cursorBg, color: theme.table.cursorFg, bold: true, children: [
1637
1734
  "> ",
1638
1735
  task.id.padEnd(12),
1639
1736
  task.status.padEnd(14),
1640
1737
  task.name
1641
- ] }) : /* @__PURE__ */ jsxs11(Fragment2, { children: [
1642
- /* @__PURE__ */ jsx13(Text13, { children: " " }),
1643
- /* @__PURE__ */ jsx13(Text13, { color: theme.yaml.value, children: task.id.padEnd(12) }),
1644
- /* @__PURE__ */ jsx13(Text13, { color: statusColor, children: task.status.padEnd(14) }),
1645
- /* @__PURE__ */ jsx13(Text13, { color: theme.table.fg, children: task.name })
1738
+ ] }) : /* @__PURE__ */ jsxs12(Fragment2, { children: [
1739
+ /* @__PURE__ */ jsx14(Text14, { children: " " }),
1740
+ /* @__PURE__ */ jsx14(Text14, { color: theme.yaml.value, children: task.id.padEnd(12) }),
1741
+ /* @__PURE__ */ jsx14(Text14, { color: statusColor, children: task.status.padEnd(14) }),
1742
+ /* @__PURE__ */ jsx14(Text14, { color: theme.table.fg, children: task.name })
1646
1743
  ] }) }, task.id);
1647
1744
  }
1648
1745
  function DependencyList({
@@ -1663,23 +1760,23 @@ function DependencyList({
1663
1760
  const relatedOffset = offset;
1664
1761
  offset += related.length;
1665
1762
  const duplicatesOffset = offset;
1666
- return /* @__PURE__ */ jsxs11(Box13, { flexDirection: "column", flexGrow: 1, borderStyle: "bold", borderColor: theme.borderFocus, children: [
1667
- /* @__PURE__ */ jsxs11(Box13, { gap: 0, children: [
1668
- /* @__PURE__ */ jsxs11(Text13, { color: theme.title, bold: true, children: [
1763
+ return /* @__PURE__ */ jsxs12(Box14, { flexDirection: "column", flexGrow: 1, borderStyle: "bold", borderColor: theme.borderFocus, children: [
1764
+ /* @__PURE__ */ jsxs12(Box14, { gap: 0, children: [
1765
+ /* @__PURE__ */ jsxs12(Text14, { color: theme.title, bold: true, children: [
1669
1766
  " ",
1670
1767
  "dependencies"
1671
1768
  ] }),
1672
- /* @__PURE__ */ jsx13(Text13, { color: theme.fg, children: "(" }),
1673
- /* @__PURE__ */ jsx13(Text13, { color: theme.titleHighlight, bold: true, children: task.name }),
1674
- /* @__PURE__ */ jsx13(Text13, { color: theme.fg, children: ")" })
1769
+ /* @__PURE__ */ jsx14(Text14, { color: theme.fg, children: "(" }),
1770
+ /* @__PURE__ */ jsx14(Text14, { color: theme.titleHighlight, bold: true, children: task.name }),
1771
+ /* @__PURE__ */ jsx14(Text14, { color: theme.fg, children: ")" })
1675
1772
  ] }),
1676
- /* @__PURE__ */ jsxs11(Box13, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
1677
- /* @__PURE__ */ jsxs11(Text13, { color: theme.table.headerFg, bold: true, children: [
1773
+ /* @__PURE__ */ jsxs12(Box14, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
1774
+ /* @__PURE__ */ jsxs12(Text14, { color: theme.table.headerFg, bold: true, children: [
1678
1775
  "BLOCKED BY (",
1679
1776
  blockers.length,
1680
1777
  ")"
1681
1778
  ] }),
1682
- blockers.length === 0 ? /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: " No blockers" }) : blockers.map((t, i) => /* @__PURE__ */ jsx13(
1779
+ blockers.length === 0 ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: " No blockers" }) : blockers.map((t, i) => /* @__PURE__ */ jsx14(
1683
1780
  TaskRow,
1684
1781
  {
1685
1782
  task: t,
@@ -1689,13 +1786,13 @@ function DependencyList({
1689
1786
  t.id
1690
1787
  ))
1691
1788
  ] }),
1692
- /* @__PURE__ */ jsxs11(Box13, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
1693
- /* @__PURE__ */ jsxs11(Text13, { color: theme.table.headerFg, bold: true, children: [
1789
+ /* @__PURE__ */ jsxs12(Box14, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
1790
+ /* @__PURE__ */ jsxs12(Text14, { color: theme.table.headerFg, bold: true, children: [
1694
1791
  "BLOCKS (",
1695
1792
  dependents.length,
1696
1793
  ")"
1697
1794
  ] }),
1698
- dependents.length === 0 ? /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: " No dependents" }) : dependents.map((t, i) => /* @__PURE__ */ jsx13(
1795
+ dependents.length === 0 ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: " No dependents" }) : dependents.map((t, i) => /* @__PURE__ */ jsx14(
1699
1796
  TaskRow,
1700
1797
  {
1701
1798
  task: t,
@@ -1705,13 +1802,13 @@ function DependencyList({
1705
1802
  t.id
1706
1803
  ))
1707
1804
  ] }),
1708
- /* @__PURE__ */ jsxs11(Box13, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
1709
- /* @__PURE__ */ jsxs11(Text13, { color: theme.table.headerFg, bold: true, children: [
1805
+ /* @__PURE__ */ jsxs12(Box14, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
1806
+ /* @__PURE__ */ jsxs12(Text14, { color: theme.table.headerFg, bold: true, children: [
1710
1807
  "RELATES TO (",
1711
1808
  related.length,
1712
1809
  ")"
1713
1810
  ] }),
1714
- related.length === 0 ? /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: " No related tasks" }) : related.map((t, i) => /* @__PURE__ */ jsx13(
1811
+ related.length === 0 ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: " No related tasks" }) : related.map((t, i) => /* @__PURE__ */ jsx14(
1715
1812
  TaskRow,
1716
1813
  {
1717
1814
  task: t,
@@ -1721,13 +1818,13 @@ function DependencyList({
1721
1818
  t.id
1722
1819
  ))
1723
1820
  ] }),
1724
- /* @__PURE__ */ jsxs11(Box13, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
1725
- /* @__PURE__ */ jsxs11(Text13, { color: theme.table.headerFg, bold: true, children: [
1821
+ /* @__PURE__ */ jsxs12(Box14, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
1822
+ /* @__PURE__ */ jsxs12(Text14, { color: theme.table.headerFg, bold: true, children: [
1726
1823
  "DUPLICATES (",
1727
1824
  duplicates.length,
1728
1825
  ")"
1729
1826
  ] }),
1730
- duplicates.length === 0 ? /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: " No duplicate tasks" }) : duplicates.map((t, i) => /* @__PURE__ */ jsx13(
1827
+ duplicates.length === 0 ? /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: " No duplicate tasks" }) : duplicates.map((t, i) => /* @__PURE__ */ jsx14(
1731
1828
  TaskRow,
1732
1829
  {
1733
1830
  task: t,
@@ -1737,19 +1834,19 @@ function DependencyList({
1737
1834
  t.id
1738
1835
  ))
1739
1836
  ] }),
1740
- /* @__PURE__ */ jsx13(Box13, { flexGrow: 1 }),
1741
- isAddingDep && /* @__PURE__ */ jsxs11(Box13, { borderStyle: "round", borderColor: theme.prompt, paddingX: 1, children: [
1742
- /* @__PURE__ */ jsx13(Text13, { color: theme.prompt, children: "depends on (id or id:type): " }),
1743
- /* @__PURE__ */ jsx13(Text13, { color: theme.prompt, children: addDepInput }),
1744
- /* @__PURE__ */ jsx13(Text13, { color: theme.promptSuggest, children: "_" })
1837
+ /* @__PURE__ */ jsx14(Box14, { flexGrow: 1 }),
1838
+ isAddingDep && /* @__PURE__ */ jsxs12(Box14, { borderStyle: "round", borderColor: theme.prompt, paddingX: 1, children: [
1839
+ /* @__PURE__ */ jsx14(Text14, { color: theme.prompt, children: "depends on (id or id:type): " }),
1840
+ /* @__PURE__ */ jsx14(Text14, { color: theme.prompt, children: addDepInput }),
1841
+ /* @__PURE__ */ jsx14(Text14, { color: theme.promptSuggest, children: "_" })
1745
1842
  ] }),
1746
- /* @__PURE__ */ jsx13(Box13, { paddingX: 1, children: /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: "a: add dep (id or id:relates-to) | x: remove selected | enter: go to task | esc: back" }) })
1843
+ /* @__PURE__ */ jsx14(Box14, { paddingX: 1, children: /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: "a: add dep (id or id:relates-to) | x: remove selected | enter: go to task | esc: back" }) })
1747
1844
  ] });
1748
1845
  }
1749
1846
 
1750
1847
  // src/tui/components/EpicPanel.tsx
1751
- import { Box as Box14, Text as Text14 } from "ink";
1752
- import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1848
+ import { Box as Box15, Text as Text15 } from "ink";
1849
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
1753
1850
  var PAGE_SIZE2 = 20;
1754
1851
  function EpicPanel({
1755
1852
  epics,
@@ -1762,34 +1859,34 @@ function EpicPanel({
1762
1859
  const currentPage = Math.floor(selectedIndex / PAGE_SIZE2);
1763
1860
  const viewStart = currentPage * PAGE_SIZE2;
1764
1861
  const visibleEpics = epics.slice(viewStart, viewStart + PAGE_SIZE2);
1765
- return /* @__PURE__ */ jsxs12(
1766
- Box14,
1862
+ return /* @__PURE__ */ jsxs13(
1863
+ Box15,
1767
1864
  {
1768
1865
  flexDirection: "column",
1769
1866
  width: 48,
1770
1867
  borderStyle: "bold",
1771
1868
  borderColor: isFocused ? theme.borderFocus : theme.border,
1772
1869
  children: [
1773
- /* @__PURE__ */ jsxs12(Box14, { children: [
1774
- /* @__PURE__ */ jsxs12(Text14, { color: theme.title, bold: true, children: [
1870
+ /* @__PURE__ */ jsxs13(Box15, { children: [
1871
+ /* @__PURE__ */ jsxs13(Text15, { color: theme.title, bold: true, children: [
1775
1872
  " ",
1776
1873
  "epics"
1777
1874
  ] }),
1778
- /* @__PURE__ */ jsxs12(Text14, { color: theme.titleCounter, bold: true, children: [
1875
+ /* @__PURE__ */ jsxs13(Text15, { color: theme.titleCounter, bold: true, children: [
1779
1876
  "[",
1780
1877
  epics.length,
1781
1878
  "]"
1782
1879
  ] }),
1783
- isReordering && /* @__PURE__ */ jsxs12(Text14, { color: theme.flash.warn, bold: true, children: [
1880
+ isReordering && /* @__PURE__ */ jsxs13(Text15, { color: theme.flash.warn, bold: true, children: [
1784
1881
  " ",
1785
1882
  "REORDER"
1786
1883
  ] }),
1787
- filterActive && /* @__PURE__ */ jsxs12(Text14, { color: theme.titleFilter, children: [
1884
+ filterActive && /* @__PURE__ */ jsxs13(Text15, { color: theme.titleFilter, children: [
1788
1885
  " *",
1789
1886
  selectedEpicIds.size
1790
1887
  ] })
1791
1888
  ] }),
1792
- /* @__PURE__ */ jsx14(Box14, { flexDirection: "column", flexGrow: 1, overflowY: "hidden", children: epics.length === 0 ? /* @__PURE__ */ jsx14(Box14, { paddingX: 1, children: /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: "No epics" }) }) : visibleEpics.map((epic, i) => {
1889
+ /* @__PURE__ */ jsx15(Box15, { flexDirection: "column", flexGrow: 1, overflowY: "hidden", children: epics.length === 0 ? /* @__PURE__ */ jsx15(Box15, { paddingX: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "No epics" }) }) : visibleEpics.map((epic, i) => {
1793
1890
  const actualIndex = viewStart + i;
1794
1891
  const isSelected = actualIndex === selectedIndex && isFocused;
1795
1892
  const isChecked = selectedEpicIds.has(epic.id);
@@ -1797,21 +1894,21 @@ function EpicPanel({
1797
1894
  const statusColor = STATUS_COLOR[epic.status] ?? theme.table.fg;
1798
1895
  if (isSelected) {
1799
1896
  const cursorBg = isReordering ? theme.flash.warn : theme.table.cursorBg;
1800
- return /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsxs12(Text14, { backgroundColor: cursorBg, color: theme.table.cursorFg, bold: true, children: [
1897
+ return /* @__PURE__ */ jsx15(Box15, { children: /* @__PURE__ */ jsxs13(Text15, { backgroundColor: cursorBg, color: theme.table.cursorFg, bold: true, children: [
1801
1898
  isReordering ? "~ " : " ",
1802
1899
  marker,
1803
1900
  " ",
1804
1901
  epic.name
1805
1902
  ] }) }, epic.id);
1806
1903
  }
1807
- return /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsxs12(Text14, { color: isChecked ? theme.titleHighlight : statusColor, children: [
1904
+ return /* @__PURE__ */ jsx15(Box15, { children: /* @__PURE__ */ jsxs13(Text15, { color: isChecked ? theme.titleHighlight : statusColor, children: [
1808
1905
  " ",
1809
1906
  marker,
1810
1907
  " ",
1811
1908
  epic.name
1812
1909
  ] }) }, epic.id);
1813
1910
  }) }),
1814
- epics.length > PAGE_SIZE2 && /* @__PURE__ */ jsx14(Box14, { justifyContent: "flex-end", paddingRight: 1, children: /* @__PURE__ */ jsxs12(Text14, { dimColor: true, children: [
1911
+ epics.length > PAGE_SIZE2 && /* @__PURE__ */ jsx15(Box15, { justifyContent: "flex-end", paddingRight: 1, children: /* @__PURE__ */ jsxs13(Text15, { dimColor: true, children: [
1815
1912
  "[",
1816
1913
  viewStart + 1,
1817
1914
  "-",
@@ -1826,16 +1923,16 @@ function EpicPanel({
1826
1923
  }
1827
1924
 
1828
1925
  // src/tui/components/EpicPicker.tsx
1829
- import { useState as useState5 } from "react";
1830
- import { Box as Box15, Text as Text15, useInput as useInput5, useStdout as useStdout3 } from "ink";
1831
- import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
1926
+ import { useState as useState6 } from "react";
1927
+ import { Box as Box16, Text as Text16, useInput as useInput6, useStdout as useStdout3 } from "ink";
1928
+ import { Fragment as Fragment3, jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
1832
1929
  function EpicPicker({ epics, currentEpicId, onSelect, onCancel }) {
1833
1930
  const { stdout } = useStdout3();
1834
1931
  const termHeight = stdout.rows > 0 ? stdout.rows : 24;
1835
1932
  const maxVisible = Math.max(3, termHeight - 10);
1836
- const [searchQuery, setSearchQuery] = useState5("");
1837
- const [isSearching, setIsSearching] = useState5(false);
1838
- const [cursorIndex, setCursorIndex] = useState5(0);
1933
+ const [searchQuery, setSearchQuery] = useState6("");
1934
+ const [isSearching, setIsSearching] = useState6(false);
1935
+ const [cursorIndex, setCursorIndex] = useState6(0);
1839
1936
  const filtered = epics.filter((e) => {
1840
1937
  if (!searchQuery.trim()) return true;
1841
1938
  const q = searchQuery.toLowerCase();
@@ -1849,7 +1946,7 @@ function EpicPicker({ epics, currentEpicId, onSelect, onCancel }) {
1849
1946
  viewStart = cursorIndex;
1850
1947
  }
1851
1948
  const visible = filtered.slice(viewStart, viewStart + maxVisible);
1852
- useInput5((input, key) => {
1949
+ useInput6((input, key) => {
1853
1950
  if (isSearching) {
1854
1951
  if (key.escape) {
1855
1952
  setIsSearching(false);
@@ -1901,53 +1998,53 @@ function EpicPicker({ epics, currentEpicId, onSelect, onCancel }) {
1901
1998
  return;
1902
1999
  }
1903
2000
  });
1904
- return /* @__PURE__ */ jsxs13(Box15, { flexDirection: "column", borderStyle: "bold", borderColor: theme.borderFocus, flexGrow: 1, children: [
1905
- /* @__PURE__ */ jsxs13(Box15, { gap: 0, children: [
1906
- /* @__PURE__ */ jsxs13(Text15, { color: theme.title, bold: true, children: [
2001
+ return /* @__PURE__ */ jsxs14(Box16, { flexDirection: "column", borderStyle: "bold", borderColor: theme.borderFocus, flexGrow: 1, children: [
2002
+ /* @__PURE__ */ jsxs14(Box16, { gap: 0, children: [
2003
+ /* @__PURE__ */ jsxs14(Text16, { color: theme.title, bold: true, children: [
1907
2004
  " ",
1908
2005
  "assign to epic"
1909
2006
  ] }),
1910
- /* @__PURE__ */ jsxs13(Text15, { color: theme.titleCounter, bold: true, children: [
2007
+ /* @__PURE__ */ jsxs14(Text16, { color: theme.titleCounter, bold: true, children: [
1911
2008
  " ",
1912
2009
  "[",
1913
2010
  epics.length,
1914
2011
  "]"
1915
2012
  ] })
1916
2013
  ] }),
1917
- isSearching ? /* @__PURE__ */ jsxs13(Box15, { borderStyle: "round", borderColor: theme.prompt, paddingX: 1, children: [
1918
- /* @__PURE__ */ jsx15(Text15, { color: theme.prompt, children: "/" }),
1919
- /* @__PURE__ */ jsx15(Text15, { color: theme.prompt, children: searchQuery }),
1920
- /* @__PURE__ */ jsx15(Text15, { color: theme.promptSuggest, children: "_" })
1921
- ] }) : searchQuery ? /* @__PURE__ */ jsx15(Box15, { paddingX: 1, children: /* @__PURE__ */ jsxs13(Text15, { color: theme.titleFilter, children: [
2014
+ isSearching ? /* @__PURE__ */ jsxs14(Box16, { borderStyle: "round", borderColor: theme.prompt, paddingX: 1, children: [
2015
+ /* @__PURE__ */ jsx16(Text16, { color: theme.prompt, children: "/" }),
2016
+ /* @__PURE__ */ jsx16(Text16, { color: theme.prompt, children: searchQuery }),
2017
+ /* @__PURE__ */ jsx16(Text16, { color: theme.promptSuggest, children: "_" })
2018
+ ] }) : searchQuery ? /* @__PURE__ */ jsx16(Box16, { paddingX: 1, children: /* @__PURE__ */ jsxs14(Text16, { color: theme.titleFilter, children: [
1922
2019
  "/",
1923
2020
  searchQuery
1924
2021
  ] }) }) : null,
1925
- /* @__PURE__ */ jsx15(Box15, { paddingX: 1, children: /* @__PURE__ */ jsxs13(Text15, { color: theme.table.headerFg, bold: true, children: [
2022
+ /* @__PURE__ */ jsx16(Box16, { paddingX: 1, children: /* @__PURE__ */ jsxs14(Text16, { color: theme.table.headerFg, bold: true, children: [
1926
2023
  " ",
1927
2024
  "ID".padEnd(14),
1928
2025
  "STATUS".padEnd(14),
1929
2026
  "NAME"
1930
2027
  ] }) }),
1931
- filtered.length === 0 ? /* @__PURE__ */ jsx15(Box15, { paddingX: 2, paddingY: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "No epics match the filter" }) }) : visible.map((epic, i) => {
2028
+ filtered.length === 0 ? /* @__PURE__ */ jsx16(Box16, { paddingX: 2, paddingY: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "No epics match the filter" }) }) : visible.map((epic, i) => {
1932
2029
  const actualIndex = viewStart + i;
1933
2030
  const isCursor = actualIndex === cursorIndex;
1934
2031
  const isCurrent = epic.id === currentEpicId;
1935
2032
  const marker = isCurrent ? "* " : " ";
1936
2033
  const statusColor = STATUS_COLOR[epic.status] ?? theme.table.fg;
1937
- return /* @__PURE__ */ jsx15(Box15, { paddingX: 1, children: isCursor ? /* @__PURE__ */ jsxs13(Text15, { backgroundColor: theme.table.cursorBg, color: theme.table.cursorFg, bold: true, children: [
2034
+ return /* @__PURE__ */ jsx16(Box16, { paddingX: 1, children: isCursor ? /* @__PURE__ */ jsxs14(Text16, { backgroundColor: theme.table.cursorBg, color: theme.table.cursorFg, bold: true, children: [
1938
2035
  "> ",
1939
2036
  epic.id.padEnd(14),
1940
2037
  epic.status.padEnd(14),
1941
2038
  epic.name
1942
- ] }) : /* @__PURE__ */ jsxs13(Fragment3, { children: [
1943
- /* @__PURE__ */ jsx15(Text15, { color: isCurrent ? theme.titleHighlight : theme.table.fg, children: marker }),
1944
- /* @__PURE__ */ jsx15(Text15, { color: theme.yaml.value, children: epic.id.padEnd(14) }),
1945
- /* @__PURE__ */ jsx15(Text15, { color: statusColor, children: epic.status.padEnd(14) }),
1946
- /* @__PURE__ */ jsx15(Text15, { color: isCurrent ? theme.titleHighlight : theme.table.fg, children: epic.name })
2039
+ ] }) : /* @__PURE__ */ jsxs14(Fragment3, { children: [
2040
+ /* @__PURE__ */ jsx16(Text16, { color: isCurrent ? theme.titleHighlight : theme.table.fg, children: marker }),
2041
+ /* @__PURE__ */ jsx16(Text16, { color: theme.yaml.value, children: epic.id.padEnd(14) }),
2042
+ /* @__PURE__ */ jsx16(Text16, { color: statusColor, children: epic.status.padEnd(14) }),
2043
+ /* @__PURE__ */ jsx16(Text16, { color: isCurrent ? theme.titleHighlight : theme.table.fg, children: epic.name })
1947
2044
  ] }) }, epic.id);
1948
2045
  }),
1949
- /* @__PURE__ */ jsx15(Box15, { flexGrow: 1 }),
1950
- filtered.length > maxVisible && /* @__PURE__ */ jsx15(Box15, { justifyContent: "flex-end", paddingRight: 1, children: /* @__PURE__ */ jsxs13(Text15, { dimColor: true, children: [
2046
+ /* @__PURE__ */ jsx16(Box16, { flexGrow: 1 }),
2047
+ filtered.length > maxVisible && /* @__PURE__ */ jsx16(Box16, { justifyContent: "flex-end", paddingRight: 1, children: /* @__PURE__ */ jsxs14(Text16, { dimColor: true, children: [
1951
2048
  "[",
1952
2049
  viewStart + 1,
1953
2050
  "-",
@@ -1956,7 +2053,7 @@ function EpicPicker({ epics, currentEpicId, onSelect, onCancel }) {
1956
2053
  filtered.length,
1957
2054
  "]"
1958
2055
  ] }) }),
1959
- /* @__PURE__ */ jsx15(Box15, { paddingX: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "enter: assign | x: unassign | /: search | esc: cancel" }) })
2056
+ /* @__PURE__ */ jsx16(Box16, { paddingX: 1, children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "enter: assign | x: unassign | /: search | esc: cancel" }) })
1960
2057
  ] });
1961
2058
  }
1962
2059
 
@@ -1996,7 +2093,7 @@ function useAutoRefetch(dbPath, onRefetch) {
1996
2093
  }
1997
2094
 
1998
2095
  // src/tui/components/App.tsx
1999
- import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
2096
+ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
2000
2097
  var STATUS_CYCLE = [
2001
2098
  TaskStatus.Backlog,
2002
2099
  TaskStatus.Todo,
@@ -2009,7 +2106,7 @@ function App({ container, initialProject }) {
2009
2106
  const { exit } = useApp();
2010
2107
  const { stdout } = useStdout4();
2011
2108
  const [state, dispatch] = useReducer(appReducer, initialState);
2012
- const [, setResizeTick] = useState6(0);
2109
+ const [, setResizeTick] = useState7(0);
2013
2110
  useEffect2(() => {
2014
2111
  const onResize = () => {
2015
2112
  setResizeTick((n) => n + 1);
@@ -2131,6 +2228,37 @@ function App({ container, initialProject }) {
2131
2228
  });
2132
2229
  loadEpics();
2133
2230
  }, [container, state.epics, state.epicSelectedIndex, loadEpics]);
2231
+ const rerankSelectedToEdge = useCallback2(
2232
+ (kind, edge) => {
2233
+ const isEpic = kind === "epic";
2234
+ const item = isEpic ? state.epics[state.epicSelectedIndex] : state.tasks[state.selectedIndex];
2235
+ if (!item) return;
2236
+ const result = container.taskService.rerankTask({
2237
+ taskId: item.id,
2238
+ ...edge === "top" ? { top: true } : { bottom: true }
2239
+ });
2240
+ dispatch({
2241
+ type: isEpic ? "EXIT_EPIC_REORDER" : "EXIT_REORDER",
2242
+ save: result.ok
2243
+ });
2244
+ dispatch({
2245
+ type: "FLASH",
2246
+ message: result.ok ? `${isEpic ? "Epic moved" : "Moved"} to ${edge}` : result.error.message,
2247
+ level: result.ok ? "info" : "error"
2248
+ });
2249
+ if (isEpic) loadEpics();
2250
+ else loadTasks();
2251
+ },
2252
+ [
2253
+ container,
2254
+ state.tasks,
2255
+ state.selectedIndex,
2256
+ state.epics,
2257
+ state.epicSelectedIndex,
2258
+ loadTasks,
2259
+ loadEpics
2260
+ ]
2261
+ );
2134
2262
  const refetchAll = useCallback2(() => {
2135
2263
  loadProjects();
2136
2264
  loadTasks();
@@ -2143,7 +2271,7 @@ function App({ container, initialProject }) {
2143
2271
  useEffect2(() => {
2144
2272
  if (state.projects.length > 0 && !state.activeProject) {
2145
2273
  logger.info(`TUI.resolveProject: resolving initialProject=${initialProject ?? "(default)"}`);
2146
- const result = container.projectService.resolveProject(initialProject);
2274
+ const result = container.projectService.resolveProjectWithGit(initialProject);
2147
2275
  if (result.ok) {
2148
2276
  logger.info(
2149
2277
  `TUI.resolveProject: resolved to key=${result.value.key} name=${result.value.name}`
@@ -2174,7 +2302,7 @@ function App({ container, initialProject }) {
2174
2302
  }
2175
2303
  return void 0;
2176
2304
  }, [state.flash]);
2177
- useInput6((input, key) => {
2305
+ useInput7((input, key) => {
2178
2306
  if (state.confirmDelete) {
2179
2307
  if (input === "y") {
2180
2308
  const result = container.taskService.deleteTask(state.confirmDelete.id);
@@ -2199,7 +2327,7 @@ function App({ container, initialProject }) {
2199
2327
  dispatch({ type: "GO_BACK" });
2200
2328
  return;
2201
2329
  }
2202
- if (state.activeView === ViewType.TaskCreate || state.activeView === ViewType.TaskEdit || state.activeView === ViewType.ProjectSelector || state.activeView === ViewType.ProjectCreate || state.activeView === ViewType.EpicPicker) {
2330
+ 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) {
2203
2331
  return;
2204
2332
  }
2205
2333
  if (state.activeView === ViewType.DependencyList && state.isAddingDep) {
@@ -2279,6 +2407,14 @@ function App({ container, initialProject }) {
2279
2407
  dispatch({ type: "EPIC_REORDER_MOVE", direction: "down" });
2280
2408
  return;
2281
2409
  }
2410
+ if (input === "t") {
2411
+ rerankSelectedToEdge("epic", "top");
2412
+ return;
2413
+ }
2414
+ if (input === "b") {
2415
+ rerankSelectedToEdge("epic", "bottom");
2416
+ return;
2417
+ }
2282
2418
  if (key.rightArrow) {
2283
2419
  saveEpicReorder();
2284
2420
  return;
@@ -2299,6 +2435,14 @@ function App({ container, initialProject }) {
2299
2435
  dispatch({ type: "REORDER_MOVE", direction: "down" });
2300
2436
  return;
2301
2437
  }
2438
+ if (input === "t") {
2439
+ rerankSelectedToEdge("task", "top");
2440
+ return;
2441
+ }
2442
+ if (input === "b") {
2443
+ rerankSelectedToEdge("task", "bottom");
2444
+ return;
2445
+ }
2302
2446
  if (key.rightArrow) {
2303
2447
  saveReorder();
2304
2448
  return;
@@ -2358,7 +2502,7 @@ function App({ container, initialProject }) {
2358
2502
  dispatch({ type: "ENTER_EPIC_REORDER" });
2359
2503
  dispatch({
2360
2504
  type: "FLASH",
2361
- message: "Reorder: \u2191\u2193 move, \u2192 save, \u2190 cancel",
2505
+ message: "Reorder: \u2191\u2193 move, t top, b bottom, \u2192 save, \u2190 cancel",
2362
2506
  level: "info"
2363
2507
  });
2364
2508
  }
@@ -2482,7 +2626,11 @@ function App({ container, initialProject }) {
2482
2626
  }
2483
2627
  if (state.tasks.length > 0) {
2484
2628
  dispatch({ type: "ENTER_REORDER" });
2485
- dispatch({ type: "FLASH", message: "Reorder: \u2191\u2193 move, \u2192 save, \u2190 cancel", level: "info" });
2629
+ dispatch({
2630
+ type: "FLASH",
2631
+ message: "Reorder: \u2191\u2193 move, t top, b bottom, \u2192 save, \u2190 cancel",
2632
+ level: "info"
2633
+ });
2486
2634
  }
2487
2635
  return;
2488
2636
  }
@@ -2780,6 +2928,57 @@ ${state.selectedTask.additionalRequirements}`;
2780
2928
  const handleProjectFormCancel = useCallback2(() => {
2781
2929
  dispatch({ type: "GO_BACK" });
2782
2930
  }, []);
2931
+ const handleProjectLink = useCallback2((project) => {
2932
+ dispatch({ type: "SET_LINKING_PROJECT", project });
2933
+ dispatch({ type: "NAVIGATE_TO", view: ViewType.ProjectLink });
2934
+ }, []);
2935
+ const handleLinkSave = useCallback2(
2936
+ (remote) => {
2937
+ if (!state.linkingProject) return;
2938
+ const result = container.projectService.linkGitRemote(state.linkingProject.id, remote);
2939
+ if (result.ok) {
2940
+ logger.info(
2941
+ `TUI.linkGitRemote: linked project=${state.linkingProject.id} remote=${result.value.gitRemote}`
2942
+ );
2943
+ dispatch({
2944
+ type: "FLASH",
2945
+ message: `Linked to: ${result.value.gitRemote}`,
2946
+ level: "info"
2947
+ });
2948
+ dispatch({ type: "GO_BACK" });
2949
+ loadProjects();
2950
+ } else {
2951
+ logger.error("TUI.linkGitRemote: failed", result.error);
2952
+ dispatch({ type: "FLASH", message: result.error.message, level: "error" });
2953
+ }
2954
+ },
2955
+ [container, state.linkingProject, loadProjects]
2956
+ );
2957
+ const handleLinkUnlink = useCallback2(() => {
2958
+ if (!state.linkingProject) return;
2959
+ const result = container.projectService.unlinkGitRemote(state.linkingProject.id);
2960
+ if (result.ok) {
2961
+ logger.info(`TUI.unlinkGitRemote: unlinked project=${state.linkingProject.id}`);
2962
+ dispatch({ type: "FLASH", message: "Git remote unlinked", level: "info" });
2963
+ dispatch({ type: "GO_BACK" });
2964
+ loadProjects();
2965
+ } else {
2966
+ logger.error("TUI.unlinkGitRemote: failed", result.error);
2967
+ dispatch({ type: "FLASH", message: result.error.message, level: "error" });
2968
+ }
2969
+ }, [container, state.linkingProject, loadProjects]);
2970
+ const handleLinkDetect = useCallback2(() => {
2971
+ const result = detectGitRemote();
2972
+ if (result.ok && result.value) {
2973
+ dispatch({ type: "FLASH", message: `Detected: ${result.value}`, level: "info" });
2974
+ return result.value;
2975
+ }
2976
+ dispatch({ type: "FLASH", message: "No git remote detected in cwd", level: "warn" });
2977
+ return null;
2978
+ }, []);
2979
+ const handleLinkCancel = useCallback2(() => {
2980
+ dispatch({ type: "GO_BACK" });
2981
+ }, []);
2783
2982
  const handleProjectCancel = useCallback2(() => {
2784
2983
  dispatch({ type: "GO_BACK" });
2785
2984
  }, []);
@@ -2806,12 +3005,12 @@ ${state.selectedTask.additionalRequirements}`;
2806
3005
  loadDeps(previewTaskId);
2807
3006
  }
2808
3007
  }, [state.activeView, previewTaskId, loadDeps]);
2809
- return /* @__PURE__ */ jsxs14(Box16, { flexDirection: "column", height: stdout.rows, children: [
2810
- /* @__PURE__ */ jsx16(Header, { state }),
2811
- /* @__PURE__ */ jsxs14(Box16, { flexDirection: "column", flexGrow: 1, overflowY: "hidden", children: [
2812
- state.confirmDelete && /* @__PURE__ */ jsx16(ConfirmDialog, { task: state.confirmDelete }),
2813
- !state.confirmDelete && state.activeView === ViewType.TaskList && /* @__PURE__ */ jsxs14(Box16, { flexDirection: "row", flexGrow: 1, children: [
2814
- /* @__PURE__ */ jsx16(
3008
+ return /* @__PURE__ */ jsxs15(Box17, { flexDirection: "column", height: stdout.rows, children: [
3009
+ /* @__PURE__ */ jsx17(Header, { state }),
3010
+ /* @__PURE__ */ jsxs15(Box17, { flexDirection: "column", flexGrow: 1, overflowY: "hidden", children: [
3011
+ state.confirmDelete && /* @__PURE__ */ jsx17(ConfirmDialog, { task: state.confirmDelete }),
3012
+ !state.confirmDelete && state.activeView === ViewType.TaskList && /* @__PURE__ */ jsxs15(Box17, { flexDirection: "row", flexGrow: 1, children: [
3013
+ /* @__PURE__ */ jsx17(
2815
3014
  EpicPanel,
2816
3015
  {
2817
3016
  epics: state.epics,
@@ -2821,7 +3020,7 @@ ${state.selectedTask.additionalRequirements}`;
2821
3020
  isReordering: state.isEpicReordering
2822
3021
  }
2823
3022
  ),
2824
- /* @__PURE__ */ jsx16(Box16, { width: taskListWidth, children: /* @__PURE__ */ jsx16(
3023
+ /* @__PURE__ */ jsx17(Box17, { width: taskListWidth, children: /* @__PURE__ */ jsx17(
2825
3024
  TaskList,
2826
3025
  {
2827
3026
  tasks: state.tasks,
@@ -2842,7 +3041,7 @@ ${state.selectedTask.additionalRequirements}`;
2842
3041
  epicFilterActive: state.selectedEpicIds.size > 0
2843
3042
  }
2844
3043
  ) }),
2845
- /* @__PURE__ */ jsx16(Box16, { width: taskDetailWidth, children: previewTask ? /* @__PURE__ */ jsx16(
3044
+ /* @__PURE__ */ jsx17(Box17, { width: taskDetailWidth, children: previewTask ? /* @__PURE__ */ jsx17(
2846
3045
  TaskDetail,
2847
3046
  {
2848
3047
  task: previewTask,
@@ -2853,24 +3052,24 @@ ${state.selectedTask.additionalRequirements}`;
2853
3052
  isFocused: state.focusedPanel === "detail",
2854
3053
  scrollOffset: state.detailScrollOffset
2855
3054
  }
2856
- ) : /* @__PURE__ */ jsxs14(
2857
- Box16,
3055
+ ) : /* @__PURE__ */ jsxs15(
3056
+ Box17,
2858
3057
  {
2859
3058
  flexDirection: "column",
2860
3059
  flexGrow: 1,
2861
3060
  borderStyle: "bold",
2862
3061
  borderColor: theme.border,
2863
3062
  children: [
2864
- /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsxs14(Text16, { color: theme.title, bold: true, children: [
3063
+ /* @__PURE__ */ jsx17(Box17, { children: /* @__PURE__ */ jsxs15(Text17, { color: theme.title, bold: true, children: [
2865
3064
  " ",
2866
3065
  "detail"
2867
3066
  ] }) }),
2868
- /* @__PURE__ */ jsx16(Box16, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "No task selected" }) })
3067
+ /* @__PURE__ */ jsx17(Box17, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsx17(Text17, { dimColor: true, children: "No task selected" }) })
2869
3068
  ]
2870
3069
  }
2871
3070
  ) })
2872
3071
  ] }),
2873
- !state.confirmDelete && state.activeView === ViewType.TaskDetail && state.selectedTask && /* @__PURE__ */ jsx16(
3072
+ !state.confirmDelete && state.activeView === ViewType.TaskDetail && state.selectedTask && /* @__PURE__ */ jsx17(
2874
3073
  TaskDetail,
2875
3074
  {
2876
3075
  task: state.selectedTask,
@@ -2881,7 +3080,7 @@ ${state.selectedTask.additionalRequirements}`;
2881
3080
  scrollOffset: state.detailScrollOffset
2882
3081
  }
2883
3082
  ),
2884
- !state.confirmDelete && state.activeView === ViewType.DependencyList && state.selectedTask && /* @__PURE__ */ jsx16(
3083
+ !state.confirmDelete && state.activeView === ViewType.DependencyList && state.selectedTask && /* @__PURE__ */ jsx17(
2885
3084
  DependencyList,
2886
3085
  {
2887
3086
  task: state.selectedTask,
@@ -2894,7 +3093,7 @@ ${state.selectedTask.additionalRequirements}`;
2894
3093
  addDepInput: state.addDepInput
2895
3094
  }
2896
3095
  ),
2897
- !state.confirmDelete && (state.activeView === ViewType.TaskCreate || state.activeView === ViewType.TaskEdit) && /* @__PURE__ */ jsx16(
3096
+ !state.confirmDelete && (state.activeView === ViewType.TaskCreate || state.activeView === ViewType.TaskEdit) && /* @__PURE__ */ jsx17(
2898
3097
  TaskForm,
2899
3098
  {
2900
3099
  editingTask: state.activeView === ViewType.TaskEdit ? state.selectedTask : null,
@@ -2904,7 +3103,7 @@ ${state.selectedTask.additionalRequirements}`;
2904
3103
  onCancel: handleFormCancel
2905
3104
  }
2906
3105
  ),
2907
- !state.confirmDelete && state.activeView === ViewType.EpicPicker && state.selectedTask && /* @__PURE__ */ jsx16(
3106
+ !state.confirmDelete && state.activeView === ViewType.EpicPicker && state.selectedTask && /* @__PURE__ */ jsx17(
2908
3107
  EpicPicker,
2909
3108
  {
2910
3109
  epics: state.epics,
@@ -2913,7 +3112,7 @@ ${state.selectedTask.additionalRequirements}`;
2913
3112
  onCancel: handleEpicPickerCancel
2914
3113
  }
2915
3114
  ),
2916
- !state.confirmDelete && state.activeView === ViewType.ProjectSelector && /* @__PURE__ */ jsx16(
3115
+ !state.confirmDelete && state.activeView === ViewType.ProjectSelector && /* @__PURE__ */ jsx17(
2917
3116
  ProjectSelector,
2918
3117
  {
2919
3118
  projects: state.projects,
@@ -2921,21 +3120,32 @@ ${state.selectedTask.additionalRequirements}`;
2921
3120
  onSelect: handleProjectSelect,
2922
3121
  onCreate: handleProjectCreate,
2923
3122
  onSetDefault: handleSetDefault,
3123
+ onLink: handleProjectLink,
2924
3124
  onCancel: handleProjectCancel
2925
3125
  }
2926
3126
  ),
2927
- !state.confirmDelete && state.activeView === ViewType.ProjectCreate && /* @__PURE__ */ jsx16(ProjectForm, { onSave: handleProjectFormSave, onCancel: handleProjectFormCancel }),
2928
- !state.confirmDelete && state.activeView === ViewType.Help && /* @__PURE__ */ jsx16(HelpOverlay, {})
3127
+ !state.confirmDelete && state.activeView === ViewType.ProjectCreate && /* @__PURE__ */ jsx17(ProjectForm, { onSave: handleProjectFormSave, onCancel: handleProjectFormCancel }),
3128
+ !state.confirmDelete && state.activeView === ViewType.ProjectLink && state.linkingProject && /* @__PURE__ */ jsx17(
3129
+ ProjectLinkForm,
3130
+ {
3131
+ project: state.linkingProject,
3132
+ onSave: handleLinkSave,
3133
+ onUnlink: handleLinkUnlink,
3134
+ onDetect: handleLinkDetect,
3135
+ onCancel: handleLinkCancel
3136
+ }
3137
+ ),
3138
+ !state.confirmDelete && state.activeView === ViewType.Help && /* @__PURE__ */ jsx17(HelpOverlay, {})
2929
3139
  ] }),
2930
- /* @__PURE__ */ jsx16(Crumbs, { breadcrumbs: state.breadcrumbs }),
2931
- state.flash && /* @__PURE__ */ jsx16(FlashMessage, { message: state.flash.message, level: state.flash.level })
3140
+ /* @__PURE__ */ jsx17(Crumbs, { breadcrumbs: state.breadcrumbs }),
3141
+ state.flash && /* @__PURE__ */ jsx17(FlashMessage, { message: state.flash.message, level: state.flash.level })
2932
3142
  ] });
2933
3143
  }
2934
3144
 
2935
3145
  // src/tui/index.tsx
2936
- import { jsx as jsx17 } from "react/jsx-runtime";
3146
+ import { jsx as jsx18 } from "react/jsx-runtime";
2937
3147
  async function launchTUI(container, initialProject) {
2938
- const instance = render(/* @__PURE__ */ jsx17(App, { container, initialProject }), {
3148
+ const instance = render(/* @__PURE__ */ jsx18(App, { container, initialProject }), {
2939
3149
  exitOnCtrlC: true
2940
3150
  });
2941
3151
  await instance.waitUntilExit();
@@ -2943,4 +3153,4 @@ async function launchTUI(container, initialProject) {
2943
3153
  export {
2944
3154
  launchTUI
2945
3155
  };
2946
- //# sourceMappingURL=tui-5JJH67YY.js.map
3156
+ //# sourceMappingURL=tui-4GNIGMCK.js.map