ralphctl 0.3.0 → 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.
@@ -54,6 +54,7 @@ import {
54
54
  taskReorderCommand,
55
55
  taskShowCommand,
56
56
  taskStatusCommand,
57
+ taskWhyCommand,
57
58
  ticketEditCommand,
58
59
  ticketListCommand,
59
60
  ticketRefineCommand,
@@ -61,7 +62,7 @@ import {
61
62
  ticketShowCommand,
62
63
  useCurrentPrompt,
63
64
  validateConfigValue
64
- } from "./chunk-4GHVNKLV.mjs";
65
+ } from "./chunk-MRN3Z2XC.mjs";
65
66
  import {
66
67
  PromptCancelledError,
67
68
  projectAddCommand
@@ -71,7 +72,7 @@ import {
71
72
  } from "./chunk-3QBEBKMZ.mjs";
72
73
  import {
73
74
  ticketAddCommand
74
- } from "./chunk-JXMHLW42.mjs";
75
+ } from "./chunk-7JLZQICD.mjs";
75
76
  import {
76
77
  addProjectRepo,
77
78
  createProject,
@@ -108,7 +109,7 @@ import {
108
109
  sprintStartCommand,
109
110
  updateTaskStatus,
110
111
  withSuspendedTui
111
- } from "./chunk-CDOPLXFK.mjs";
112
+ } from "./chunk-JYCGQA2D.mjs";
112
113
  import {
113
114
  addTicket,
114
115
  allRequirementsApproved,
@@ -118,8 +119,9 @@ import {
118
119
  getTicket,
119
120
  listTickets,
120
121
  removeTicket,
122
+ truncate,
121
123
  updateTicket
122
- } from "./chunk-HL4ZMHCQ.mjs";
124
+ } from "./chunk-JOQO4HMM.mjs";
123
125
  import "./chunk-CFUVE2BP.mjs";
124
126
  import {
125
127
  getPrompt,
@@ -322,7 +324,7 @@ import { useEffect as useEffect28, useState as useState30 } from "react";
322
324
  import { Box as Box35, useStdout } from "ink";
323
325
 
324
326
  // src/integration/ui/tui/views/view-router.tsx
325
- import { useCallback as useCallback10, useMemo as useMemo34, useRef as useRef2, useState as useState29 } from "react";
327
+ import { useCallback as useCallback10, useMemo as useMemo35, useRef as useRef3, useState as useState29 } from "react";
326
328
  import { Box as Box34, useApp, useInput as useInput19 } from "ink";
327
329
 
328
330
  // src/integration/ui/tui/components/banner.tsx
@@ -553,6 +555,7 @@ function buildTaskSubMenu(ctx) {
553
555
  items.push(titled("VIEW"));
554
556
  items.push({ name: "List", value: "list", description: "List all tasks" });
555
557
  items.push({ name: "Next", value: "next", description: "Get next task" });
558
+ items.push({ name: "Why Blocked?", value: "why", description: "Explain why a task is blocked" });
556
559
  items.push(blank());
557
560
  items.push(titled("MANAGE"));
558
561
  items.push({ name: "Add", value: "add", description: "Add a new task" });
@@ -1034,9 +1037,27 @@ var commandMap = {
1034
1037
  show: () => sprintShowCommand([]),
1035
1038
  context: () => sprintContextCommand([]),
1036
1039
  current: () => sprintCurrentCommand(["-"]),
1037
- refine: () => sprintRefineCommand([]),
1040
+ refine: async () => {
1041
+ const mode = await getPrompt().select({
1042
+ message: "How should refinement run?",
1043
+ choices: [
1044
+ { label: "Interactive \u2014 approve requirements for each ticket", value: "interactive" },
1045
+ { label: "Auto \u2014 AI drafts requirements without prompts", value: "auto" }
1046
+ ]
1047
+ });
1048
+ await sprintRefineCommand(mode === "auto" ? ["--auto"] : []);
1049
+ },
1038
1050
  ideate: () => sprintIdeateCommand([]),
1039
- plan: () => sprintPlanCommand([]),
1051
+ plan: async () => {
1052
+ const mode = await getPrompt().select({
1053
+ message: "How should planning run?",
1054
+ choices: [
1055
+ { label: "Interactive \u2014 pick affected repos manually", value: "interactive" },
1056
+ { label: "Auto \u2014 AI explores all repos autonomously", value: "auto" }
1057
+ ]
1058
+ });
1059
+ await sprintPlanCommand(mode === "auto" ? ["--auto", "--all-paths"] : []);
1060
+ },
1040
1061
  start: () => sprintStartCommand([]),
1041
1062
  requirements: () => sprintRequirementsCommand([]),
1042
1063
  health: () => sprintHealthCommand(),
@@ -1061,6 +1082,7 @@ var commandMap = {
1061
1082
  show: () => taskShowCommand([]),
1062
1083
  status: () => taskStatusCommand([]),
1063
1084
  next: () => taskNextCommand(),
1085
+ why: () => taskWhyCommand(),
1064
1086
  reorder: () => taskReorderCommand([]),
1065
1087
  remove: () => taskRemoveCommand([])
1066
1088
  },
@@ -1641,11 +1663,11 @@ function SettingsView() {
1641
1663
  }
1642
1664
 
1643
1665
  // src/integration/ui/tui/views/execute-view.tsx
1644
- import { useEffect as useEffect7, useRef, useState as useState7 } from "react";
1645
- import { Box as Box16, Text as Text15, useInput as useInput5 } from "ink";
1666
+ import { useEffect as useEffect8, useRef as useRef2, useState as useState8 } from "react";
1667
+ import { Box as Box17, Text as Text16, useInput as useInput5 } from "ink";
1646
1668
 
1647
1669
  // src/integration/ui/tui/runtime/hooks.ts
1648
- import { useCallback as useCallback4, useEffect as useEffect6, useState as useState6 } from "react";
1670
+ import { useCallback as useCallback4, useEffect as useEffect6, useRef, useState as useState6 } from "react";
1649
1671
  function useLoggerEvents(limit = 200) {
1650
1672
  const [buffer, setBuffer] = useState6([]);
1651
1673
  useEffect6(() => {
@@ -1674,14 +1696,31 @@ function useSignalEvents(bus, limit = 200) {
1674
1696
  }, [bus, limit]);
1675
1697
  return buffer;
1676
1698
  }
1699
+ var DASHBOARD_REFRESH_THROTTLE_MS = 500;
1677
1700
  function useDashboardData() {
1678
1701
  const [data, setData] = useState6(null);
1679
1702
  const [loading, setLoading] = useState6(true);
1680
1703
  const [error, setError] = useState6(null);
1681
1704
  const [counter, setCounter] = useState6(0);
1705
+ const lastRefreshAt = useRef(0);
1706
+ const pendingRefresh = useRef(null);
1682
1707
  const refresh = useCallback4(() => {
1683
1708
  setCounter((n) => n + 1);
1684
1709
  }, []);
1710
+ const scheduleRefresh = useCallback4(() => {
1711
+ const elapsed = Date.now() - lastRefreshAt.current;
1712
+ if (elapsed >= DASHBOARD_REFRESH_THROTTLE_MS) {
1713
+ lastRefreshAt.current = Date.now();
1714
+ setCounter((n) => n + 1);
1715
+ return;
1716
+ }
1717
+ if (pendingRefresh.current) return;
1718
+ pendingRefresh.current = setTimeout(() => {
1719
+ pendingRefresh.current = null;
1720
+ lastRefreshAt.current = Date.now();
1721
+ setCounter((n) => n + 1);
1722
+ }, DASHBOARD_REFRESH_THROTTLE_MS - elapsed);
1723
+ }, []);
1685
1724
  useEffect6(() => {
1686
1725
  const cancel = { current: false };
1687
1726
  setLoading(true);
@@ -1701,6 +1740,24 @@ function useDashboardData() {
1701
1740
  cancel.current = true;
1702
1741
  };
1703
1742
  }, [counter]);
1743
+ useEffect6(() => {
1744
+ let bus;
1745
+ try {
1746
+ bus = getSharedDeps().signalBus;
1747
+ } catch {
1748
+ return;
1749
+ }
1750
+ const unsubscribe = bus.subscribe(() => {
1751
+ scheduleRefresh();
1752
+ });
1753
+ return () => {
1754
+ unsubscribe();
1755
+ if (pendingRefresh.current) {
1756
+ clearTimeout(pendingRefresh.current);
1757
+ pendingRefresh.current = null;
1758
+ }
1759
+ };
1760
+ }, [scheduleRefresh]);
1704
1761
  return { data, loading, error, refresh };
1705
1762
  }
1706
1763
 
@@ -1790,9 +1847,34 @@ function SprintSummary({ tasks, width = 30 }) {
1790
1847
  }
1791
1848
 
1792
1849
  // src/integration/ui/tui/components/log-tail.tsx
1793
- import "react";
1850
+ import { useMemo as useMemo5 } from "react";
1851
+ import { Box as Box15, Text as Text14 } from "ink";
1852
+
1853
+ // src/integration/ui/tui/components/spinner.tsx
1854
+ import { useEffect as useEffect7, useState as useState7 } from "react";
1794
1855
  import { Box as Box14, Text as Text13 } from "ink";
1795
1856
  import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1857
+ function Spinner({ label, color: color2 = inkColors.warning, intervalMs = 80 }) {
1858
+ const [frame, setFrame] = useState7(0);
1859
+ const isAwaitingPrompt = label.startsWith("Awaiting");
1860
+ useEffect7(() => {
1861
+ if (isAwaitingPrompt) return;
1862
+ const id = setInterval(() => {
1863
+ setFrame((f) => (f + 1) % glyphs.spinner.length);
1864
+ }, intervalMs);
1865
+ return () => {
1866
+ clearInterval(id);
1867
+ };
1868
+ }, [intervalMs, isAwaitingPrompt]);
1869
+ if (isAwaitingPrompt) return null;
1870
+ return /* @__PURE__ */ jsxs13(Box14, { children: [
1871
+ /* @__PURE__ */ jsx16(Text13, { color: color2, bold: true, children: glyphs.spinner[frame] }),
1872
+ /* @__PURE__ */ jsx16(Text13, { children: ` ${label}` })
1873
+ ] });
1874
+ }
1875
+
1876
+ // src/integration/ui/tui/components/log-tail.tsx
1877
+ import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
1796
1878
  function levelColor(level) {
1797
1879
  switch (level) {
1798
1880
  case "error":
@@ -1812,43 +1894,46 @@ function levelColor(level) {
1812
1894
  return void 0;
1813
1895
  }
1814
1896
  }
1815
- function renderLine(event, index) {
1897
+ function renderLine(event, index, isActiveSpinner) {
1816
1898
  switch (event.kind) {
1817
1899
  case "log":
1818
- return /* @__PURE__ */ jsx16(Text13, { color: levelColor(event.level), dimColor: event.level === "dim" || event.level === "debug", children: event.message }, index);
1900
+ return /* @__PURE__ */ jsx17(Text14, { color: levelColor(event.level), dimColor: event.level === "dim" || event.level === "debug", children: event.message }, index);
1819
1901
  case "header":
1820
- return /* @__PURE__ */ jsxs13(Text13, { bold: true, children: [
1902
+ return /* @__PURE__ */ jsxs14(Text14, { bold: true, children: [
1821
1903
  event.icon ? `${event.icon} ` : "",
1822
1904
  event.title
1823
1905
  ] }, index);
1824
1906
  case "separator":
1825
- return /* @__PURE__ */ jsx16(Text13, { dimColor: true, children: "\u2500".repeat(Math.min(event.width, 40)) }, index);
1907
+ return /* @__PURE__ */ jsx17(Text14, { dimColor: true, children: "\u2500".repeat(Math.min(event.width, 40)) }, index);
1826
1908
  case "field":
1827
- return /* @__PURE__ */ jsxs13(Text13, { children: [
1828
- /* @__PURE__ */ jsxs13(Text13, { dimColor: true, children: [
1909
+ return /* @__PURE__ */ jsxs14(Text14, { children: [
1910
+ /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
1829
1911
  event.label,
1830
1912
  ": "
1831
1913
  ] }),
1832
1914
  event.value
1833
1915
  ] }, index);
1834
1916
  case "card":
1835
- return /* @__PURE__ */ jsx16(Text13, { bold: true, children: event.title }, index);
1917
+ return /* @__PURE__ */ jsx17(Text14, { bold: true, children: event.title }, index);
1836
1918
  case "newline":
1837
- return /* @__PURE__ */ jsx16(Text13, { children: " " }, index);
1919
+ return /* @__PURE__ */ jsx17(Text14, { children: " " }, index);
1838
1920
  case "spinner-start":
1839
- return /* @__PURE__ */ jsxs13(Text13, { color: inkColors.info, children: [
1921
+ if (isActiveSpinner) {
1922
+ return /* @__PURE__ */ jsx17(Box15, { children: /* @__PURE__ */ jsx17(Spinner, { label: event.message, color: inkColors.info }) }, index);
1923
+ }
1924
+ return /* @__PURE__ */ jsxs14(Text14, { color: inkColors.info, children: [
1840
1925
  glyphs.phaseDisabled,
1841
1926
  " ",
1842
1927
  event.message
1843
1928
  ] }, index);
1844
1929
  case "spinner-succeed":
1845
- return /* @__PURE__ */ jsxs13(Text13, { color: inkColors.success, children: [
1930
+ return /* @__PURE__ */ jsxs14(Text14, { color: inkColors.success, children: [
1846
1931
  glyphs.check,
1847
1932
  " ",
1848
1933
  event.message
1849
1934
  ] }, index);
1850
1935
  case "spinner-fail":
1851
- return /* @__PURE__ */ jsxs13(Text13, { color: inkColors.error, children: [
1936
+ return /* @__PURE__ */ jsxs14(Text14, { color: inkColors.error, children: [
1852
1937
  glyphs.cross,
1853
1938
  " ",
1854
1939
  event.message
@@ -1864,25 +1949,37 @@ function renderLine(event, index) {
1864
1949
  }
1865
1950
  function LogTail({ events, limit = 8 }) {
1866
1951
  const tail = events.slice(-limit);
1867
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
1868
- /* @__PURE__ */ jsx16(Text13, { dimColor: true, children: "\u2500\u2500 Log \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
1869
- tail.length === 0 ? /* @__PURE__ */ jsx16(Text13, { dimColor: true, children: "(no activity yet)" }) : tail.map((event, i) => renderLine(event, i))
1952
+ const resolvedIds = useMemo5(() => {
1953
+ const ids = /* @__PURE__ */ new Set();
1954
+ for (const ev of events) {
1955
+ if (ev.kind === "spinner-succeed" || ev.kind === "spinner-fail" || ev.kind === "spinner-stop") {
1956
+ ids.add(ev.id);
1957
+ }
1958
+ }
1959
+ return ids;
1960
+ }, [events]);
1961
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
1962
+ /* @__PURE__ */ jsx17(Text14, { dimColor: true, children: "\u2500\u2500 Log \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
1963
+ tail.length === 0 ? /* @__PURE__ */ jsx17(Text14, { dimColor: true, children: "(no activity yet)" }) : tail.map((event, i) => {
1964
+ const active = event.kind === "spinner-start" && !resolvedIds.has(event.id);
1965
+ return renderLine(event, i, active);
1966
+ })
1870
1967
  ] });
1871
1968
  }
1872
1969
 
1873
1970
  // src/integration/ui/tui/components/rate-limit-banner.tsx
1874
1971
  import "react";
1875
- import { Box as Box15, Text as Text14 } from "ink";
1876
- import { jsxs as jsxs14 } from "react/jsx-runtime";
1972
+ import { Box as Box16, Text as Text15 } from "ink";
1973
+ import { jsxs as jsxs15 } from "react/jsx-runtime";
1877
1974
  function RateLimitBanner({ pausedSince, delayMs }) {
1878
1975
  if (!pausedSince) return null;
1879
1976
  const seconds = Math.max(0, Math.round(delayMs / 1e3));
1880
- return /* @__PURE__ */ jsxs14(Box15, { borderStyle: "round", borderColor: inkColors.warning, paddingX: spacing.gutter, children: [
1881
- /* @__PURE__ */ jsxs14(Text14, { color: inkColors.warning, bold: true, children: [
1977
+ return /* @__PURE__ */ jsxs15(Box16, { borderStyle: "round", borderColor: inkColors.warning, paddingX: spacing.gutter, children: [
1978
+ /* @__PURE__ */ jsxs15(Text15, { color: inkColors.warning, bold: true, children: [
1882
1979
  glyphs.warningGlyph,
1883
1980
  " Rate limit hit"
1884
1981
  ] }),
1885
- /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
1982
+ /* @__PURE__ */ jsxs15(Text15, { dimColor: true, children: [
1886
1983
  " \u2014 new tasks paused",
1887
1984
  seconds > 0 ? ` (~${String(seconds)}s)` : "",
1888
1985
  ". Running tasks continue."
@@ -1891,7 +1988,7 @@ function RateLimitBanner({ pausedSince, delayMs }) {
1891
1988
  }
1892
1989
 
1893
1990
  // src/integration/ui/tui/views/execute-view.tsx
1894
- import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
1991
+ import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
1895
1992
  var EXECUTE_HINTS_RUNNING = [];
1896
1993
  var EXECUTE_HINTS_DONE = [{ key: "Enter", action: "home" }];
1897
1994
  function initialState() {
@@ -1901,6 +1998,7 @@ function initialState() {
1901
1998
  running: /* @__PURE__ */ new Set(),
1902
1999
  blocked: /* @__PURE__ */ new Set(),
1903
2000
  activity: /* @__PURE__ */ new Map(),
2001
+ currentStep: /* @__PURE__ */ new Map(),
1904
2002
  summary: null,
1905
2003
  error: null,
1906
2004
  rateLimit: null
@@ -1911,10 +2009,10 @@ function ExecuteView({ sprintId, executionOptions }) {
1911
2009
  const shared = getSharedDeps();
1912
2010
  const signalEvents = useSignalEvents(shared.signalBus);
1913
2011
  const logEvents = useLoggerEvents(200);
1914
- const [state, setState] = useState7(initialState);
1915
- const [done, setDone] = useState7(false);
1916
- const processedCountRef = useRef(0);
1917
- useEffect7(() => {
2012
+ const [state, setState] = useState8(initialState);
2013
+ const [done, setDone] = useState8(false);
2014
+ const processedCountRef = useRef2(0);
2015
+ useEffect8(() => {
1918
2016
  const cancel = { current: false };
1919
2017
  const load = async () => {
1920
2018
  try {
@@ -1932,7 +2030,7 @@ function ExecuteView({ sprintId, executionOptions }) {
1932
2030
  cancel.current = true;
1933
2031
  };
1934
2032
  }, [shared, sprintId]);
1935
- useEffect7(() => {
2033
+ useEffect8(() => {
1936
2034
  if (state.sprint === null || done) return;
1937
2035
  const cancel = { current: false };
1938
2036
  const run = async () => {
@@ -1957,14 +2055,23 @@ function ExecuteView({ sprintId, executionOptions }) {
1957
2055
  cancel.current = true;
1958
2056
  };
1959
2057
  }, [state.sprint, shared, sprintId, executionOptions, done]);
1960
- useEffect7(() => {
2058
+ useEffect8(() => {
1961
2059
  if (signalEvents.length <= processedCountRef.current) return;
1962
2060
  const fresh = signalEvents.slice(processedCountRef.current);
1963
2061
  processedCountRef.current = signalEvents.length;
1964
2062
  setState((prev) => reduceEvents(prev, fresh));
1965
- }, [signalEvents]);
1966
- const [closePromptRun, setClosePromptRun] = useState7(false);
1967
- useEffect7(() => {
2063
+ if (fresh.some((e) => e.type === "task-finished")) {
2064
+ void (async () => {
2065
+ try {
2066
+ const tasks = await shared.persistence.getTasks(sprintId);
2067
+ setState((s) => ({ ...s, tasks }));
2068
+ } catch {
2069
+ }
2070
+ })();
2071
+ }
2072
+ }, [signalEvents, shared, sprintId]);
2073
+ const [closePromptRun, setClosePromptRun] = useState8(false);
2074
+ useEffect8(() => {
1968
2075
  if (!done) return;
1969
2076
  void (async () => {
1970
2077
  try {
@@ -1995,19 +2102,19 @@ function ExecuteView({ sprintId, executionOptions }) {
1995
2102
  }
1996
2103
  });
1997
2104
  useViewHints(done ? EXECUTE_HINTS_DONE : EXECUTE_HINTS_RUNNING);
1998
- return /* @__PURE__ */ jsxs15(ViewShell, { title: "Execute", children: [
1999
- /* @__PURE__ */ jsxs15(Box16, { children: [
2000
- /* @__PURE__ */ jsx17(Text15, { bold: true, color: inkColors.primary, children: state.sprint?.name ?? "Sprint" }),
2001
- /* @__PURE__ */ jsxs15(Text15, { dimColor: true, children: [
2105
+ return /* @__PURE__ */ jsxs16(ViewShell, { title: "Execute", children: [
2106
+ /* @__PURE__ */ jsxs16(Box17, { children: [
2107
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: inkColors.primary, children: state.sprint?.name ?? "Sprint" }),
2108
+ /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
2002
2109
  " ",
2003
2110
  state.sprint?.branch ? `[${state.sprint.branch}]` : "",
2004
2111
  " ",
2005
2112
  state.sprint?.status ? `(${state.sprint.status})` : ""
2006
2113
  ] })
2007
2114
  ] }),
2008
- /* @__PURE__ */ jsx17(Box16, { marginTop: spacing.section, children: /* @__PURE__ */ jsx17(SprintSummary, { tasks: state.tasks }) }),
2009
- state.rateLimit ? /* @__PURE__ */ jsx17(Box16, { marginTop: spacing.section, children: /* @__PURE__ */ jsx17(RateLimitBanner, { pausedSince: state.rateLimit.pausedSince, delayMs: state.rateLimit.delayMs }) }) : null,
2010
- /* @__PURE__ */ jsx17(Box16, { marginTop: spacing.section, children: /* @__PURE__ */ jsx17(
2115
+ /* @__PURE__ */ jsx18(Box17, { marginTop: spacing.section, children: /* @__PURE__ */ jsx18(SprintSummary, { tasks: state.tasks }) }),
2116
+ state.rateLimit ? /* @__PURE__ */ jsx18(Box17, { marginTop: spacing.section, children: /* @__PURE__ */ jsx18(RateLimitBanner, { pausedSince: state.rateLimit.pausedSince, delayMs: state.rateLimit.delayMs }) }) : null,
2117
+ /* @__PURE__ */ jsx18(Box17, { marginTop: spacing.section, children: /* @__PURE__ */ jsx18(
2011
2118
  TaskGrid,
2012
2119
  {
2013
2120
  tasks: state.tasks,
@@ -2016,18 +2123,23 @@ function ExecuteView({ sprintId, executionOptions }) {
2016
2123
  activityByTask: state.activity
2017
2124
  }
2018
2125
  ) }),
2019
- /* @__PURE__ */ jsx17(Box16, { marginTop: spacing.section, children: /* @__PURE__ */ jsx17(LogTail, { events: logEvents }) }),
2020
- state.error ? /* @__PURE__ */ jsx17(Box16, { marginTop: spacing.section, children: /* @__PURE__ */ jsxs15(Text15, { color: inkColors.error, children: [
2126
+ !done && state.currentStep.size > 0 ? /* @__PURE__ */ jsx18(Box17, { marginTop: spacing.section, flexDirection: "column", children: Array.from(state.currentStep.entries()).map(([taskId, label]) => {
2127
+ const task = state.tasks.find((t) => t.id === taskId);
2128
+ const taskName = task?.name ?? taskId.slice(0, 8);
2129
+ return /* @__PURE__ */ jsx18(Spinner, { label: `${taskName} ${glyphs.emDash} ${label}` }, taskId);
2130
+ }) }) : null,
2131
+ /* @__PURE__ */ jsx18(Box17, { marginTop: spacing.section, children: /* @__PURE__ */ jsx18(LogTail, { events: logEvents }) }),
2132
+ state.error ? /* @__PURE__ */ jsx18(Box17, { marginTop: spacing.section, children: /* @__PURE__ */ jsxs16(Text16, { color: inkColors.error, children: [
2021
2133
  glyphs.cross,
2022
2134
  " ",
2023
2135
  state.error
2024
2136
  ] }) }) : null,
2025
- state.summary && done ? /* @__PURE__ */ jsxs15(Box16, { marginTop: spacing.section, flexDirection: "column", children: [
2026
- /* @__PURE__ */ jsxs15(Text15, { color: inkColors.success, bold: true, children: [
2137
+ state.summary && done ? /* @__PURE__ */ jsxs16(Box17, { marginTop: spacing.section, flexDirection: "column", children: [
2138
+ /* @__PURE__ */ jsxs16(Text16, { color: inkColors.success, bold: true, children: [
2027
2139
  glyphs.check,
2028
2140
  " Execution finished"
2029
2141
  ] }),
2030
- /* @__PURE__ */ jsxs15(Text15, { dimColor: true, children: [
2142
+ /* @__PURE__ */ jsxs16(Text16, { dimColor: true, children: [
2031
2143
  state.summary.completed,
2032
2144
  " completed ",
2033
2145
  glyphs.inlineDot,
@@ -2043,14 +2155,28 @@ function ExecuteView({ sprintId, executionOptions }) {
2043
2155
  state.summary.stopReason,
2044
2156
  ")"
2045
2157
  ] }),
2046
- /* @__PURE__ */ jsx17(Box16, { marginTop: spacing.section, children: /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Press any key to return home." }) })
2158
+ /* @__PURE__ */ jsx18(Box17, { marginTop: spacing.section, children: /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Press any key to return home." }) })
2047
2159
  ] }) : null
2048
2160
  ] });
2049
2161
  }
2162
+ var STEP_LABELS = {
2163
+ "branch-preflight": "Verifying branch\u2026",
2164
+ "contract-negotiate": "Writing contract\u2026",
2165
+ "mark-in-progress": "Starting\u2026",
2166
+ "execute-task": "Running Claude\u2026",
2167
+ "store-verification": "Storing verification\u2026",
2168
+ "post-task-check": "Running post-task check\u2026",
2169
+ "evaluate-task": "Evaluating\u2026",
2170
+ "mark-done": "Finalizing\u2026"
2171
+ };
2172
+ function labelForStep(stepName) {
2173
+ return STEP_LABELS[stepName] ?? stepName;
2174
+ }
2050
2175
  function reduceEvents(state, events) {
2051
2176
  const running = new Set(state.running);
2052
2177
  const blocked = new Set(state.blocked);
2053
2178
  const activity = new Map(state.activity);
2179
+ const currentStep = new Map(state.currentStep);
2054
2180
  let rateLimit = state.rateLimit;
2055
2181
  for (const event of events) {
2056
2182
  switch (event.type) {
@@ -2059,10 +2185,22 @@ function reduceEvents(state, events) {
2059
2185
  break;
2060
2186
  case "task-finished":
2061
2187
  running.delete(event.taskId);
2188
+ activity.delete(event.taskId);
2189
+ currentStep.delete(event.taskId);
2062
2190
  if (event.status === "blocked" || event.status === "failed") {
2063
2191
  blocked.add(event.taskId);
2064
2192
  }
2065
2193
  break;
2194
+ case "task-step":
2195
+ if (event.phase === "start") {
2196
+ activity.set(event.taskId, labelForStep(event.stepName));
2197
+ currentStep.set(event.taskId, labelForStep(event.stepName));
2198
+ } else {
2199
+ if (currentStep.get(event.taskId) === labelForStep(event.stepName)) {
2200
+ currentStep.delete(event.taskId);
2201
+ }
2202
+ }
2203
+ break;
2066
2204
  case "rate-limit-paused":
2067
2205
  rateLimit = { pausedSince: event.timestamp, delayMs: event.delayMs };
2068
2206
  break;
@@ -2085,36 +2223,13 @@ function reduceEvents(state, events) {
2085
2223
  }
2086
2224
  }
2087
2225
  }
2088
- return { ...state, running, blocked, activity, rateLimit };
2226
+ return { ...state, running, blocked, activity, currentStep, rateLimit };
2089
2227
  }
2090
2228
 
2091
2229
  // src/integration/ui/tui/views/dashboard-view.tsx
2092
2230
  import { useEffect as useEffect9, useState as useState9 } from "react";
2093
2231
  import { Box as Box20, Text as Text19 } from "ink";
2094
2232
 
2095
- // src/integration/ui/tui/components/spinner.tsx
2096
- import { useEffect as useEffect8, useState as useState8 } from "react";
2097
- import { Box as Box17, Text as Text16 } from "ink";
2098
- import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
2099
- function Spinner({ label, color: color2 = inkColors.warning, intervalMs = 80 }) {
2100
- const [frame, setFrame] = useState8(0);
2101
- const isAwaitingPrompt = label.startsWith("Awaiting");
2102
- useEffect8(() => {
2103
- if (isAwaitingPrompt) return;
2104
- const id = setInterval(() => {
2105
- setFrame((f) => (f + 1) % glyphs.spinner.length);
2106
- }, intervalMs);
2107
- return () => {
2108
- clearInterval(id);
2109
- };
2110
- }, [intervalMs, isAwaitingPrompt]);
2111
- if (isAwaitingPrompt) return null;
2112
- return /* @__PURE__ */ jsxs16(Box17, { children: [
2113
- /* @__PURE__ */ jsx18(Text16, { color: color2, bold: true, children: glyphs.spinner[frame] }),
2114
- /* @__PURE__ */ jsx18(Text16, { children: ` ${label}` })
2115
- ] });
2116
- }
2117
-
2118
2233
  // src/integration/ui/tui/components/result-card.tsx
2119
2234
  import "react";
2120
2235
  import { Box as Box19, Text as Text18 } from "ink";
@@ -2177,7 +2292,7 @@ async function loadRecentProgress(sprintId, limit) {
2177
2292
  const headerMatch = /^##\s+(.+)$/m.exec(entry);
2178
2293
  const header = headerMatch?.[1]?.trim() ?? "Entry";
2179
2294
  const body = entry.replace(/^##\s+.+$/gm, "").replace(/^\*\*Project:\*\*.+$/gm, "").replace(/^###\s+.+$/gm, "").replace(/\s+/g, " ").trim();
2180
- const preview = body.length > 200 ? body.slice(0, 197) + "\u2026" : body;
2295
+ const preview = truncate(body, 200);
2181
2296
  return { header, preview };
2182
2297
  });
2183
2298
  } catch {
@@ -2383,6 +2498,15 @@ function RefinePhaseView({ sprintId }) {
2383
2498
  useEffect10(() => {
2384
2499
  void loadSprint();
2385
2500
  }, [loadSprint]);
2501
+ useEffect10(() => {
2502
+ if (!state.running) return;
2503
+ const handle = setInterval(() => {
2504
+ void loadSprint();
2505
+ }, 1e3);
2506
+ return () => {
2507
+ clearInterval(handle);
2508
+ };
2509
+ }, [state.running, loadSprint]);
2386
2510
  const runRefine = useCallback5(async () => {
2387
2511
  setState((s) => ({ ...s, running: true, error: null, records: [] }));
2388
2512
  try {
@@ -2597,7 +2721,7 @@ function statusColor2(status) {
2597
2721
 
2598
2722
  // src/integration/ui/tui/views/phases/close-phase-view.tsx
2599
2723
  import { spawnSync } from "child_process";
2600
- import { useCallback as useCallback7, useEffect as useEffect12, useMemo as useMemo5, useState as useState12 } from "react";
2724
+ import { useCallback as useCallback7, useEffect as useEffect12, useMemo as useMemo6, useState as useState12 } from "react";
2601
2725
  import { Box as Box24, Text as Text23, useInput as useInput8 } from "ink";
2602
2726
  import { jsx as jsx25, jsxs as jsxs23 } from "react/jsx-runtime";
2603
2727
  var HINTS_READY = [
@@ -2631,7 +2755,7 @@ function ClosePhaseView({ sprintId }) {
2631
2755
  useEffect12(() => {
2632
2756
  void load();
2633
2757
  }, [load]);
2634
- const actions = useMemo5(() => {
2758
+ const actions = useMemo6(() => {
2635
2759
  const sprint2 = state.sprint;
2636
2760
  if (sprint2?.status !== "active") return [];
2637
2761
  const base = ["close"];
@@ -2876,7 +3000,7 @@ ID: ${sprintId}`
2876
3000
  }
2877
3001
 
2878
3002
  // src/integration/ui/tui/views/workflows/create-sprint-view.tsx
2879
- import { useMemo as useMemo6 } from "react";
3003
+ import { useMemo as useMemo7 } from "react";
2880
3004
 
2881
3005
  // src/integration/ui/tui/views/workflows/use-workflow.ts
2882
3006
  import { useCallback as useCallback8, useEffect as useEffect13, useState as useState13 } from "react";
@@ -2970,7 +3094,7 @@ function CreateSprintView() {
2970
3094
  setPhase({ kind: "done", sprint, project, setAsCurrent });
2971
3095
  }
2972
3096
  });
2973
- const hints = useMemo6(() => phase.kind === "running" ? HINTS_RUNNING : HINTS_DONE, [phase.kind]);
3097
+ const hints = useMemo7(() => phase.kind === "running" ? HINTS_RUNNING : HINTS_DONE, [phase.kind]);
2974
3098
  useViewHints(hints);
2975
3099
  return /* @__PURE__ */ jsx26(ViewShell, { title: TITLE, children: renderBody(phase) });
2976
3100
  }
@@ -3012,7 +3136,7 @@ function renderBody(phase) {
3012
3136
  }
3013
3137
 
3014
3138
  // src/integration/ui/tui/views/workflows/delete-sprint-view.tsx
3015
- import { useMemo as useMemo7 } from "react";
3139
+ import { useMemo as useMemo8 } from "react";
3016
3140
  import { jsx as jsx27 } from "react/jsx-runtime";
3017
3141
  var TITLE2 = "Delete Sprint";
3018
3142
  var HINTS_RUNNING2 = [{ key: "Esc", action: "cancel" }];
@@ -3074,7 +3198,7 @@ function DeleteSprintView({ sprintId: initial2 }) {
3074
3198
  }
3075
3199
  });
3076
3200
  const running = phase.kind === "running" || phase.kind === "loading";
3077
- const hints = useMemo7(() => running ? HINTS_RUNNING2 : HINTS_DONE2, [running]);
3201
+ const hints = useMemo8(() => running ? HINTS_RUNNING2 : HINTS_DONE2, [running]);
3078
3202
  useViewHints(hints);
3079
3203
  return /* @__PURE__ */ jsx27(ViewShell, { title: TITLE2, children: renderBody2(phase) });
3080
3204
  }
@@ -3121,7 +3245,7 @@ function runningLabel(step) {
3121
3245
  }
3122
3246
 
3123
3247
  // src/integration/ui/tui/views/workflows/set-current-sprint-view.tsx
3124
- import { useMemo as useMemo8 } from "react";
3248
+ import { useMemo as useMemo9 } from "react";
3125
3249
  import { jsx as jsx28 } from "react/jsx-runtime";
3126
3250
  var TITLE3 = "Set Current Sprint";
3127
3251
  var HINTS_RUNNING3 = [{ key: "Esc", action: "cancel" }];
@@ -3154,7 +3278,7 @@ function SetCurrentSprintView() {
3154
3278
  }
3155
3279
  });
3156
3280
  const running = phase.kind === "loading" || phase.kind === "running";
3157
- const hints = useMemo8(() => running ? HINTS_RUNNING3 : HINTS_DONE3, [running]);
3281
+ const hints = useMemo9(() => running ? HINTS_RUNNING3 : HINTS_DONE3, [running]);
3158
3282
  useViewHints(hints);
3159
3283
  return /* @__PURE__ */ jsx28(ViewShell, { title: TITLE3, children: renderBody3(phase) });
3160
3284
  }
@@ -3192,7 +3316,7 @@ function renderBody3(phase) {
3192
3316
 
3193
3317
  // src/integration/ui/tui/views/workflows/requirements-export-view.tsx
3194
3318
  import { join } from "path";
3195
- import { useMemo as useMemo9 } from "react";
3319
+ import { useMemo as useMemo10 } from "react";
3196
3320
  import { jsx as jsx29 } from "react/jsx-runtime";
3197
3321
  var TITLE4 = "Export Requirements";
3198
3322
  var HINTS_RUNNING4 = [{ key: "Esc", action: "cancel" }];
@@ -3227,7 +3351,7 @@ function RequirementsExportView({ sprintId }) {
3227
3351
  });
3228
3352
  }
3229
3353
  });
3230
- const hints = useMemo9(() => phase.kind === "running" ? HINTS_RUNNING4 : HINTS_DONE4, [phase.kind]);
3354
+ const hints = useMemo10(() => phase.kind === "running" ? HINTS_RUNNING4 : HINTS_DONE4, [phase.kind]);
3231
3355
  useViewHints(hints);
3232
3356
  return /* @__PURE__ */ jsx29(ViewShell, { title: TITLE4, children: renderBody4(phase) });
3233
3357
  }
@@ -3274,7 +3398,7 @@ function renderBody4(phase) {
3274
3398
  // src/integration/ui/tui/views/workflows/context-export-view.tsx
3275
3399
  import { writeFile } from "fs/promises";
3276
3400
  import { join as join2 } from "path";
3277
- import { useMemo as useMemo10 } from "react";
3401
+ import { useMemo as useMemo11 } from "react";
3278
3402
  import { jsx as jsx30 } from "react/jsx-runtime";
3279
3403
  var TITLE5 = "Export Context";
3280
3404
  var HINTS_RUNNING5 = [{ key: "Esc", action: "cancel" }];
@@ -3304,7 +3428,7 @@ function ContextExportView({ sprintId }) {
3304
3428
  });
3305
3429
  }
3306
3430
  });
3307
- const hints = useMemo10(() => phase.kind === "running" ? HINTS_RUNNING5 : HINTS_DONE5, [phase.kind]);
3431
+ const hints = useMemo11(() => phase.kind === "running" ? HINTS_RUNNING5 : HINTS_DONE5, [phase.kind]);
3308
3432
  useViewHints(hints);
3309
3433
  return /* @__PURE__ */ jsx30(ViewShell, { title: TITLE5, children: renderBody5(phase) });
3310
3434
  }
@@ -3404,7 +3528,7 @@ async function renderContextMarkdown(sprint, tasks) {
3404
3528
  }
3405
3529
 
3406
3530
  // src/integration/ui/tui/views/workflows/ticket-add-view.tsx
3407
- import { useMemo as useMemo11 } from "react";
3531
+ import { useMemo as useMemo12 } from "react";
3408
3532
  import { jsx as jsx31 } from "react/jsx-runtime";
3409
3533
  var TITLE6 = "Add Ticket";
3410
3534
  var HINTS_RUNNING6 = [{ key: "Esc", action: "cancel" }];
@@ -3417,7 +3541,8 @@ var STEP_LABEL = {
3417
3541
  fetching: "Fetching issue data\u2026",
3418
3542
  title: "Awaiting ticket title\u2026",
3419
3543
  description: "Awaiting ticket description\u2026",
3420
- saving: "Saving ticket\u2026"
3544
+ saving: "Saving ticket\u2026",
3545
+ another: "Add another ticket?"
3421
3546
  };
3422
3547
  function isValidUrl(value) {
3423
3548
  try {
@@ -3452,43 +3577,55 @@ function TicketAddView() {
3452
3577
  setPhase({ kind: "no-project" });
3453
3578
  return;
3454
3579
  }
3455
- setPhase({ kind: "running", step: "link" });
3456
- const link = await prompt.input({
3457
- message: "Issue link (optional):",
3458
- validate: (v) => {
3459
- const trimmed = v.trim();
3460
- if (trimmed.length === 0) return true;
3461
- return isValidUrl(trimmed) ? true : "Must be a valid URL (or leave blank)";
3580
+ let count = 0;
3581
+ while (true) {
3582
+ setPhase({ kind: "running", step: "link" });
3583
+ const link = await prompt.input({
3584
+ message: "Issue link (optional):",
3585
+ validate: (v) => {
3586
+ const trimmed = v.trim();
3587
+ if (trimmed.length === 0) return true;
3588
+ return isValidUrl(trimmed) ? true : "Must be a valid URL (or leave blank)";
3589
+ }
3590
+ });
3591
+ const trimmedLink = link.trim();
3592
+ let prefill = null;
3593
+ if (trimmedLink.length > 0) {
3594
+ setPhase({ kind: "running", step: "fetching" });
3595
+ prefill = tryFetchIssue(trimmedLink);
3596
+ }
3597
+ setPhase({ kind: "running", step: "title" });
3598
+ const title = await prompt.input({
3599
+ message: "Title:",
3600
+ default: prefill?.title,
3601
+ validate: (v) => v.trim().length > 0 ? true : "Title is required"
3602
+ });
3603
+ setPhase({ kind: "running", step: "description" });
3604
+ const description = await prompt.editor({
3605
+ message: "Description (recommended)",
3606
+ default: prefill?.body
3607
+ });
3608
+ setPhase({ kind: "running", step: "saving" });
3609
+ const trimmedDescription = description?.trim() ?? "";
3610
+ const ticket = await addTicket({
3611
+ title: title.trim(),
3612
+ description: trimmedDescription.length > 0 ? trimmedDescription : void 0,
3613
+ link: trimmedLink.length > 0 ? trimmedLink : void 0
3614
+ });
3615
+ count++;
3616
+ setPhase({ kind: "running", step: "another" });
3617
+ const another = await prompt.confirm({
3618
+ message: `Add another ticket? (${String(count)} added)`,
3619
+ default: true
3620
+ });
3621
+ if (!another) {
3622
+ setPhase({ kind: "done", ticket, project, prefilled: prefill !== null, count });
3623
+ return;
3462
3624
  }
3463
- });
3464
- const trimmedLink = link.trim();
3465
- let prefill = null;
3466
- if (trimmedLink.length > 0) {
3467
- setPhase({ kind: "running", step: "fetching" });
3468
- prefill = tryFetchIssue(trimmedLink);
3469
3625
  }
3470
- setPhase({ kind: "running", step: "title" });
3471
- const title = await prompt.input({
3472
- message: "Title:",
3473
- default: prefill?.title,
3474
- validate: (v) => v.trim().length > 0 ? true : "Title is required"
3475
- });
3476
- setPhase({ kind: "running", step: "description" });
3477
- const description = await prompt.editor({
3478
- message: "Description (recommended)",
3479
- default: prefill?.body
3480
- });
3481
- setPhase({ kind: "running", step: "saving" });
3482
- const trimmedDescription = description?.trim() ?? "";
3483
- const ticket = await addTicket({
3484
- title: title.trim(),
3485
- description: trimmedDescription.length > 0 ? trimmedDescription : void 0,
3486
- link: trimmedLink.length > 0 ? trimmedLink : void 0
3487
- });
3488
- setPhase({ kind: "done", ticket, project, prefilled: prefill !== null });
3489
3626
  }
3490
3627
  });
3491
- const hints = useMemo11(() => phase.kind === "running" ? HINTS_RUNNING6 : HINTS_DONE6, [phase.kind]);
3628
+ const hints = useMemo12(() => phase.kind === "running" ? HINTS_RUNNING6 : HINTS_DONE6, [phase.kind]);
3492
3629
  useViewHints(hints);
3493
3630
  return /* @__PURE__ */ jsx31(ViewShell, { title: TITLE6, children: renderBody6(phase) });
3494
3631
  }
@@ -3517,26 +3654,28 @@ function renderBody6(phase) {
3517
3654
  );
3518
3655
  case "error":
3519
3656
  return /* @__PURE__ */ jsx31(ResultCard, { kind: "error", title: "Could not add ticket", lines: [phase.message] });
3520
- case "done":
3657
+ case "done": {
3658
+ const title = phase.count > 1 ? `${String(phase.count)} tickets added` : phase.prefilled ? "Ticket added (prefilled from issue)" : "Ticket added";
3521
3659
  return /* @__PURE__ */ jsx31(
3522
3660
  ResultCard,
3523
3661
  {
3524
3662
  kind: "success",
3525
- title: phase.prefilled ? "Ticket added (prefilled from issue)" : "Ticket added",
3663
+ title,
3526
3664
  fields: [
3527
- ["ID", phase.ticket.id],
3528
- ["Title", phase.ticket.title],
3665
+ ["Last ID", phase.ticket.id],
3666
+ ["Last title", phase.ticket.title],
3529
3667
  ["Project", `${phase.project.displayName} (${phase.project.name})`],
3530
3668
  ["Status", `requirement: ${phase.ticket.requirementStatus}`]
3531
3669
  ],
3532
3670
  nextSteps: [{ action: "Refine requirements", description: "Home \u2192 Next: Refine Requirements" }]
3533
3671
  }
3534
3672
  );
3673
+ }
3535
3674
  }
3536
3675
  }
3537
3676
 
3538
3677
  // src/integration/ui/tui/views/workflows/ticket-edit-view.tsx
3539
- import { useMemo as useMemo12 } from "react";
3678
+ import { useMemo as useMemo13 } from "react";
3540
3679
  import { jsx as jsx32 } from "react/jsx-runtime";
3541
3680
  var TITLE7 = "Edit Ticket";
3542
3681
  var HINTS_RUNNING7 = [{ key: "Esc", action: "cancel" }];
@@ -3611,7 +3750,7 @@ function TicketEditView({ ticketId } = {}) {
3611
3750
  setPhase({ kind: "done", ticket, field });
3612
3751
  }
3613
3752
  });
3614
- const hints = useMemo12(() => phase.kind === "running" ? HINTS_RUNNING7 : HINTS_DONE7, [phase.kind]);
3753
+ const hints = useMemo13(() => phase.kind === "running" ? HINTS_RUNNING7 : HINTS_DONE7, [phase.kind]);
3615
3754
  useViewHints(hints);
3616
3755
  return /* @__PURE__ */ jsx32(ViewShell, { title: TITLE7, children: renderBody7(phase) });
3617
3756
  }
@@ -3660,7 +3799,7 @@ function stepLabel(step) {
3660
3799
  }
3661
3800
 
3662
3801
  // src/integration/ui/tui/views/workflows/ticket-remove-view.tsx
3663
- import { useMemo as useMemo13 } from "react";
3802
+ import { useMemo as useMemo14 } from "react";
3664
3803
  import { jsx as jsx33 } from "react/jsx-runtime";
3665
3804
  var TITLE8 = "Remove Ticket";
3666
3805
  var HINTS_RUNNING8 = [{ key: "Esc", action: "cancel" }];
@@ -3709,7 +3848,7 @@ function TicketRemoveView() {
3709
3848
  setPhase({ kind: "done", id: target.id, title: target.title });
3710
3849
  }
3711
3850
  });
3712
- const hints = useMemo13(() => phase.kind === "running" ? HINTS_RUNNING8 : HINTS_DONE8, [phase.kind]);
3851
+ const hints = useMemo14(() => phase.kind === "running" ? HINTS_RUNNING8 : HINTS_DONE8, [phase.kind]);
3713
3852
  useViewHints(hints);
3714
3853
  return /* @__PURE__ */ jsx33(ViewShell, { title: TITLE8, children: renderBody8(phase) });
3715
3854
  }
@@ -3746,7 +3885,7 @@ function stepLabel2(step) {
3746
3885
  }
3747
3886
 
3748
3887
  // src/integration/ui/tui/views/workflows/ticket-refine-view.tsx
3749
- import { useMemo as useMemo14 } from "react";
3888
+ import { useMemo as useMemo15 } from "react";
3750
3889
  import { jsx as jsx34 } from "react/jsx-runtime";
3751
3890
  var TITLE9 = "Re-Refine Ticket";
3752
3891
  var HINTS_RUNNING9 = [{ key: "Esc", action: "cancel" }];
@@ -3782,7 +3921,7 @@ function TicketRefineView() {
3782
3921
  setPhase({ kind: "done", ticketTitle: target.title });
3783
3922
  }
3784
3923
  });
3785
- const hints = useMemo14(() => phase.kind === "running" ? HINTS_RUNNING9 : HINTS_DONE9, [phase.kind]);
3924
+ const hints = useMemo15(() => phase.kind === "running" ? HINTS_RUNNING9 : HINTS_DONE9, [phase.kind]);
3786
3925
  useViewHints(hints);
3787
3926
  return /* @__PURE__ */ jsx34(ViewShell, { title: TITLE9, children: renderBody9(phase) });
3788
3927
  }
@@ -3817,7 +3956,7 @@ function renderBody9(phase) {
3817
3956
  }
3818
3957
 
3819
3958
  // src/integration/ui/tui/views/workflows/task-add-view.tsx
3820
- import { useMemo as useMemo15 } from "react";
3959
+ import { useMemo as useMemo16 } from "react";
3821
3960
  import { jsx as jsx35 } from "react/jsx-runtime";
3822
3961
  var TITLE10 = "Add Task";
3823
3962
  var HINTS_RUNNING10 = [{ key: "Esc", action: "cancel" }];
@@ -3884,7 +4023,7 @@ function TaskAddView() {
3884
4023
  setPhase({ kind: "done", task, repo });
3885
4024
  }
3886
4025
  });
3887
- const hints = useMemo15(() => phase.kind === "running" ? HINTS_RUNNING10 : HINTS_DONE10, [phase.kind]);
4026
+ const hints = useMemo16(() => phase.kind === "running" ? HINTS_RUNNING10 : HINTS_DONE10, [phase.kind]);
3888
4027
  useViewHints(hints);
3889
4028
  return /* @__PURE__ */ jsx35(ViewShell, { title: TITLE10, children: renderBody10(phase) });
3890
4029
  }
@@ -3922,7 +4061,7 @@ function stepLabel3(step) {
3922
4061
  }
3923
4062
 
3924
4063
  // src/integration/ui/tui/views/workflows/task-import-view.tsx
3925
- import { useMemo as useMemo16 } from "react";
4064
+ import { useMemo as useMemo17 } from "react";
3926
4065
  import { jsx as jsx36 } from "react/jsx-runtime";
3927
4066
  var TITLE11 = "Import Tasks";
3928
4067
  var HINTS_RUNNING11 = [{ key: "Esc", action: "cancel" }];
@@ -3946,7 +4085,7 @@ function TaskImportView() {
3946
4085
  setPhase({ kind: "done" });
3947
4086
  }
3948
4087
  });
3949
- const hints = useMemo16(() => phase.kind === "running" ? HINTS_RUNNING11 : HINTS_DONE11, [phase.kind]);
4088
+ const hints = useMemo17(() => phase.kind === "running" ? HINTS_RUNNING11 : HINTS_DONE11, [phase.kind]);
3950
4089
  useViewHints(hints);
3951
4090
  return /* @__PURE__ */ jsx36(ViewShell, { title: TITLE11, children: renderBody11(phase) });
3952
4091
  }
@@ -3964,7 +4103,7 @@ function renderBody11(phase) {
3964
4103
  }
3965
4104
 
3966
4105
  // src/integration/ui/tui/views/workflows/task-status-view.tsx
3967
- import { useMemo as useMemo17 } from "react";
4106
+ import { useMemo as useMemo18 } from "react";
3968
4107
  import { jsx as jsx37 } from "react/jsx-runtime";
3969
4108
  var TITLE12 = "Task Status";
3970
4109
  var HINTS_RUNNING12 = [{ key: "Esc", action: "cancel" }];
@@ -4011,7 +4150,7 @@ function TaskStatusView() {
4011
4150
  setPhase({ kind: "done", task });
4012
4151
  }
4013
4152
  });
4014
- const hints = useMemo17(() => phase.kind === "running" ? HINTS_RUNNING12 : HINTS_DONE12, [phase.kind]);
4153
+ const hints = useMemo18(() => phase.kind === "running" ? HINTS_RUNNING12 : HINTS_DONE12, [phase.kind]);
4015
4154
  useViewHints(hints);
4016
4155
  return /* @__PURE__ */ jsx37(ViewShell, { title: TITLE12, children: renderBody12(phase) });
4017
4156
  }
@@ -4053,7 +4192,7 @@ function stepLabel4(step) {
4053
4192
  }
4054
4193
 
4055
4194
  // src/integration/ui/tui/views/workflows/task-reorder-view.tsx
4056
- import { useMemo as useMemo18 } from "react";
4195
+ import { useMemo as useMemo19 } from "react";
4057
4196
  import { jsx as jsx38 } from "react/jsx-runtime";
4058
4197
  var TITLE13 = "Reorder Task";
4059
4198
  var HINTS_RUNNING13 = [{ key: "Esc", action: "cancel" }];
@@ -4101,7 +4240,7 @@ function TaskReorderView() {
4101
4240
  setPhase({ kind: "done", task });
4102
4241
  }
4103
4242
  });
4104
- const hints = useMemo18(() => phase.kind === "running" ? HINTS_RUNNING13 : HINTS_DONE13, [phase.kind]);
4243
+ const hints = useMemo19(() => phase.kind === "running" ? HINTS_RUNNING13 : HINTS_DONE13, [phase.kind]);
4105
4244
  useViewHints(hints);
4106
4245
  return /* @__PURE__ */ jsx38(ViewShell, { title: TITLE13, children: renderBody13(phase) });
4107
4246
  }
@@ -4136,7 +4275,7 @@ function stepLabel5(step) {
4136
4275
  }
4137
4276
 
4138
4277
  // src/integration/ui/tui/views/workflows/task-remove-view.tsx
4139
- import { useMemo as useMemo19 } from "react";
4278
+ import { useMemo as useMemo20 } from "react";
4140
4279
  import { jsx as jsx39 } from "react/jsx-runtime";
4141
4280
  var TITLE14 = "Remove Task";
4142
4281
  var HINTS_RUNNING14 = [{ key: "Esc", action: "cancel" }];
@@ -4181,7 +4320,7 @@ function TaskRemoveView() {
4181
4320
  setPhase({ kind: "done", id: target.id, name: target.name });
4182
4321
  }
4183
4322
  });
4184
- const hints = useMemo19(() => phase.kind === "running" ? HINTS_RUNNING14 : HINTS_DONE14, [phase.kind]);
4323
+ const hints = useMemo20(() => phase.kind === "running" ? HINTS_RUNNING14 : HINTS_DONE14, [phase.kind]);
4185
4324
  useViewHints(hints);
4186
4325
  return /* @__PURE__ */ jsx39(ViewShell, { title: TITLE14, children: renderBody14(phase) });
4187
4326
  }
@@ -4218,7 +4357,7 @@ function stepLabel6(step) {
4218
4357
  }
4219
4358
 
4220
4359
  // src/integration/ui/tui/views/workflows/task-next-view.tsx
4221
- import { useMemo as useMemo20 } from "react";
4360
+ import { useMemo as useMemo21 } from "react";
4222
4361
  import { jsx as jsx40 } from "react/jsx-runtime";
4223
4362
  var TITLE15 = "Next Task";
4224
4363
  var HINTS_RUNNING15 = [{ key: "Esc", action: "cancel" }];
@@ -4239,7 +4378,7 @@ function TaskNextView() {
4239
4378
  setPhase({ kind: "ready", task });
4240
4379
  }
4241
4380
  });
4242
- const hints = useMemo20(() => phase.kind === "running" ? HINTS_RUNNING15 : HINTS_DONE15, [phase.kind]);
4381
+ const hints = useMemo21(() => phase.kind === "running" ? HINTS_RUNNING15 : HINTS_DONE15, [phase.kind]);
4243
4382
  useViewHints(hints);
4244
4383
  return /* @__PURE__ */ jsx40(ViewShell, { title: TITLE15, children: renderBody15(phase) });
4245
4384
  }
@@ -4271,7 +4410,7 @@ function renderBody15(phase) {
4271
4410
 
4272
4411
  // src/integration/ui/tui/views/workflows/project-add-view.tsx
4273
4412
  import { resolve } from "path";
4274
- import { useMemo as useMemo21 } from "react";
4413
+ import { useMemo as useMemo22 } from "react";
4275
4414
  import { jsx as jsx41 } from "react/jsx-runtime";
4276
4415
  var TITLE16 = "Add Project";
4277
4416
  var HINTS_RUNNING16 = [{ key: "Esc", action: "cancel" }];
@@ -4318,7 +4457,7 @@ function ProjectAddView() {
4318
4457
  setPhase({ kind: "done", project });
4319
4458
  }
4320
4459
  });
4321
- const hints = useMemo21(() => phase.kind === "running" ? HINTS_RUNNING16 : HINTS_DONE16, [phase.kind]);
4460
+ const hints = useMemo22(() => phase.kind === "running" ? HINTS_RUNNING16 : HINTS_DONE16, [phase.kind]);
4322
4461
  useViewHints(hints);
4323
4462
  return /* @__PURE__ */ jsx41(ViewShell, { title: TITLE16, children: renderBody16(phase) });
4324
4463
  }
@@ -4359,7 +4498,7 @@ function stepLabel7(step) {
4359
4498
  }
4360
4499
 
4361
4500
  // src/integration/ui/tui/views/workflows/project-remove-view.tsx
4362
- import { useMemo as useMemo22 } from "react";
4501
+ import { useMemo as useMemo23 } from "react";
4363
4502
  import { jsx as jsx42 } from "react/jsx-runtime";
4364
4503
  var TITLE17 = "Remove Project";
4365
4504
  var HINTS_RUNNING17 = [{ key: "Esc", action: "cancel" }];
@@ -4401,7 +4540,7 @@ function ProjectRemoveView() {
4401
4540
  setPhase({ kind: "done", name });
4402
4541
  }
4403
4542
  });
4404
- const hints = useMemo22(() => phase.kind === "running" ? HINTS_RUNNING17 : HINTS_DONE17, [phase.kind]);
4543
+ const hints = useMemo23(() => phase.kind === "running" ? HINTS_RUNNING17 : HINTS_DONE17, [phase.kind]);
4405
4544
  useViewHints(hints);
4406
4545
  return /* @__PURE__ */ jsx42(ViewShell, { title: TITLE17, children: renderBody17(phase) });
4407
4546
  }
@@ -4427,7 +4566,7 @@ function stepLabel8(step) {
4427
4566
 
4428
4567
  // src/integration/ui/tui/views/workflows/project-repo-add-view.tsx
4429
4568
  import { resolve as resolve2 } from "path";
4430
- import { useMemo as useMemo23 } from "react";
4569
+ import { useMemo as useMemo24 } from "react";
4431
4570
  import { jsx as jsx43 } from "react/jsx-runtime";
4432
4571
  var TITLE18 = "Add Repository";
4433
4572
  var HINTS_RUNNING18 = [{ key: "Esc", action: "cancel" }];
@@ -4463,7 +4602,7 @@ function ProjectRepoAddView() {
4463
4602
  setPhase({ kind: "done", project, repoName });
4464
4603
  }
4465
4604
  });
4466
- const hints = useMemo23(() => phase.kind === "running" ? HINTS_RUNNING18 : HINTS_DONE18, [phase.kind]);
4605
+ const hints = useMemo24(() => phase.kind === "running" ? HINTS_RUNNING18 : HINTS_DONE18, [phase.kind]);
4467
4606
  useViewHints(hints);
4468
4607
  return /* @__PURE__ */ jsx43(ViewShell, { title: TITLE18, children: renderBody18(phase) });
4469
4608
  }
@@ -4497,7 +4636,7 @@ function stepLabel9(step) {
4497
4636
  }
4498
4637
 
4499
4638
  // src/integration/ui/tui/views/workflows/project-repo-remove-view.tsx
4500
- import { useMemo as useMemo24 } from "react";
4639
+ import { useMemo as useMemo25 } from "react";
4501
4640
  import { jsx as jsx44 } from "react/jsx-runtime";
4502
4641
  var TITLE19 = "Remove Repository";
4503
4642
  var HINTS_RUNNING19 = [{ key: "Esc", action: "cancel" }];
@@ -4546,7 +4685,7 @@ function ProjectRepoRemoveView() {
4546
4685
  setPhase({ kind: "done", project: updated, repoName });
4547
4686
  }
4548
4687
  });
4549
- const hints = useMemo24(() => phase.kind === "running" ? HINTS_RUNNING19 : HINTS_DONE19, [phase.kind]);
4688
+ const hints = useMemo25(() => phase.kind === "running" ? HINTS_RUNNING19 : HINTS_DONE19, [phase.kind]);
4550
4689
  useViewHints(hints);
4551
4690
  return /* @__PURE__ */ jsx44(ViewShell, { title: TITLE19, children: renderBody19(phase) });
4552
4691
  }
@@ -4585,7 +4724,7 @@ function stepLabel10(step) {
4585
4724
  }
4586
4725
 
4587
4726
  // src/integration/ui/tui/views/workflows/project-edit-view.tsx
4588
- import { useMemo as useMemo25 } from "react";
4727
+ import { useMemo as useMemo26 } from "react";
4589
4728
  import { jsx as jsx45 } from "react/jsx-runtime";
4590
4729
  var TITLE20 = "Edit Project";
4591
4730
  var HINTS_RUNNING20 = [{ key: "Esc", action: "cancel" }];
@@ -4631,7 +4770,7 @@ function ProjectEditView() {
4631
4770
  setPhase({ kind: "done", project });
4632
4771
  }
4633
4772
  });
4634
- const hints = useMemo25(() => phase.kind === "running" ? HINTS_RUNNING20 : HINTS_DONE20, [phase.kind]);
4773
+ const hints = useMemo26(() => phase.kind === "running" ? HINTS_RUNNING20 : HINTS_DONE20, [phase.kind]);
4635
4774
  useViewHints(hints);
4636
4775
  return /* @__PURE__ */ jsx45(ViewShell, { title: TITLE20, children: renderBody20(phase) });
4637
4776
  }
@@ -4666,11 +4805,11 @@ function stepLabel11(step) {
4666
4805
  }
4667
4806
 
4668
4807
  // src/integration/ui/tui/views/browse/sprint-list-view.tsx
4669
- import { useEffect as useEffect14, useMemo as useMemo27, useState as useState15 } from "react";
4808
+ import { useEffect as useEffect14, useMemo as useMemo28, useState as useState15 } from "react";
4670
4809
  import { useInput as useInput11 } from "ink";
4671
4810
 
4672
4811
  // src/integration/ui/tui/components/list-view.tsx
4673
- import React47, { useMemo as useMemo26, useState as useState14 } from "react";
4812
+ import React47, { useMemo as useMemo27, useState as useState14 } from "react";
4674
4813
  import { Box as Box25, Text as Text24, useInput as useInput10 } from "ink";
4675
4814
  import { jsx as jsx46, jsxs as jsxs24 } from "react/jsx-runtime";
4676
4815
  var DEFAULT_PAGE_SIZE = 12;
@@ -4704,7 +4843,7 @@ function ListView({
4704
4843
  disabled = false
4705
4844
  }) {
4706
4845
  const [cursor, setCursor] = useState14(() => Math.max(0, Math.min(initialCursor, rows.length - 1)));
4707
- const widths = useMemo26(() => computeWidths(columns, rows, 72), [columns, rows]);
4846
+ const widths = useMemo27(() => computeWidths(columns, rows, 72), [columns, rows]);
4708
4847
  useInput10(
4709
4848
  (_input, key) => {
4710
4849
  if (rows.length === 0) return;
@@ -4917,7 +5056,7 @@ function SprintListView() {
4917
5056
  }
4918
5057
  });
4919
5058
  const title = filter === "all" ? TITLE_BASE : `${TITLE_BASE} \xB7 filter: ${filter}`;
4920
- const filtered = useMemo27(() => {
5059
+ const filtered = useMemo28(() => {
4921
5060
  if (state.kind !== "ready") return [];
4922
5061
  if (filter === "all") return state.sprints;
4923
5062
  return state.sprints.filter((s) => s.status === filter);
@@ -4936,7 +5075,7 @@ function SprintListView() {
4936
5075
  }
4937
5076
 
4938
5077
  // src/integration/ui/tui/views/browse/sprint-show-view.tsx
4939
- import { useEffect as useEffect15, useMemo as useMemo28, useState as useState16 } from "react";
5078
+ import { useEffect as useEffect15, useMemo as useMemo29, useState as useState16 } from "react";
4940
5079
  import { Box as Box26, Text as Text26, useInput as useInput12 } from "ink";
4941
5080
  import { jsx as jsx48, jsxs as jsxs26 } from "react/jsx-runtime";
4942
5081
  var TITLE21 = "Sprint Details";
@@ -4964,11 +5103,11 @@ function SprintShowView({ sprintId }) {
4964
5103
  ctl.cancelled = true;
4965
5104
  };
4966
5105
  }, [sprintId]);
4967
- const rows = useMemo28(() => {
5106
+ const rows = useMemo29(() => {
4968
5107
  if (state.kind !== "ready") return [];
4969
5108
  return buildRows2(state.sprint, state.tasks);
4970
5109
  }, [state]);
4971
- const sections = useMemo28(() => rows.filter((r) => r.separator !== true), [rows]);
5110
+ const sections = useMemo29(() => rows.filter((r) => r.separator !== true), [rows]);
4972
5111
  const [cursor, setCursor] = useState16(0);
4973
5112
  useInput12(
4974
5113
  (_input, key) => {
@@ -5265,7 +5404,7 @@ function renderBody22(state) {
5265
5404
  }
5266
5405
 
5267
5406
  // src/integration/ui/tui/views/browse/task-list-view.tsx
5268
- import { useEffect as useEffect18, useMemo as useMemo29, useState as useState19 } from "react";
5407
+ import { useEffect as useEffect18, useMemo as useMemo30, useState as useState19 } from "react";
5269
5408
  import { useInput as useInput15 } from "ink";
5270
5409
  import { jsx as jsx51 } from "react/jsx-runtime";
5271
5410
  var FILTER_CYCLE2 = ["all", "todo", "active", "done"];
@@ -5390,7 +5529,7 @@ function TaskListView({ sprintId }) {
5390
5529
  router.push({ id: "task-remove" });
5391
5530
  }
5392
5531
  });
5393
- const filtered = useMemo29(() => {
5532
+ const filtered = useMemo30(() => {
5394
5533
  if (state.kind !== "ready") return [];
5395
5534
  if (filter === "all") return state.tasks;
5396
5535
  return state.tasks.filter((t) => matches(t, filter));
@@ -5756,7 +5895,7 @@ function renderContent(content) {
5756
5895
  }
5757
5896
 
5758
5897
  // src/integration/ui/tui/views/workflows/progress-log-view.tsx
5759
- import { useMemo as useMemo30 } from "react";
5898
+ import { useMemo as useMemo31 } from "react";
5760
5899
  import { jsx as jsx57 } from "react/jsx-runtime";
5761
5900
  var TITLE29 = "Log Progress";
5762
5901
  var HINTS_RUNNING21 = [
@@ -5786,7 +5925,7 @@ function ProgressLogView() {
5786
5925
  setPhase({ kind: "done" });
5787
5926
  }
5788
5927
  });
5789
- const hints = useMemo30(() => phase.kind === "running" ? HINTS_RUNNING21 : HINTS_DONE21, [phase.kind]);
5928
+ const hints = useMemo31(() => phase.kind === "running" ? HINTS_RUNNING21 : HINTS_DONE21, [phase.kind]);
5790
5929
  useViewHints(hints);
5791
5930
  return /* @__PURE__ */ jsx57(ViewShell, { title: TITLE29, children: renderBody25(phase) });
5792
5931
  }
@@ -5804,7 +5943,7 @@ function renderBody25(phase) {
5804
5943
  }
5805
5944
 
5806
5945
  // src/integration/ui/tui/views/workflows/ideate-view.tsx
5807
- import { useMemo as useMemo31 } from "react";
5946
+ import { useMemo as useMemo32 } from "react";
5808
5947
  import { jsx as jsx58 } from "react/jsx-runtime";
5809
5948
  var TITLE30 = "Ideate";
5810
5949
  var HINTS_RUNNING22 = [{ key: "Esc", action: "cancel" }];
@@ -5862,7 +6001,7 @@ function IdeateView() {
5862
6001
  setPhase({ kind: "done", summary });
5863
6002
  }
5864
6003
  });
5865
- const hints = useMemo31(() => phase.kind === "running" ? HINTS_RUNNING22 : HINTS_DONE22, [phase.kind]);
6004
+ const hints = useMemo32(() => phase.kind === "running" ? HINTS_RUNNING22 : HINTS_DONE22, [phase.kind]);
5866
6005
  useViewHints(hints);
5867
6006
  return /* @__PURE__ */ jsx58(ViewShell, { title: TITLE30, children: renderBody26(phase) });
5868
6007
  }
@@ -5899,7 +6038,7 @@ function stepLabel12(step) {
5899
6038
  }
5900
6039
 
5901
6040
  // src/integration/ui/tui/views/onboarding-view.tsx
5902
- import { useMemo as useMemo32 } from "react";
6041
+ import { useMemo as useMemo33 } from "react";
5903
6042
  import { jsx as jsx59 } from "react/jsx-runtime";
5904
6043
  var TITLE31 = "Welcome";
5905
6044
  var HINTS_RUNNING23 = [{ key: "Esc", action: "skip" }];
@@ -5950,7 +6089,7 @@ function OnboardingView() {
5950
6089
  }
5951
6090
  }
5952
6091
  });
5953
- const hints = useMemo32(() => phase.kind === "running" ? HINTS_RUNNING23 : HINTS_DONE23, [phase.kind]);
6092
+ const hints = useMemo33(() => phase.kind === "running" ? HINTS_RUNNING23 : HINTS_DONE23, [phase.kind]);
5954
6093
  useViewHints(hints);
5955
6094
  return /* @__PURE__ */ jsx59(ViewShell, { title: TITLE31, children: renderBody27(phase) });
5956
6095
  }
@@ -5970,7 +6109,7 @@ function renderBody27(phase) {
5970
6109
  }
5971
6110
 
5972
6111
  // src/integration/ui/tui/views/workflows/reactivate-sprint-view.tsx
5973
- import { useMemo as useMemo33 } from "react";
6112
+ import { useMemo as useMemo34 } from "react";
5974
6113
  import { jsx as jsx60 } from "react/jsx-runtime";
5975
6114
  var TITLE32 = "Reactivate Sprint";
5976
6115
  var HINTS_RUNNING24 = [{ key: "Esc", action: "cancel" }];
@@ -6008,7 +6147,7 @@ function ReactivateSprintView({ sprintId }) {
6008
6147
  }
6009
6148
  });
6010
6149
  const running = phase.kind === "running" || phase.kind === "loading";
6011
- const hints = useMemo33(() => running ? HINTS_RUNNING24 : HINTS_DONE24, [running]);
6150
+ const hints = useMemo34(() => running ? HINTS_RUNNING24 : HINTS_DONE24, [running]);
6012
6151
  useViewHints(hints);
6013
6152
  return /* @__PURE__ */ jsx60(ViewShell, { title: TITLE32, children: renderBody28(phase) });
6014
6153
  }
@@ -6522,7 +6661,7 @@ function ViewRouter({ initialStack }) {
6522
6661
  }
6523
6662
  return collapseAdjacentDuplicates(initialStack);
6524
6663
  });
6525
- const stackRef = useRef2(stack);
6664
+ const stackRef = useRef3(stack);
6526
6665
  stackRef.current = stack;
6527
6666
  const push = useCallback10((entry) => {
6528
6667
  setStack((s) => {
@@ -6546,7 +6685,7 @@ function ViewRouter({ initialStack }) {
6546
6685
  if (current === void 0) {
6547
6686
  throw new Error("ViewRouter stack is empty");
6548
6687
  }
6549
- const api = useMemo34(
6688
+ const api = useMemo35(
6550
6689
  () => ({ current, stack, push, pop, replace, reset }),
6551
6690
  [current, stack, push, pop, replace, reset]
6552
6691
  );