@tomkapa/tayto 0.8.2 → 0.9.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.
@@ -11,14 +11,14 @@ import {
11
11
  isNewerVersion,
12
12
  isTerminalStatus,
13
13
  logger
14
- } from "./chunk-TJH7MKMN.js";
14
+ } from "./chunk-7SQR55R2.js";
15
15
 
16
16
  // src/tui/index.tsx
17
17
  import { render } from "ink";
18
18
 
19
19
  // src/tui/components/App.tsx
20
20
  import { useReducer, useEffect as useEffect5, useCallback as useCallback2, useMemo as useMemo3, useState as useState8, useRef as useRef5 } from "react";
21
- import { Box as Box21, Text as Text21, useInput as useInput7, useApp, useStdout as useStdout4 } from "ink";
21
+ import { Box as Box23, Text as Text23, useInput as useInput7, useApp, useStdout as useStdout4 } from "ink";
22
22
 
23
23
  // src/tui/types.ts
24
24
  var ViewType = {
@@ -30,10 +30,12 @@ var ViewType = {
30
30
  ProjectCreate: "project-create",
31
31
  ProjectEdit: "project-edit",
32
32
  DependencyList: "dependency-list",
33
- EpicPicker: "epic-picker",
33
+ ReleasePicker: "release-picker",
34
34
  ProjectLink: "project-link",
35
- Help: "help"
35
+ Help: "help",
36
+ Settings: "settings"
36
37
  };
38
+ var TopTab = { Tasks: "tasks", Settings: "settings" };
37
39
 
38
40
  // src/tui/assets/logo-data.ts
39
41
  var PALETTE = {
@@ -139,7 +141,7 @@ var STATUS_COLOR = {
139
141
  [TaskStatus.Cancelled]: theme.status.kill
140
142
  };
141
143
  var TYPE_COLOR = {
142
- [TaskType.Epic]: theme.status.modified,
144
+ [TaskType.Release]: theme.status.modified,
143
145
  [TaskType.Story]: theme.status.highlight,
144
146
  [TaskType.TechDebt]: theme.status.pending,
145
147
  [TaskType.Bug]: theme.status.error
@@ -152,9 +154,19 @@ var DEP_TYPE_LABEL = {
152
154
  };
153
155
 
154
156
  // src/tui/state.ts
157
+ function swapAt(arr, i, j) {
158
+ const next = [...arr];
159
+ const a = next[i];
160
+ const b = next[j];
161
+ if (a === void 0 || b === void 0) return arr;
162
+ next[i] = b;
163
+ next[j] = a;
164
+ return next;
165
+ }
155
166
  var initialState = {
156
167
  activeView: ViewType.TaskList,
157
168
  breadcrumbs: [ViewType.TaskList],
169
+ activeTab: TopTab.Tasks,
158
170
  tasks: [],
159
171
  selectedIndex: 0,
160
172
  selectedTask: null,
@@ -177,13 +189,13 @@ var initialState = {
177
189
  addDepInput: "",
178
190
  focusedPanel: "list",
179
191
  detailScrollOffset: 0,
180
- epics: [],
181
- epicSelectedIndex: 0,
182
- selectedEpicIds: /* @__PURE__ */ new Set(),
192
+ releases: [],
193
+ releaseSelectedIndex: 0,
194
+ selectedReleaseIds: /* @__PURE__ */ new Set(),
183
195
  linkingProject: null,
184
196
  editingProject: null,
185
- isEpicReordering: false,
186
- epicReorderSnapshot: null,
197
+ isReleaseReordering: false,
198
+ releaseReorderSnapshot: null,
187
199
  detectedGitRemote: null,
188
200
  changelogEntries: null,
189
201
  changelogIndex: 0,
@@ -274,19 +286,9 @@ function appReducer(state, action) {
274
286
  case "REORDER_MOVE": {
275
287
  if (!state.isReordering) return state;
276
288
  const idx = state.selectedIndex;
277
- const tasks = [...state.tasks];
278
289
  const swapIdx = action.direction === "up" ? idx - 1 : idx + 1;
279
- if (swapIdx < 0 || swapIdx >= tasks.length) return state;
280
- const current = tasks[idx];
281
- const swap = tasks[swapIdx];
282
- if (!current || !swap) return state;
283
- tasks[idx] = swap;
284
- tasks[swapIdx] = current;
285
- return {
286
- ...state,
287
- tasks,
288
- selectedIndex: swapIdx
289
- };
290
+ if (swapIdx < 0 || swapIdx >= state.tasks.length) return state;
291
+ return { ...state, tasks: swapAt(state.tasks, idx, swapIdx), selectedIndex: swapIdx };
290
292
  }
291
293
  case "EXIT_REORDER": {
292
294
  if (!action.save && state.reorderSnapshot) {
@@ -332,65 +334,62 @@ function appReducer(state, action) {
332
334
  };
333
335
  case "DETAIL_RESET_SCROLL":
334
336
  return { ...state, detailScrollOffset: 0 };
335
- case "SET_EPICS":
337
+ case "SET_RELEASES":
336
338
  return {
337
339
  ...state,
338
- epics: action.epics,
339
- epicSelectedIndex: Math.min(state.epicSelectedIndex, Math.max(0, action.epics.length - 1))
340
+ releases: action.releases,
341
+ releaseSelectedIndex: Math.min(
342
+ state.releaseSelectedIndex,
343
+ Math.max(0, action.releases.length - 1)
344
+ )
340
345
  };
341
- case "EPIC_MOVE_CURSOR": {
342
- if (state.epics.length === 0) return state;
343
- const maxIdx = Math.max(0, state.epics.length - 1);
344
- const newIdx = action.direction === "up" ? Math.max(0, state.epicSelectedIndex - 1) : Math.min(maxIdx, state.epicSelectedIndex + 1);
345
- return { ...state, epicSelectedIndex: newIdx };
346
- }
347
- case "TOGGLE_EPIC": {
348
- const next = new Set(state.selectedEpicIds);
349
- if (next.has(action.epicId)) {
350
- next.delete(action.epicId);
346
+ case "RELEASE_MOVE_CURSOR": {
347
+ if (state.releases.length === 0) return state;
348
+ const maxIdx = Math.max(0, state.releases.length - 1);
349
+ const newIdx = action.direction === "up" ? Math.max(0, state.releaseSelectedIndex - 1) : Math.min(maxIdx, state.releaseSelectedIndex + 1);
350
+ return { ...state, releaseSelectedIndex: newIdx };
351
+ }
352
+ case "TOGGLE_RELEASE": {
353
+ const next = new Set(state.selectedReleaseIds);
354
+ if (next.has(action.releaseId)) {
355
+ next.delete(action.releaseId);
351
356
  } else {
352
- next.add(action.epicId);
357
+ next.add(action.releaseId);
353
358
  }
354
- return { ...state, selectedEpicIds: next, selectedIndex: 0 };
359
+ return { ...state, selectedReleaseIds: next, selectedIndex: 0 };
355
360
  }
356
- case "CLEAR_EPIC_SELECTION":
357
- return { ...state, selectedEpicIds: /* @__PURE__ */ new Set(), selectedIndex: 0 };
358
- case "ENTER_EPIC_REORDER":
361
+ case "CLEAR_RELEASE_SELECTION":
362
+ return { ...state, selectedReleaseIds: /* @__PURE__ */ new Set(), selectedIndex: 0 };
363
+ case "ENTER_RELEASE_REORDER":
359
364
  return {
360
365
  ...state,
361
- isEpicReordering: true,
362
- epicReorderSnapshot: [...state.epics]
366
+ isReleaseReordering: true,
367
+ releaseReorderSnapshot: [...state.releases]
363
368
  };
364
- case "EPIC_REORDER_MOVE": {
365
- if (!state.isEpicReordering) return state;
366
- const idx = state.epicSelectedIndex;
367
- const epics = [...state.epics];
369
+ case "RELEASE_REORDER_MOVE": {
370
+ if (!state.isReleaseReordering) return state;
371
+ const idx = state.releaseSelectedIndex;
368
372
  const swapIdx = action.direction === "up" ? idx - 1 : idx + 1;
369
- if (swapIdx < 0 || swapIdx >= epics.length) return state;
370
- const current = epics[idx];
371
- const swap = epics[swapIdx];
372
- if (!current || !swap) return state;
373
- epics[idx] = swap;
374
- epics[swapIdx] = current;
373
+ if (swapIdx < 0 || swapIdx >= state.releases.length) return state;
375
374
  return {
376
375
  ...state,
377
- epics,
378
- epicSelectedIndex: swapIdx
376
+ releases: swapAt(state.releases, idx, swapIdx),
377
+ releaseSelectedIndex: swapIdx
379
378
  };
380
379
  }
381
- case "EXIT_EPIC_REORDER": {
382
- if (!action.save && state.epicReorderSnapshot) {
380
+ case "EXIT_RELEASE_REORDER": {
381
+ if (!action.save && state.releaseReorderSnapshot) {
383
382
  return {
384
383
  ...state,
385
- isEpicReordering: false,
386
- epics: state.epicReorderSnapshot,
387
- epicReorderSnapshot: null
384
+ isReleaseReordering: false,
385
+ releases: state.releaseReorderSnapshot,
386
+ releaseReorderSnapshot: null
388
387
  };
389
388
  }
390
389
  return {
391
390
  ...state,
392
- isEpicReordering: false,
393
- epicReorderSnapshot: null
391
+ isReleaseReordering: false,
392
+ releaseReorderSnapshot: null
394
393
  };
395
394
  }
396
395
  case "SET_LINKING_PROJECT":
@@ -413,6 +412,16 @@ function appReducer(state, action) {
413
412
  return { ...state, changelogDialogOpen: true, changelogIndex: 0 };
414
413
  case "CLOSE_CHANGELOG_DIALOG":
415
414
  return { ...state, changelogDialogOpen: false };
415
+ case "SWITCH_TAB": {
416
+ const isSettings = action.tab === TopTab.Settings;
417
+ return {
418
+ ...state,
419
+ activeTab: action.tab,
420
+ activeView: isSettings ? ViewType.Settings : ViewType.TaskList,
421
+ breadcrumbs: isSettings ? [ViewType.Settings] : [ViewType.TaskList],
422
+ focusedPanel: "list"
423
+ };
424
+ }
416
425
  }
417
426
  }
418
427
 
@@ -510,7 +519,14 @@ function ChangelogTicker({ entries }) {
510
519
  // src/tui/components/Header.tsx
511
520
  import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
512
521
  function getKeyHints(state) {
513
- const { activeView, isSearchActive, isReordering, isEpicReordering, isAddingDep, focusedPanel } = state;
522
+ const {
523
+ activeView,
524
+ isSearchActive,
525
+ isReordering,
526
+ isReleaseReordering,
527
+ isAddingDep,
528
+ focusedPanel
529
+ } = state;
514
530
  if (state.changelogDialogOpen) {
515
531
  return [
516
532
  { key: "\u2191/\u2193", desc: "navigate" },
@@ -531,7 +547,7 @@ function getKeyHints(state) {
531
547
  { key: "esc/\u2190", desc: "cancel" }
532
548
  ];
533
549
  if (isReordering) return [{ key: "\u2191/\u2193", desc: "move" }, ...REORDER_SUFFIX];
534
- if (isEpicReordering) return [{ key: "\u2191/\u2193", desc: "move epic" }, ...REORDER_SUFFIX];
550
+ if (isReleaseReordering) return [{ key: "\u2191/\u2193", desc: "move release" }, ...REORDER_SUFFIX];
535
551
  if (isSearchActive) {
536
552
  return [
537
553
  { key: "type", desc: "query" },
@@ -539,7 +555,7 @@ function getKeyHints(state) {
539
555
  { key: "esc", desc: "cancel" }
540
556
  ];
541
557
  }
542
- if (activeView === ViewType.TaskList && focusedPanel === "epic") {
558
+ if (activeView === ViewType.TaskList && focusedPanel === "release") {
543
559
  return [
544
560
  { key: "j/k", desc: "nav" },
545
561
  { key: "space", desc: "toggle" },
@@ -562,6 +578,13 @@ function getKeyHints(state) {
562
578
  { key: "?", desc: "help" }
563
579
  ];
564
580
  }
581
+ if (activeView === ViewType.Settings) {
582
+ return [
583
+ { key: "1", desc: "tasks" },
584
+ { key: "?", desc: "help" },
585
+ { key: "q", desc: "quit" }
586
+ ];
587
+ }
565
588
  if (activeView === ViewType.TaskList) {
566
589
  return [
567
590
  { key: "enter", desc: "view" },
@@ -576,6 +599,7 @@ function getKeyHints(state) {
576
599
  { key: "f/t", desc: "filter" },
577
600
  { key: "PgDn/Up", desc: "page" },
578
601
  { key: "tab/S-tab", desc: "panel" },
602
+ { key: "2", desc: "settings" },
579
603
  { key: "?", desc: "help" },
580
604
  { key: "q", desc: "quit" }
581
605
  ];
@@ -620,7 +644,7 @@ function getKeyHints(state) {
620
644
  { key: "esc", desc: "back" }
621
645
  ];
622
646
  }
623
- if (activeView === ViewType.EpicPicker) {
647
+ if (activeView === ViewType.ReleasePicker) {
624
648
  return [
625
649
  { key: "j/k", desc: "nav" },
626
650
  { key: "enter", desc: "select" },
@@ -694,7 +718,8 @@ var VIEW_LABELS = {
694
718
  "task-create": "create",
695
719
  "task-edit": "edit",
696
720
  "project-selector": "projects",
697
- help: "help"
721
+ help: "help",
722
+ settings: "settings"
698
723
  };
699
724
  function Crumbs({ breadcrumbs }) {
700
725
  return /* @__PURE__ */ jsx4(Box4, { flexDirection: "row", gap: 0, width: "100%", children: breadcrumbs.map((crumb, i) => {
@@ -733,6 +758,11 @@ var COL = {
733
758
  type: 12,
734
759
  status: 14
735
760
  };
761
+ var FIXED_WIDTH = 2 + 2 + COL.rank + COL.type + COL.status;
762
+ function padTrunc(s, w) {
763
+ if (s.length >= w) return s.slice(0, w);
764
+ return s.padEnd(w);
765
+ }
736
766
  function TaskList({
737
767
  tasks,
738
768
  selectedIndex,
@@ -745,8 +775,10 @@ function TaskList({
745
775
  nonTerminalDependentIds,
746
776
  isSelectedBlocked,
747
777
  isFocused = true,
748
- epicFilterActive = false
778
+ releaseFilterActive = false,
779
+ width
749
780
  }) {
781
+ const maxNameWidth = Math.max(0, width - FIXED_WIDTH);
750
782
  const currentPage = Math.floor(selectedIndex / PAGE_SIZE);
751
783
  const viewStart = currentPage * PAGE_SIZE;
752
784
  const visibleTasks = tasks.slice(viewStart, viewStart + PAGE_SIZE);
@@ -780,7 +812,7 @@ function TaskList({
780
812
  " ",
781
813
  "REORDER"
782
814
  ] }),
783
- epicFilterActive && /* @__PURE__ */ jsx6(Text6, { color: theme.titleHighlight, children: " [epic]" }),
815
+ releaseFilterActive && /* @__PURE__ */ jsx6(Text6, { color: theme.titleHighlight, children: " [release]" }),
784
816
  filterText && /* @__PURE__ */ jsxs3(Text6, { color: theme.titleFilter, children: [
785
817
  " /",
786
818
  filterText
@@ -800,7 +832,7 @@ function TaskList({
800
832
  ] }),
801
833
  /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", flexGrow: 1, overflowY: "hidden", children: tasks.length === 0 ? /* @__PURE__ */ jsx6(Box6, { paddingX: 2, paddingY: 1, children: /* @__PURE__ */ jsx6(Text6, { color: theme.fg, children: "No tasks found. Press 'c' to create one." }) }) : visibleTasks.map((task, i) => {
802
834
  const actualIndex = viewStart + i;
803
- const isSelected = actualIndex === selectedIndex;
835
+ const isSelected = actualIndex === selectedIndex && isFocused;
804
836
  const isNonTerminalBlocker = nonTerminalBlockerIds.has(task.id);
805
837
  const isNonTerminalDependent = nonTerminalDependentIds.has(task.id);
806
838
  const rowColor = STATUS_COLOR[task.status] ?? theme.table.fg;
@@ -808,29 +840,29 @@ function TaskList({
808
840
  const depMarker = isNonTerminalBlocker ? "\u25B2 " : isNonTerminalDependent ? "\u25BC " : " ";
809
841
  if (isSelected) {
810
842
  const cursorBg = isReordering ? theme.flash.warn : isSelectedBlocked ? theme.table.blockedCursorBg : theme.table.cursorBg;
811
- return /* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsxs3(Text6, { backgroundColor: cursorBg, color: theme.table.cursorFg, bold: true, children: [
812
- isReordering ? "~ " : "> ",
813
- rowNum.padEnd(COL.rank),
814
- task.type.padEnd(COL.type),
815
- task.status.padEnd(COL.status),
816
- task.name
817
- ] }) }, task.id);
843
+ return /* @__PURE__ */ jsxs3(Box6, { children: [
844
+ /* @__PURE__ */ jsx6(Text6, { backgroundColor: cursorBg, color: theme.table.cursorFg, bold: true, children: isReordering ? "~ " : "> " }),
845
+ /* @__PURE__ */ jsx6(Text6, { backgroundColor: cursorBg, color: theme.table.cursorFg, bold: true, children: padTrunc(rowNum, COL.rank) }),
846
+ /* @__PURE__ */ jsx6(Text6, { backgroundColor: cursorBg, color: theme.table.cursorFg, bold: true, children: padTrunc(task.type, COL.type) }),
847
+ /* @__PURE__ */ jsx6(Text6, { backgroundColor: cursorBg, color: theme.table.cursorFg, bold: true, children: padTrunc(task.status, COL.status) }),
848
+ /* @__PURE__ */ jsx6(Text6, { backgroundColor: cursorBg, color: theme.table.cursorFg, bold: true, children: padTrunc(task.name, maxNameWidth) })
849
+ ] }, task.id);
818
850
  }
819
851
  if (isNonTerminalBlocker || isNonTerminalDependent) {
820
- return /* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsxs3(Text6, { backgroundColor: theme.table.depHighlightBg, color: theme.table.fg, bold: true, children: [
821
- depMarker,
822
- rowNum.padEnd(COL.rank),
823
- task.type.padEnd(COL.type),
824
- task.status.padEnd(COL.status),
825
- task.name
826
- ] }) }, task.id);
852
+ return /* @__PURE__ */ jsxs3(Box6, { children: [
853
+ /* @__PURE__ */ jsx6(Text6, { backgroundColor: theme.table.depHighlightBg, color: theme.table.fg, bold: true, children: depMarker }),
854
+ /* @__PURE__ */ jsx6(Text6, { backgroundColor: theme.table.depHighlightBg, color: theme.table.fg, bold: true, children: padTrunc(rowNum, COL.rank) }),
855
+ /* @__PURE__ */ jsx6(Text6, { backgroundColor: theme.table.depHighlightBg, color: theme.table.fg, bold: true, children: padTrunc(task.type, COL.type) }),
856
+ /* @__PURE__ */ jsx6(Text6, { backgroundColor: theme.table.depHighlightBg, color: theme.table.fg, bold: true, children: padTrunc(task.status, COL.status) }),
857
+ /* @__PURE__ */ jsx6(Text6, { backgroundColor: theme.table.depHighlightBg, color: theme.table.fg, bold: true, children: padTrunc(task.name, maxNameWidth) })
858
+ ] }, task.id);
827
859
  }
828
860
  return /* @__PURE__ */ jsxs3(Box6, { children: [
829
861
  /* @__PURE__ */ jsx6(Text6, { children: " " }),
830
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: rowNum.padEnd(COL.rank) }),
831
- /* @__PURE__ */ jsx6(Text6, { color: TYPE_COLOR[task.type] ?? rowColor, children: task.type.padEnd(COL.type) }),
832
- /* @__PURE__ */ jsx6(Text6, { color: STATUS_COLOR[task.status] ?? rowColor, children: task.status.padEnd(COL.status) }),
833
- /* @__PURE__ */ jsx6(Text6, { color: rowColor, children: task.name })
862
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: padTrunc(rowNum, COL.rank) }),
863
+ /* @__PURE__ */ jsx6(Text6, { color: TYPE_COLOR[task.type] ?? rowColor, children: padTrunc(task.type, COL.type) }),
864
+ /* @__PURE__ */ jsx6(Text6, { color: STATUS_COLOR[task.status] ?? rowColor, children: padTrunc(task.status, COL.status) }),
865
+ /* @__PURE__ */ jsx6(Text6, { color: rowColor, children: padTrunc(task.name, maxNameWidth) })
834
866
  ] }, task.id);
835
867
  }) }),
836
868
  tasks.length > PAGE_SIZE && /* @__PURE__ */ jsx6(Box6, { justifyContent: "flex-end", paddingRight: 1, children: /* @__PURE__ */ jsxs3(Text6, { dimColor: true, children: [
@@ -981,19 +1013,50 @@ function field(label, value) {
981
1013
  function sectionHeader(title) {
982
1014
  return PAD + chalk2.hex(theme.title).bold(`--- ${title} ---`);
983
1015
  }
1016
+ function wrapWords(text, maxWidth) {
1017
+ if (maxWidth <= 0 || text.length <= maxWidth) return [text];
1018
+ const words = text.split(" ");
1019
+ const out = [];
1020
+ let line = "";
1021
+ for (const word of words) {
1022
+ if (line.length === 0) {
1023
+ line = word;
1024
+ } else if (line.length + 1 + word.length <= maxWidth) {
1025
+ line += " " + word;
1026
+ } else {
1027
+ out.push(line);
1028
+ line = word;
1029
+ }
1030
+ }
1031
+ if (line.length > 0) out.push(line);
1032
+ return out.length > 0 ? out : [""];
1033
+ }
1034
+ function buildNameLines(name, panelWidth) {
1035
+ const label = "name";
1036
+ const LABEL_PREFIX_LEN = PAD.length + label.length + 2;
1037
+ const valueWidth = Math.max(10, panelWidth - 2 - LABEL_PREFIX_LEN);
1038
+ const indent = " ".repeat(LABEL_PREFIX_LEN);
1039
+ const prefix = PAD + chalk2.hex(theme.yaml.key).bold(label) + chalk2.hex(theme.yaml.colon)(": ");
1040
+ const segments = name.split("\n").flatMap((seg) => wrapWords(seg, valueWidth));
1041
+ return segments.map((seg, i) => (i === 0 ? prefix : indent) + chalk2.hex(theme.titleHighlight).bold(seg));
1042
+ }
984
1043
  function markdownLines(content) {
985
1044
  const rendered = renderMarkdown(content);
986
1045
  if (!rendered) return [];
987
1046
  return rendered.split("\n").map((line) => PAD + line);
988
1047
  }
989
- function buildContentLines(task, blockers, dependents, related, duplicates) {
1048
+ function buildContentLines(task, parentTask, panelWidth, blockers, dependents, related, duplicates) {
990
1049
  const lines = [];
991
1050
  lines.push(field("id", task.id));
1051
+ lines.push(...buildNameLines(task.name, panelWidth));
992
1052
  lines.push(field("type", task.type));
993
1053
  lines.push(field("status", task.status));
994
1054
  lines.push(field("created", new Date(task.createdAt).toLocaleString()));
995
1055
  lines.push(field("updated", new Date(task.updatedAt).toLocaleString()));
996
- if (task.parentId) lines.push(field("parent", task.parentId));
1056
+ if (task.parentId) {
1057
+ const parentLabel = parentTask ? parentTask.name : task.parentId;
1058
+ lines.push(field("release", parentLabel));
1059
+ }
997
1060
  const hasDeps = blockers.length > 0 || dependents.length > 0 || related.length > 0 || duplicates.length > 0;
998
1061
  if (hasDeps) {
999
1062
  lines.push(sectionHeader("dependencies"));
@@ -1038,6 +1101,8 @@ function buildContentLines(task, blockers, dependents, related, duplicates) {
1038
1101
  var CHROME_LINES = 8;
1039
1102
  function TaskDetail({
1040
1103
  task,
1104
+ parentTask,
1105
+ panelWidth,
1041
1106
  blockers = [],
1042
1107
  dependents = [],
1043
1108
  related = [],
@@ -1049,9 +1114,10 @@ function TaskDetail({
1049
1114
  const allText = `${task.description}
1050
1115
  ${task.technicalNotes}
1051
1116
  ${task.additionalRequirements}`;
1117
+ const resolvedWidth = panelWidth ?? (stdout.columns > 0 ? stdout.columns : 120);
1052
1118
  const contentLines = useMemo2(
1053
- () => buildContentLines(task, blockers, dependents, related, duplicates),
1054
- [task, blockers, dependents, related, duplicates]
1119
+ () => buildContentLines(task, parentTask, resolvedWidth, blockers, dependents, related, duplicates),
1120
+ [task, parentTask, resolvedWidth, blockers, dependents, related, duplicates]
1055
1121
  );
1056
1122
  const viewportHeight = Math.max(1, (stdout.rows > 0 ? stdout.rows : 24) - CHROME_LINES);
1057
1123
  const visibleLines = contentLines.slice(scrollOffset, scrollOffset + viewportHeight);
@@ -1063,22 +1129,18 @@ ${task.additionalRequirements}`;
1063
1129
  borderStyle: "bold",
1064
1130
  borderColor: isFocused ? theme.borderFocus : theme.border,
1065
1131
  children: [
1066
- /* @__PURE__ */ jsxs5(Box8, { gap: 0, children: [
1132
+ /* @__PURE__ */ jsxs5(Box8, { gap: 0, flexShrink: 0, children: [
1067
1133
  /* @__PURE__ */ jsxs5(Text8, { color: theme.title, bold: true, children: [
1068
1134
  " ",
1069
1135
  "detail"
1070
1136
  ] }),
1071
- /* @__PURE__ */ jsx8(Text8, { color: theme.fg, children: "(" }),
1072
- /* @__PURE__ */ jsx8(Text8, { color: theme.titleHighlight, bold: true, children: task.name }),
1073
- /* @__PURE__ */ jsx8(Text8, { color: theme.fg, children: ")" }),
1074
1137
  scrollOffset > 0 && /* @__PURE__ */ jsxs5(Text8, { dimColor: true, children: [
1075
1138
  " \u2191",
1076
1139
  scrollOffset
1077
1140
  ] })
1078
1141
  ] }),
1079
- /* @__PURE__ */ jsx8(Text8, { children: visibleLines.join("\n") }),
1080
- /* @__PURE__ */ jsx8(Box8, { flexGrow: 1 }),
1081
- /* @__PURE__ */ jsx8(Box8, { paddingX: 1, children: /* @__PURE__ */ jsx8(MermaidHint, { content: allText }) })
1142
+ /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflow: "hidden", children: /* @__PURE__ */ jsx8(Text8, { children: visibleLines.join("\n") }) }),
1143
+ /* @__PURE__ */ jsx8(Box8, { paddingX: 1, flexShrink: 0, children: /* @__PURE__ */ jsx8(MermaidHint, { content: allText }) })
1082
1144
  ]
1083
1145
  }
1084
1146
  );
@@ -1126,6 +1188,20 @@ function openInEditor(content, filename, options) {
1126
1188
  // src/tui/components/TaskPicker.tsx
1127
1189
  import { useState as useState2 } from "react";
1128
1190
  import { Box as Box9, Text as Text9, useInput, useStdout as useStdout2 } from "ink";
1191
+
1192
+ // src/tui/viewport.ts
1193
+ function calcViewStart(cursorIndex, maxVisible) {
1194
+ let viewStart = 0;
1195
+ if (cursorIndex >= viewStart + maxVisible) {
1196
+ viewStart = cursorIndex - maxVisible + 1;
1197
+ }
1198
+ if (cursorIndex < viewStart) {
1199
+ viewStart = cursorIndex;
1200
+ }
1201
+ return viewStart;
1202
+ }
1203
+
1204
+ // src/tui/components/TaskPicker.tsx
1129
1205
  import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1130
1206
  var DEP_TYPE_VALUES = Object.values(UIDependencyType);
1131
1207
  var DEP_TYPE_COLOR = {
@@ -1156,13 +1232,7 @@ function TaskPicker({ tasks, excludeIds, initialSelection, onConfirm, onCancel }
1156
1232
  const q = searchQuery.toLowerCase();
1157
1233
  return t.id.toLowerCase().includes(q) || t.name.toLowerCase().includes(q) || t.description.toLowerCase().includes(q);
1158
1234
  });
1159
- let viewStart = 0;
1160
- if (cursorIndex >= viewStart + maxVisible) {
1161
- viewStart = cursorIndex - maxVisible + 1;
1162
- }
1163
- if (cursorIndex < viewStart) {
1164
- viewStart = cursorIndex;
1165
- }
1235
+ const viewStart = calcViewStart(cursorIndex, maxVisible);
1166
1236
  const visible = available.slice(viewStart, viewStart + maxVisible);
1167
1237
  useInput((input, key) => {
1168
1238
  if (isSearching) {
@@ -1993,12 +2063,12 @@ var ROW1 = [
1993
2063
  ];
1994
2064
  var ROW2 = [
1995
2065
  {
1996
- title: "EPIC PANEL",
2066
+ title: "RELEASE PANEL",
1997
2067
  keys: [
1998
2068
  ["j/k", "Navigate"],
1999
2069
  ["space", "Toggle filter"],
2000
2070
  ["0", "Clear filter"],
2001
- ["\u2190", "Reorder epics"]
2071
+ ["\u2190", "Reorder releases"]
2002
2072
  ]
2003
2073
  },
2004
2074
  {
@@ -2263,21 +2333,21 @@ function DependencyList({
2263
2333
  ] });
2264
2334
  }
2265
2335
 
2266
- // src/tui/components/EpicPanel.tsx
2336
+ // src/tui/components/ReleasePanel.tsx
2267
2337
  import { Box as Box18, Text as Text18 } from "ink";
2268
2338
  import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
2269
2339
  var PAGE_SIZE2 = 20;
2270
- function EpicPanel({
2271
- epics,
2340
+ function ReleasePanel({
2341
+ releases,
2272
2342
  selectedIndex,
2273
- selectedEpicIds,
2343
+ selectedReleaseIds,
2274
2344
  isFocused,
2275
2345
  isReordering = false
2276
2346
  }) {
2277
- const filterActive = selectedEpicIds.size > 0;
2347
+ const filterActive = selectedReleaseIds.size > 0;
2278
2348
  const currentPage = Math.floor(selectedIndex / PAGE_SIZE2);
2279
2349
  const viewStart = currentPage * PAGE_SIZE2;
2280
- const visibleEpics = epics.slice(viewStart, viewStart + PAGE_SIZE2);
2350
+ const visibleReleases = releases.slice(viewStart, viewStart + PAGE_SIZE2);
2281
2351
  return /* @__PURE__ */ jsxs15(
2282
2352
  Box18,
2283
2353
  {
@@ -2289,11 +2359,11 @@ function EpicPanel({
2289
2359
  /* @__PURE__ */ jsxs15(Box18, { children: [
2290
2360
  /* @__PURE__ */ jsxs15(Text18, { color: theme.title, bold: true, children: [
2291
2361
  " ",
2292
- "epics"
2362
+ "releases"
2293
2363
  ] }),
2294
2364
  /* @__PURE__ */ jsxs15(Text18, { color: theme.titleCounter, bold: true, children: [
2295
2365
  "[",
2296
- epics.length,
2366
+ releases.length,
2297
2367
  "]"
2298
2368
  ] }),
2299
2369
  isReordering && /* @__PURE__ */ jsxs15(Text18, { color: theme.flash.warn, bold: true, children: [
@@ -2302,38 +2372,38 @@ function EpicPanel({
2302
2372
  ] }),
2303
2373
  filterActive && /* @__PURE__ */ jsxs15(Text18, { color: theme.titleFilter, children: [
2304
2374
  " *",
2305
- selectedEpicIds.size
2375
+ selectedReleaseIds.size
2306
2376
  ] })
2307
2377
  ] }),
2308
- /* @__PURE__ */ jsx18(Box18, { flexDirection: "column", flexGrow: 1, overflowY: "hidden", children: epics.length === 0 ? /* @__PURE__ */ jsx18(Box18, { paddingX: 1, children: /* @__PURE__ */ jsx18(Text18, { dimColor: true, children: "No epics" }) }) : visibleEpics.map((epic, i) => {
2378
+ /* @__PURE__ */ jsx18(Box18, { flexDirection: "column", flexGrow: 1, overflowY: "hidden", children: releases.length === 0 ? /* @__PURE__ */ jsx18(Box18, { paddingX: 1, children: /* @__PURE__ */ jsx18(Text18, { dimColor: true, children: "No releases" }) }) : visibleReleases.map((release, i) => {
2309
2379
  const actualIndex = viewStart + i;
2310
2380
  const isSelected = actualIndex === selectedIndex && isFocused;
2311
- const isChecked = selectedEpicIds.has(epic.id);
2381
+ const isChecked = selectedReleaseIds.has(release.id);
2312
2382
  const marker = isChecked ? "[x]" : "[ ]";
2313
- const statusColor = STATUS_COLOR[epic.status] ?? theme.table.fg;
2383
+ const statusColor = STATUS_COLOR[release.status] ?? theme.table.fg;
2314
2384
  if (isSelected) {
2315
2385
  const cursorBg = isReordering ? theme.flash.warn : theme.table.cursorBg;
2316
2386
  return /* @__PURE__ */ jsx18(Box18, { children: /* @__PURE__ */ jsxs15(Text18, { backgroundColor: cursorBg, color: theme.table.cursorFg, bold: true, children: [
2317
2387
  isReordering ? "~ " : " ",
2318
2388
  marker,
2319
2389
  " ",
2320
- epic.name
2321
- ] }) }, epic.id);
2390
+ release.name
2391
+ ] }) }, release.id);
2322
2392
  }
2323
2393
  return /* @__PURE__ */ jsx18(Box18, { children: /* @__PURE__ */ jsxs15(Text18, { color: isChecked ? theme.titleHighlight : statusColor, children: [
2324
2394
  " ",
2325
2395
  marker,
2326
2396
  " ",
2327
- epic.name
2328
- ] }) }, epic.id);
2397
+ release.name
2398
+ ] }) }, release.id);
2329
2399
  }) }),
2330
- epics.length > PAGE_SIZE2 && /* @__PURE__ */ jsx18(Box18, { justifyContent: "flex-end", paddingRight: 1, children: /* @__PURE__ */ jsxs15(Text18, { dimColor: true, children: [
2400
+ releases.length > PAGE_SIZE2 && /* @__PURE__ */ jsx18(Box18, { justifyContent: "flex-end", paddingRight: 1, children: /* @__PURE__ */ jsxs15(Text18, { dimColor: true, children: [
2331
2401
  "[",
2332
2402
  viewStart + 1,
2333
2403
  "-",
2334
- Math.min(viewStart + PAGE_SIZE2, epics.length),
2404
+ Math.min(viewStart + PAGE_SIZE2, releases.length),
2335
2405
  "/",
2336
- epics.length,
2406
+ releases.length,
2337
2407
  "]"
2338
2408
  ] }) })
2339
2409
  ]
@@ -2341,29 +2411,23 @@ function EpicPanel({
2341
2411
  );
2342
2412
  }
2343
2413
 
2344
- // src/tui/components/EpicPicker.tsx
2414
+ // src/tui/components/ReleasePicker.tsx
2345
2415
  import { useState as useState7 } from "react";
2346
2416
  import { Box as Box19, Text as Text19, useInput as useInput6, useStdout as useStdout3 } from "ink";
2347
2417
  import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
2348
- function EpicPicker({ epics, currentEpicId, onSelect, onCancel }) {
2418
+ function ReleasePicker({ releases, currentReleaseId, onSelect, onCancel }) {
2349
2419
  const { stdout } = useStdout3();
2350
2420
  const termHeight = stdout.rows > 0 ? stdout.rows : 24;
2351
2421
  const maxVisible = Math.max(3, termHeight - 10);
2352
2422
  const [searchQuery, setSearchQuery] = useState7("");
2353
2423
  const [isSearching, setIsSearching] = useState7(false);
2354
2424
  const [cursorIndex, setCursorIndex] = useState7(0);
2355
- const filtered = epics.filter((e) => {
2425
+ const filtered = releases.filter((e) => {
2356
2426
  if (!searchQuery.trim()) return true;
2357
2427
  const q = searchQuery.toLowerCase();
2358
2428
  return e.id.toLowerCase().includes(q) || e.name.toLowerCase().includes(q) || e.description.toLowerCase().includes(q);
2359
2429
  });
2360
- let viewStart = 0;
2361
- if (cursorIndex >= viewStart + maxVisible) {
2362
- viewStart = cursorIndex - maxVisible + 1;
2363
- }
2364
- if (cursorIndex < viewStart) {
2365
- viewStart = cursorIndex;
2366
- }
2430
+ const viewStart = calcViewStart(cursorIndex, maxVisible);
2367
2431
  const visible = filtered.slice(viewStart, viewStart + maxVisible);
2368
2432
  useInput6((input, key) => {
2369
2433
  if (isSearching) {
@@ -2397,9 +2461,9 @@ function EpicPicker({ epics, currentEpicId, onSelect, onCancel }) {
2397
2461
  return;
2398
2462
  }
2399
2463
  if (key.return) {
2400
- const epic = filtered[cursorIndex];
2401
- if (epic) {
2402
- onSelect(epic.id);
2464
+ const release = filtered[cursorIndex];
2465
+ if (release) {
2466
+ onSelect(release.id);
2403
2467
  }
2404
2468
  return;
2405
2469
  }
@@ -2421,12 +2485,12 @@ function EpicPicker({ epics, currentEpicId, onSelect, onCancel }) {
2421
2485
  /* @__PURE__ */ jsxs16(Box19, { gap: 0, children: [
2422
2486
  /* @__PURE__ */ jsxs16(Text19, { color: theme.title, bold: true, children: [
2423
2487
  " ",
2424
- "assign to epic"
2488
+ "assign to release"
2425
2489
  ] }),
2426
2490
  /* @__PURE__ */ jsxs16(Text19, { color: theme.titleCounter, bold: true, children: [
2427
2491
  " ",
2428
2492
  "[",
2429
- epics.length,
2493
+ releases.length,
2430
2494
  "]"
2431
2495
  ] })
2432
2496
  ] }),
@@ -2444,23 +2508,23 @@ function EpicPicker({ epics, currentEpicId, onSelect, onCancel }) {
2444
2508
  "STATUS".padEnd(14),
2445
2509
  "NAME"
2446
2510
  ] }) }),
2447
- filtered.length === 0 ? /* @__PURE__ */ jsx19(Box19, { paddingX: 2, paddingY: 1, children: /* @__PURE__ */ jsx19(Text19, { dimColor: true, children: "No epics match the filter" }) }) : visible.map((epic, i) => {
2511
+ filtered.length === 0 ? /* @__PURE__ */ jsx19(Box19, { paddingX: 2, paddingY: 1, children: /* @__PURE__ */ jsx19(Text19, { dimColor: true, children: "No releases match the filter" }) }) : visible.map((release, i) => {
2448
2512
  const actualIndex = viewStart + i;
2449
2513
  const isCursor = actualIndex === cursorIndex;
2450
- const isCurrent = epic.id === currentEpicId;
2514
+ const isCurrent = release.id === currentReleaseId;
2451
2515
  const marker = isCurrent ? "* " : " ";
2452
- const statusColor = STATUS_COLOR[epic.status] ?? theme.table.fg;
2516
+ const statusColor = STATUS_COLOR[release.status] ?? theme.table.fg;
2453
2517
  return /* @__PURE__ */ jsx19(Box19, { paddingX: 1, children: isCursor ? /* @__PURE__ */ jsxs16(Text19, { backgroundColor: theme.table.cursorBg, color: theme.table.cursorFg, bold: true, children: [
2454
2518
  "> ",
2455
- epic.id.padEnd(14),
2456
- epic.status.padEnd(14),
2457
- epic.name
2519
+ release.id.padEnd(14),
2520
+ release.status.padEnd(14),
2521
+ release.name
2458
2522
  ] }) : /* @__PURE__ */ jsxs16(Fragment6, { children: [
2459
2523
  /* @__PURE__ */ jsx19(Text19, { color: isCurrent ? theme.titleHighlight : theme.table.fg, children: marker }),
2460
- /* @__PURE__ */ jsx19(Text19, { color: theme.yaml.value, children: epic.id.padEnd(14) }),
2461
- /* @__PURE__ */ jsx19(Text19, { color: statusColor, children: epic.status.padEnd(14) }),
2462
- /* @__PURE__ */ jsx19(Text19, { color: isCurrent ? theme.titleHighlight : theme.table.fg, children: epic.name })
2463
- ] }) }, epic.id);
2524
+ /* @__PURE__ */ jsx19(Text19, { color: theme.yaml.value, children: release.id.padEnd(14) }),
2525
+ /* @__PURE__ */ jsx19(Text19, { color: statusColor, children: release.status.padEnd(14) }),
2526
+ /* @__PURE__ */ jsx19(Text19, { color: isCurrent ? theme.titleHighlight : theme.table.fg, children: release.name })
2527
+ ] }) }, release.id);
2464
2528
  }),
2465
2529
  /* @__PURE__ */ jsx19(Box19, { flexGrow: 1 }),
2466
2530
  filtered.length > maxVisible && /* @__PURE__ */ jsx19(Box19, { justifyContent: "flex-end", paddingRight: 1, children: /* @__PURE__ */ jsxs16(Text19, { dimColor: true, children: [
@@ -2509,6 +2573,39 @@ function ChangelogBanner({ entries, currentIndex }) {
2509
2573
  );
2510
2574
  }
2511
2575
 
2576
+ // src/tui/components/TabBar.tsx
2577
+ import { Box as Box21, Text as Text21 } from "ink";
2578
+ import { jsx as jsx21 } from "react/jsx-runtime";
2579
+ function TabBar({ activeTab }) {
2580
+ const tabs = [
2581
+ { id: "tasks", label: " Tasks " },
2582
+ { id: "settings", label: " Settings " }
2583
+ ];
2584
+ return /* @__PURE__ */ jsx21(Box21, { flexDirection: "row", gap: 0, width: "100%", children: tabs.map((tab) => {
2585
+ const isActive = tab.id === activeTab;
2586
+ return /* @__PURE__ */ jsx21(
2587
+ Text21,
2588
+ {
2589
+ color: theme.crumb.fg,
2590
+ backgroundColor: isActive ? theme.crumb.activeBg : theme.crumb.bg,
2591
+ bold: isActive,
2592
+ children: tab.label
2593
+ },
2594
+ tab.id
2595
+ );
2596
+ }) });
2597
+ }
2598
+
2599
+ // src/tui/components/SettingsView.tsx
2600
+ import { Box as Box22, Text as Text22 } from "ink";
2601
+ import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
2602
+ function SettingsView() {
2603
+ return /* @__PURE__ */ jsxs18(Box22, { flexDirection: "column", flexGrow: 1, borderStyle: "bold", borderColor: theme.border, children: [
2604
+ /* @__PURE__ */ jsx22(Box22, { children: /* @__PURE__ */ jsx22(Text22, { color: theme.title, bold: true, children: " settings" }) }),
2605
+ /* @__PURE__ */ jsx22(Box22, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsx22(Text22, { dimColor: true, children: "No settings available yet" }) })
2606
+ ] });
2607
+ }
2608
+
2512
2609
  // src/tui/useAutoRefetch.ts
2513
2610
  import { useEffect as useEffect4, useRef as useRef4 } from "react";
2514
2611
  import { watchFile, unwatchFile } from "fs";
@@ -2584,7 +2681,7 @@ function writeSeenVersion(cachePath, version) {
2584
2681
  }
2585
2682
 
2586
2683
  // src/tui/components/App.tsx
2587
- import { jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
2684
+ import { jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
2588
2685
  var STATUS_CYCLE = [
2589
2686
  TaskStatus.Backlog,
2590
2687
  TaskStatus.Todo,
@@ -2592,7 +2689,7 @@ var STATUS_CYCLE = [
2592
2689
  TaskStatus.Review,
2593
2690
  TaskStatus.Done
2594
2691
  ];
2595
- var EPIC_PANEL_WIDTH = 48;
2692
+ var RELEASE_PANEL_WIDTH = 48;
2596
2693
  function App({ container, initialProject, latestVersion }) {
2597
2694
  const { exit } = useApp();
2598
2695
  const { stdout } = useStdout4();
@@ -2608,7 +2705,7 @@ function App({ container, initialProject, latestVersion }) {
2608
2705
  };
2609
2706
  }, [stdout]);
2610
2707
  const termWidth = stdout.columns > 0 ? stdout.columns : 120;
2611
- const remaining = Math.max(0, termWidth - EPIC_PANEL_WIDTH - 2);
2708
+ const remaining = Math.max(0, termWidth - RELEASE_PANEL_WIDTH - 2);
2612
2709
  const taskListWidth = Math.floor(remaining * 0.6);
2613
2710
  const taskDetailWidth = remaining - taskListWidth;
2614
2711
  const loadProjects = useCallback2(() => {
@@ -2626,8 +2723,8 @@ function App({ container, initialProject, latestVersion }) {
2626
2723
  if (!activeProject) return;
2627
2724
  logger.startSpan("TUI.loadTasks", () => {
2628
2725
  const filter = { ...state.filter, level: TaskLevel.Work };
2629
- if (state.selectedEpicIds.size > 0) {
2630
- filter.parentIds = [...state.selectedEpicIds];
2726
+ if (state.selectedReleaseIds.size > 0) {
2727
+ filter.parentIds = [...state.selectedReleaseIds];
2631
2728
  }
2632
2729
  const result = container.taskService.listTasks(activeProject, filter);
2633
2730
  if (result.ok) {
@@ -2638,16 +2735,16 @@ function App({ container, initialProject, latestVersion }) {
2638
2735
  dispatch({ type: "FLASH", message: result.error.message, level: "error" });
2639
2736
  }
2640
2737
  });
2641
- }, [container, state.filter, state.activeProject, state.selectedEpicIds]);
2642
- const loadEpics = useCallback2(() => {
2738
+ }, [container, state.filter, state.activeProject, state.selectedReleaseIds]);
2739
+ const loadReleases = useCallback2(() => {
2643
2740
  if (!state.activeProject) return;
2644
2741
  const result = container.taskService.listTasks(state.activeProject, {
2645
- level: TaskLevel.Epic
2742
+ level: TaskLevel.Release
2646
2743
  });
2647
2744
  if (result.ok) {
2648
- dispatch({ type: "SET_EPICS", epics: result.value });
2745
+ dispatch({ type: "SET_RELEASES", releases: result.value });
2649
2746
  } else {
2650
- logger.error("TUI.loadEpics: failed", result.error);
2747
+ logger.error("TUI.loadReleases: failed", result.error);
2651
2748
  }
2652
2749
  }, [container, state.activeProject]);
2653
2750
  const loadDeps = useCallback2(
@@ -2678,55 +2775,51 @@ function App({ container, initialProject, latestVersion }) {
2678
2775
  dispatch({ type: "SELECT_TASK", task: result.value });
2679
2776
  }
2680
2777
  loadTasks();
2681
- loadEpics();
2778
+ loadReleases();
2682
2779
  } else {
2683
2780
  dispatch({ type: "FLASH", message: result.error.message, level: "error" });
2684
2781
  }
2685
2782
  },
2686
- [container, state.selectedTask, loadTasks, loadEpics]
2783
+ [container, state.selectedTask, loadTasks, loadReleases]
2784
+ );
2785
+ const saveCurrentReorder = useCallback2(
2786
+ (kind) => {
2787
+ const activeProject = state.activeProject;
2788
+ if (!activeProject) return;
2789
+ const isRelease = kind === "release";
2790
+ const items = isRelease ? state.releases : state.tasks;
2791
+ const idx = isRelease ? state.releaseSelectedIndex : state.selectedIndex;
2792
+ const item = items[idx];
2793
+ if (!item) return;
2794
+ const prev = items[idx - 1];
2795
+ const next = items[idx + 1];
2796
+ const result = prev ? container.taskService.rerankTask({ taskId: item.id, afterId: prev.id }, activeProject) : next ? container.taskService.rerankTask({ taskId: item.id, beforeId: next.id }, activeProject) : container.taskService.rerankTask({ taskId: item.id, position: 1 }, activeProject);
2797
+ dispatch({ type: isRelease ? "EXIT_RELEASE_REORDER" : "EXIT_REORDER", save: result.ok });
2798
+ dispatch({
2799
+ type: "FLASH",
2800
+ message: result.ok ? isRelease ? "Release rank saved" : "Rank saved" : result.error.message,
2801
+ level: result.ok ? "info" : "error"
2802
+ });
2803
+ if (isRelease) loadReleases();
2804
+ else loadTasks();
2805
+ },
2806
+ [
2807
+ container,
2808
+ state.tasks,
2809
+ state.selectedIndex,
2810
+ state.releases,
2811
+ state.releaseSelectedIndex,
2812
+ state.activeProject,
2813
+ loadTasks,
2814
+ loadReleases
2815
+ ]
2687
2816
  );
2688
- const saveReorder = useCallback2(() => {
2689
- const activeProject = state.activeProject;
2690
- if (!activeProject) return;
2691
- const tasks = state.tasks;
2692
- const idx = state.selectedIndex;
2693
- const task = tasks[idx];
2694
- if (!task) return;
2695
- const prev = tasks[idx - 1];
2696
- const next = tasks[idx + 1];
2697
- const result = prev ? container.taskService.rerankTask({ taskId: task.id, afterId: prev.id }, activeProject) : next ? container.taskService.rerankTask({ taskId: task.id, beforeId: next.id }, activeProject) : container.taskService.rerankTask({ taskId: task.id, position: 1 }, activeProject);
2698
- dispatch({ type: "EXIT_REORDER", save: result.ok });
2699
- dispatch({
2700
- type: "FLASH",
2701
- message: result.ok ? "Rank saved" : result.error.message,
2702
- level: result.ok ? "info" : "error"
2703
- });
2704
- loadTasks();
2705
- }, [container, state.tasks, state.selectedIndex, loadTasks]);
2706
- const saveEpicReorder = useCallback2(() => {
2707
- const activeProject = state.activeProject;
2708
- if (!activeProject) return;
2709
- const epics = state.epics;
2710
- const idx = state.epicSelectedIndex;
2711
- const epic = epics[idx];
2712
- if (!epic) return;
2713
- const prev = epics[idx - 1];
2714
- const next = epics[idx + 1];
2715
- const result = prev ? container.taskService.rerankTask({ taskId: epic.id, afterId: prev.id }, activeProject) : next ? container.taskService.rerankTask({ taskId: epic.id, beforeId: next.id }, activeProject) : container.taskService.rerankTask({ taskId: epic.id, position: 1 }, activeProject);
2716
- dispatch({ type: "EXIT_EPIC_REORDER", save: result.ok });
2717
- dispatch({
2718
- type: "FLASH",
2719
- message: result.ok ? "Epic rank saved" : result.error.message,
2720
- level: result.ok ? "info" : "error"
2721
- });
2722
- loadEpics();
2723
- }, [container, state.epics, state.epicSelectedIndex, loadEpics]);
2724
2817
  const rerankSelectedToEdge = useCallback2(
2725
2818
  (kind, edge) => {
2726
2819
  const activeProject = state.activeProject;
2727
2820
  if (!activeProject) return;
2728
- const isEpic = kind === "epic";
2729
- const item = isEpic ? state.epics[state.epicSelectedIndex] : state.tasks[state.selectedIndex];
2821
+ const isRelease = kind === "release";
2822
+ const item = isRelease ? state.releases[state.releaseSelectedIndex] : state.tasks[state.selectedIndex];
2730
2823
  if (!item) return;
2731
2824
  const result = container.taskService.rerankTask(
2732
2825
  {
@@ -2736,32 +2829,32 @@ function App({ container, initialProject, latestVersion }) {
2736
2829
  activeProject
2737
2830
  );
2738
2831
  dispatch({
2739
- type: isEpic ? "EXIT_EPIC_REORDER" : "EXIT_REORDER",
2832
+ type: isRelease ? "EXIT_RELEASE_REORDER" : "EXIT_REORDER",
2740
2833
  save: result.ok
2741
2834
  });
2742
2835
  dispatch({
2743
2836
  type: "FLASH",
2744
- message: result.ok ? `${isEpic ? "Epic moved" : "Moved"} to ${edge}` : result.error.message,
2837
+ message: result.ok ? `${isRelease ? "Release moved" : "Moved"} to ${edge}` : result.error.message,
2745
2838
  level: result.ok ? "info" : "error"
2746
2839
  });
2747
- if (isEpic) loadEpics();
2840
+ if (isRelease) loadReleases();
2748
2841
  else loadTasks();
2749
2842
  },
2750
2843
  [
2751
2844
  container,
2752
2845
  state.tasks,
2753
2846
  state.selectedIndex,
2754
- state.epics,
2755
- state.epicSelectedIndex,
2847
+ state.releases,
2848
+ state.releaseSelectedIndex,
2756
2849
  loadTasks,
2757
- loadEpics
2850
+ loadReleases
2758
2851
  ]
2759
2852
  );
2760
2853
  const refetchAll = useCallback2(() => {
2761
2854
  loadProjects();
2762
2855
  loadTasks();
2763
- loadEpics();
2764
- }, [loadProjects, loadTasks, loadEpics]);
2856
+ loadReleases();
2857
+ }, [loadProjects, loadTasks, loadReleases]);
2765
2858
  useAutoRefetch(container.dbPath, refetchAll);
2766
2859
  useEffect5(() => {
2767
2860
  loadProjects();
@@ -2820,9 +2913,9 @@ function App({ container, initialProject, latestVersion }) {
2820
2913
  useEffect5(() => {
2821
2914
  if (state.activeProject) {
2822
2915
  loadTasks();
2823
- loadEpics();
2916
+ loadReleases();
2824
2917
  }
2825
- }, [state.activeProject, state.filter, loadTasks, loadEpics]);
2918
+ }, [state.activeProject, state.filter, loadTasks, loadReleases]);
2826
2919
  useEffect5(() => {
2827
2920
  if (state.flash) {
2828
2921
  const timer = setTimeout(() => {
@@ -2860,7 +2953,7 @@ function App({ container, initialProject, latestVersion }) {
2860
2953
  dispatch({ type: "GO_BACK" });
2861
2954
  }
2862
2955
  loadTasks();
2863
- loadEpics();
2956
+ loadReleases();
2864
2957
  } else {
2865
2958
  dispatch({ type: "FLASH", message: result.error.message, level: "error" });
2866
2959
  dispatch({ type: "CANCEL_DELETE" });
@@ -2883,7 +2976,7 @@ function App({ container, initialProject, latestVersion }) {
2883
2976
  dispatch({ type: "GO_BACK" });
2884
2977
  return;
2885
2978
  }
2886
- if (state.activeView === ViewType.TaskCreate || state.activeView === ViewType.TaskEdit || state.activeView === ViewType.ProjectSelector || state.activeView === ViewType.ProjectCreate || state.activeView === ViewType.ProjectEdit || state.activeView === ViewType.ProjectLink || state.activeView === ViewType.EpicPicker) {
2979
+ if (state.activeView === ViewType.TaskCreate || state.activeView === ViewType.TaskEdit || state.activeView === ViewType.ProjectSelector || state.activeView === ViewType.ProjectCreate || state.activeView === ViewType.ProjectEdit || state.activeView === ViewType.ProjectLink || state.activeView === ViewType.ReleasePicker) {
2887
2980
  return;
2888
2981
  }
2889
2982
  if (state.activeView === ViewType.DependencyList && state.isAddingDep) {
@@ -2954,30 +3047,42 @@ function App({ container, initialProject, latestVersion }) {
2954
3047
  }
2955
3048
  return;
2956
3049
  }
2957
- if (state.isEpicReordering) {
3050
+ const isRootView = state.activeView === ViewType.TaskList || state.activeView === ViewType.Settings;
3051
+ const noModal = !state.changelogEntries && !state.detectedGitRemote && !state.isReordering && !state.isReleaseReordering && !state.isAddingDep;
3052
+ if (noModal && isRootView) {
3053
+ if (input === "1") {
3054
+ dispatch({ type: "SWITCH_TAB", tab: TopTab.Tasks });
3055
+ return;
3056
+ }
3057
+ if (input === "2") {
3058
+ dispatch({ type: "SWITCH_TAB", tab: TopTab.Settings });
3059
+ return;
3060
+ }
3061
+ }
3062
+ if (state.isReleaseReordering) {
2958
3063
  if (key.upArrow || input === "k") {
2959
- dispatch({ type: "EPIC_REORDER_MOVE", direction: "up" });
3064
+ dispatch({ type: "RELEASE_REORDER_MOVE", direction: "up" });
2960
3065
  return;
2961
3066
  }
2962
3067
  if (key.downArrow || input === "j") {
2963
- dispatch({ type: "EPIC_REORDER_MOVE", direction: "down" });
3068
+ dispatch({ type: "RELEASE_REORDER_MOVE", direction: "down" });
2964
3069
  return;
2965
3070
  }
2966
3071
  if (input === "t") {
2967
- rerankSelectedToEdge("epic", "top");
3072
+ rerankSelectedToEdge("release", "top");
2968
3073
  return;
2969
3074
  }
2970
3075
  if (input === "b") {
2971
- rerankSelectedToEdge("epic", "bottom");
3076
+ rerankSelectedToEdge("release", "bottom");
2972
3077
  return;
2973
3078
  }
2974
3079
  if (key.rightArrow) {
2975
- saveEpicReorder();
3080
+ saveCurrentReorder("release");
2976
3081
  return;
2977
3082
  }
2978
3083
  if (key.escape || key.leftArrow) {
2979
- dispatch({ type: "EXIT_EPIC_REORDER", save: false });
2980
- dispatch({ type: "FLASH", message: "Epic reorder cancelled", level: "info" });
3084
+ dispatch({ type: "EXIT_RELEASE_REORDER", save: false });
3085
+ dispatch({ type: "FLASH", message: "Release reorder cancelled", level: "info" });
2981
3086
  return;
2982
3087
  }
2983
3088
  return;
@@ -3000,7 +3105,7 @@ function App({ container, initialProject, latestVersion }) {
3000
3105
  return;
3001
3106
  }
3002
3107
  if (key.rightArrow) {
3003
- saveReorder();
3108
+ saveCurrentReorder("task");
3004
3109
  return;
3005
3110
  }
3006
3111
  if (key.escape || key.leftArrow) {
@@ -3010,7 +3115,7 @@ function App({ container, initialProject, latestVersion }) {
3010
3115
  }
3011
3116
  return;
3012
3117
  }
3013
- if (input === "q" && state.activeView === ViewType.TaskList && state.focusedPanel === "list") {
3118
+ if (input === "q" && (state.activeView === ViewType.Settings || state.activeView === ViewType.TaskList && state.focusedPanel === "list")) {
3014
3119
  exit();
3015
3120
  return;
3016
3121
  }
@@ -3027,36 +3132,45 @@ function App({ container, initialProject, latestVersion }) {
3027
3132
  return;
3028
3133
  }
3029
3134
  if (key.tab && state.activeView === ViewType.TaskList) {
3030
- const panels = previewTask ? ["epic", "list", "detail"] : ["epic", "list"];
3135
+ const panels = previewItem ? ["release", "list", "detail"] : ["release", "list"];
3031
3136
  const curIdx = panels.indexOf(state.focusedPanel);
3032
3137
  const delta = key.shift ? -1 : 1;
3033
3138
  const nextPanel = panels[(curIdx + delta + panels.length) % panels.length] ?? "list";
3034
3139
  dispatch({ type: "SET_PANEL_FOCUS", panel: nextPanel });
3035
3140
  return;
3036
3141
  }
3037
- if (state.activeView === ViewType.TaskList && state.focusedPanel === "epic") {
3142
+ if (state.activeView === ViewType.TaskList && state.focusedPanel === "release") {
3038
3143
  if (key.upArrow || input === "k") {
3039
- dispatch({ type: "EPIC_MOVE_CURSOR", direction: "up" });
3144
+ dispatch({ type: "RELEASE_MOVE_CURSOR", direction: "up" });
3040
3145
  return;
3041
3146
  }
3042
3147
  if (key.downArrow || input === "j") {
3043
- dispatch({ type: "EPIC_MOVE_CURSOR", direction: "down" });
3148
+ dispatch({ type: "RELEASE_MOVE_CURSOR", direction: "down" });
3149
+ return;
3150
+ }
3151
+ if (input === " ") {
3152
+ const release = state.releases[state.releaseSelectedIndex];
3153
+ if (release) {
3154
+ dispatch({ type: "TOGGLE_RELEASE", releaseId: release.id });
3155
+ }
3044
3156
  return;
3045
3157
  }
3046
- if (input === " " || key.return) {
3047
- const epic = state.epics[state.epicSelectedIndex];
3048
- if (epic) {
3049
- dispatch({ type: "TOGGLE_EPIC", epicId: epic.id });
3158
+ if (key.return) {
3159
+ const release = state.releases[state.releaseSelectedIndex];
3160
+ if (release) {
3161
+ dispatch({ type: "SELECT_TASK", task: release });
3162
+ loadDeps(release.id);
3163
+ dispatch({ type: "NAVIGATE_TO", view: ViewType.TaskDetail });
3050
3164
  }
3051
3165
  return;
3052
3166
  }
3053
3167
  if (input === "0") {
3054
- dispatch({ type: "CLEAR_EPIC_SELECTION" });
3168
+ dispatch({ type: "CLEAR_RELEASE_SELECTION" });
3055
3169
  return;
3056
3170
  }
3057
3171
  if (key.leftArrow) {
3058
- if (state.epics.length > 0) {
3059
- dispatch({ type: "ENTER_EPIC_REORDER" });
3172
+ if (state.releases.length > 0) {
3173
+ dispatch({ type: "ENTER_RELEASE_REORDER" });
3060
3174
  dispatch({
3061
3175
  type: "FLASH",
3062
3176
  message: "Reorder: \u2191\u2193 move, t top, b bottom, \u2192 save, \u2190 cancel",
@@ -3135,7 +3249,7 @@ function App({ container, initialProject, latestVersion }) {
3135
3249
  const task = state.tasks[state.selectedIndex];
3136
3250
  if (task) {
3137
3251
  dispatch({ type: "SELECT_TASK", task });
3138
- dispatch({ type: "NAVIGATE_TO", view: ViewType.EpicPicker });
3252
+ dispatch({ type: "NAVIGATE_TO", view: ViewType.ReleasePicker });
3139
3253
  }
3140
3254
  return;
3141
3255
  }
@@ -3144,14 +3258,14 @@ function App({ container, initialProject, latestVersion }) {
3144
3258
  if (task && task.parentId) {
3145
3259
  const result = container.taskService.updateTask(task.id, { parentId: null });
3146
3260
  if (result.ok) {
3147
- dispatch({ type: "FLASH", message: "Unassigned from epic", level: "info" });
3261
+ dispatch({ type: "FLASH", message: "Unassigned from release", level: "info" });
3148
3262
  loadTasks();
3149
- loadEpics();
3263
+ loadReleases();
3150
3264
  } else {
3151
3265
  dispatch({ type: "FLASH", message: result.error.message, level: "error" });
3152
3266
  }
3153
3267
  } else if (task) {
3154
- dispatch({ type: "FLASH", message: "Task has no epic", level: "warn" });
3268
+ dispatch({ type: "FLASH", message: "Task has no release", level: "warn" });
3155
3269
  }
3156
3270
  return;
3157
3271
  }
@@ -3165,10 +3279,10 @@ function App({ container, initialProject, latestVersion }) {
3165
3279
  return;
3166
3280
  }
3167
3281
  if (key.leftArrow) {
3168
- if (state.selectedEpicIds.size > 0) {
3282
+ if (state.selectedReleaseIds.size > 0) {
3169
3283
  dispatch({
3170
3284
  type: "FLASH",
3171
- message: "Clear epic filter (0) before reordering",
3285
+ message: "Clear release filter (0) before reordering",
3172
3286
  level: "warn"
3173
3287
  });
3174
3288
  return;
@@ -3216,7 +3330,7 @@ function App({ container, initialProject, latestVersion }) {
3216
3330
  return;
3217
3331
  }
3218
3332
  }
3219
- if (state.activeView === ViewType.TaskList && state.focusedPanel === "detail" && previewTask) {
3333
+ if (state.activeView === ViewType.TaskList && state.focusedPanel === "detail" && previewItem) {
3220
3334
  if (key.upArrow || input === "k") {
3221
3335
  dispatch({ type: "DETAIL_SCROLL", direction: "up" });
3222
3336
  return;
@@ -3226,22 +3340,22 @@ function App({ container, initialProject, latestVersion }) {
3226
3340
  return;
3227
3341
  }
3228
3342
  if (input === "e") {
3229
- dispatch({ type: "SELECT_TASK", task: previewTask });
3343
+ dispatch({ type: "SELECT_TASK", task: previewItem });
3230
3344
  dispatch({ type: "NAVIGATE_TO", view: ViewType.TaskEdit });
3231
3345
  return;
3232
3346
  }
3233
3347
  if (input === "d") {
3234
- dispatch({ type: "CONFIRM_DELETE", task: previewTask });
3348
+ dispatch({ type: "CONFIRM_DELETE", task: previewItem });
3235
3349
  return;
3236
3350
  }
3237
3351
  if (input === "s") {
3238
- cycleStatus(previewTask);
3352
+ cycleStatus(previewItem);
3239
3353
  return;
3240
3354
  }
3241
3355
  if (input === "m") {
3242
- const allText = `${previewTask.description}
3243
- ${previewTask.technicalNotes}
3244
- ${previewTask.additionalRequirements}`;
3356
+ const allText = `${previewItem.description}
3357
+ ${previewItem.technicalNotes}
3358
+ ${previewItem.additionalRequirements}`;
3245
3359
  const count = openAllMermaidDiagrams(allText);
3246
3360
  if (count > 0) {
3247
3361
  dispatch({
@@ -3253,8 +3367,8 @@ ${previewTask.additionalRequirements}`;
3253
3367
  return;
3254
3368
  }
3255
3369
  if (input === "D") {
3256
- dispatch({ type: "SELECT_TASK", task: previewTask });
3257
- loadDeps(previewTask.id);
3370
+ dispatch({ type: "SELECT_TASK", task: previewItem });
3371
+ loadDeps(previewItem.id);
3258
3372
  dispatch({ type: "NAVIGATE_TO", view: ViewType.DependencyList });
3259
3373
  return;
3260
3374
  }
@@ -3387,7 +3501,7 @@ ${state.selectedTask.additionalRequirements}`;
3387
3501
  loadDeps(taskId);
3388
3502
  dispatch({ type: "GO_BACK" });
3389
3503
  loadTasks();
3390
- loadEpics();
3504
+ loadReleases();
3391
3505
  } else {
3392
3506
  if (!state.activeProject) return;
3393
3507
  const result = container.taskService.createTask(data, state.activeProject);
@@ -3395,7 +3509,7 @@ ${state.selectedTask.additionalRequirements}`;
3395
3509
  dispatch({ type: "FLASH", message: "Task created", level: "info" });
3396
3510
  dispatch({ type: "GO_BACK" });
3397
3511
  loadTasks();
3398
- loadEpics();
3512
+ loadReleases();
3399
3513
  } else {
3400
3514
  dispatch({ type: "FLASH", message: result.error.message, level: "error" });
3401
3515
  }
@@ -3408,31 +3522,31 @@ ${state.selectedTask.additionalRequirements}`;
3408
3522
  state.activeProject,
3409
3523
  loadTasks,
3410
3524
  loadDeps,
3411
- loadEpics
3525
+ loadReleases
3412
3526
  ]
3413
3527
  );
3414
3528
  const handleFormCancel = useCallback2(() => {
3415
3529
  dispatch({ type: "GO_BACK" });
3416
3530
  }, []);
3417
- const handleEpicPickerSelect = useCallback2(
3418
- (epicId) => {
3531
+ const handleReleasePickerSelect = useCallback2(
3532
+ (releaseId) => {
3419
3533
  if (!state.selectedTask) return;
3420
3534
  const result = container.taskService.updateTask(state.selectedTask.id, {
3421
- parentId: epicId
3535
+ parentId: releaseId
3422
3536
  });
3423
3537
  if (result.ok) {
3424
- const msg = epicId ? `Assigned to ${epicId}` : "Unassigned from epic";
3538
+ const msg = releaseId ? `Assigned to ${releaseId}` : "Unassigned from release";
3425
3539
  dispatch({ type: "FLASH", message: msg, level: "info" });
3426
3540
  loadTasks();
3427
- loadEpics();
3541
+ loadReleases();
3428
3542
  } else {
3429
3543
  dispatch({ type: "FLASH", message: result.error.message, level: "error" });
3430
3544
  }
3431
3545
  dispatch({ type: "GO_BACK" });
3432
3546
  },
3433
- [container, state.selectedTask, loadTasks, loadEpics]
3547
+ [container, state.selectedTask, loadTasks, loadReleases]
3434
3548
  );
3435
- const handleEpicPickerCancel = useCallback2(() => {
3549
+ const handleReleasePickerCancel = useCallback2(() => {
3436
3550
  dispatch({ type: "GO_BACK" });
3437
3551
  }, []);
3438
3552
  const handleProjectSelect = useCallback2((project) => {
@@ -3566,7 +3680,7 @@ ${state.selectedTask.additionalRequirements}`;
3566
3680
  const handleProjectCancel = useCallback2(() => {
3567
3681
  dispatch({ type: "GO_BACK" });
3568
3682
  }, []);
3569
- const previewTask = state.tasks[state.selectedIndex] ?? null;
3683
+ const previewItem = state.focusedPanel === "release" ? state.releases[state.releaseSelectedIndex] ?? null : state.tasks[state.selectedIndex] ?? null;
3570
3684
  const initialDepsForEdit = useMemo3(() => {
3571
3685
  return [
3572
3686
  ...state.depBlockers.map((t) => ({ id: t.id, name: t.name, type: DependencyType.Blocks })),
@@ -3583,29 +3697,39 @@ ${state.selectedTask.additionalRequirements}`;
3583
3697
  const result = container.taskService.listTasks(state.activeProject, {});
3584
3698
  return result.ok ? result.value : [];
3585
3699
  }, [container, state.activeProject, state.tasks]);
3586
- const previewTaskId = previewTask?.id ?? null;
3700
+ const previewItemId = previewItem?.id ?? null;
3587
3701
  useEffect5(() => {
3588
- if (state.activeView === ViewType.TaskList && previewTaskId) {
3589
- loadDeps(previewTaskId);
3590
- }
3591
- }, [state.activeView, previewTaskId, loadDeps]);
3592
- return /* @__PURE__ */ jsxs18(Box21, { flexDirection: "column", height: stdout.rows, children: [
3593
- /* @__PURE__ */ jsx21(Header, { state, latestVersion }),
3594
- /* @__PURE__ */ jsxs18(Box21, { flexDirection: "column", flexGrow: 1, overflowY: "hidden", children: [
3595
- state.confirmDelete && /* @__PURE__ */ jsx21(ConfirmDialog, { task: state.confirmDelete }),
3596
- !state.confirmDelete && state.changelogEntries && state.changelogDialogOpen && state.activeView === ViewType.TaskList && /* @__PURE__ */ jsx21(ChangelogBanner, { entries: state.changelogEntries, currentIndex: state.changelogIndex }),
3597
- !state.confirmDelete && !state.changelogDialogOpen && state.activeView === ViewType.TaskList && (state.detectedGitRemote ? /* @__PURE__ */ jsx21(DetectedProjectDialog, { remote: state.detectedGitRemote }) : /* @__PURE__ */ jsxs18(Box21, { flexDirection: "row", flexGrow: 1, children: [
3598
- /* @__PURE__ */ jsx21(
3599
- EpicPanel,
3702
+ if (state.activeView === ViewType.TaskList && previewItemId) {
3703
+ loadDeps(previewItemId);
3704
+ }
3705
+ }, [state.activeView, previewItemId, loadDeps]);
3706
+ return /* @__PURE__ */ jsxs19(Box23, { flexDirection: "column", height: stdout.rows, children: [
3707
+ /* @__PURE__ */ jsxs19(Box23, { flexShrink: 0, flexDirection: "column", children: [
3708
+ /* @__PURE__ */ jsx23(Header, { state, latestVersion }),
3709
+ /* @__PURE__ */ jsx23(TabBar, { activeTab: state.activeTab })
3710
+ ] }),
3711
+ state.activeTab === TopTab.Settings && /* @__PURE__ */ jsx23(SettingsView, {}),
3712
+ state.activeTab === TopTab.Tasks && /* @__PURE__ */ jsxs19(Box23, { flexDirection: "column", flexGrow: 1, overflowY: "hidden", children: [
3713
+ state.confirmDelete && /* @__PURE__ */ jsx23(ConfirmDialog, { task: state.confirmDelete }),
3714
+ !state.confirmDelete && state.changelogEntries && state.changelogDialogOpen && state.activeView === ViewType.TaskList && /* @__PURE__ */ jsx23(
3715
+ ChangelogBanner,
3716
+ {
3717
+ entries: state.changelogEntries,
3718
+ currentIndex: state.changelogIndex
3719
+ }
3720
+ ),
3721
+ !state.confirmDelete && !state.changelogDialogOpen && state.activeView === ViewType.TaskList && (state.detectedGitRemote ? /* @__PURE__ */ jsx23(DetectedProjectDialog, { remote: state.detectedGitRemote }) : /* @__PURE__ */ jsxs19(Box23, { flexDirection: "row", flexGrow: 1, children: [
3722
+ /* @__PURE__ */ jsx23(
3723
+ ReleasePanel,
3600
3724
  {
3601
- epics: state.epics,
3602
- selectedIndex: state.epicSelectedIndex,
3603
- selectedEpicIds: state.selectedEpicIds,
3604
- isFocused: state.focusedPanel === "epic",
3605
- isReordering: state.isEpicReordering
3725
+ releases: state.releases,
3726
+ selectedIndex: state.releaseSelectedIndex,
3727
+ selectedReleaseIds: state.selectedReleaseIds,
3728
+ isFocused: state.focusedPanel === "release",
3729
+ isReordering: state.isReleaseReordering
3606
3730
  }
3607
3731
  ),
3608
- /* @__PURE__ */ jsx21(Box21, { width: taskListWidth, children: /* @__PURE__ */ jsx21(
3732
+ /* @__PURE__ */ jsx23(Box23, { width: taskListWidth, children: /* @__PURE__ */ jsx23(
3609
3733
  TaskList,
3610
3734
  {
3611
3735
  tasks: state.tasks,
@@ -3623,13 +3747,16 @@ ${state.selectedTask.additionalRequirements}`;
3623
3747
  ),
3624
3748
  isSelectedBlocked: state.depBlockers.some((t) => !isTerminalStatus(t.status)),
3625
3749
  isFocused: state.focusedPanel === "list",
3626
- epicFilterActive: state.selectedEpicIds.size > 0
3750
+ releaseFilterActive: state.selectedReleaseIds.size > 0,
3751
+ width: taskListWidth
3627
3752
  }
3628
3753
  ) }),
3629
- /* @__PURE__ */ jsx21(Box21, { width: taskDetailWidth, children: previewTask ? /* @__PURE__ */ jsx21(
3754
+ /* @__PURE__ */ jsx23(Box23, { width: taskDetailWidth, children: previewItem ? /* @__PURE__ */ jsx23(
3630
3755
  TaskDetail,
3631
3756
  {
3632
- task: previewTask,
3757
+ task: previewItem,
3758
+ parentTask: state.releases.find((r) => r.id === previewItem.parentId) ?? null,
3759
+ panelWidth: taskDetailWidth,
3633
3760
  blockers: state.depBlockers,
3634
3761
  dependents: state.depDependents,
3635
3762
  related: state.depRelated,
@@ -3637,27 +3764,29 @@ ${state.selectedTask.additionalRequirements}`;
3637
3764
  isFocused: state.focusedPanel === "detail",
3638
3765
  scrollOffset: state.detailScrollOffset
3639
3766
  }
3640
- ) : /* @__PURE__ */ jsxs18(
3641
- Box21,
3767
+ ) : /* @__PURE__ */ jsxs19(
3768
+ Box23,
3642
3769
  {
3643
3770
  flexDirection: "column",
3644
3771
  flexGrow: 1,
3645
3772
  borderStyle: "bold",
3646
3773
  borderColor: theme.border,
3647
3774
  children: [
3648
- /* @__PURE__ */ jsx21(Box21, { children: /* @__PURE__ */ jsxs18(Text21, { color: theme.title, bold: true, children: [
3775
+ /* @__PURE__ */ jsx23(Box23, { children: /* @__PURE__ */ jsxs19(Text23, { color: theme.title, bold: true, children: [
3649
3776
  " ",
3650
3777
  "detail"
3651
3778
  ] }) }),
3652
- /* @__PURE__ */ jsx21(Box21, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsx21(Text21, { dimColor: true, children: "No task selected" }) })
3779
+ /* @__PURE__ */ jsx23(Box23, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsx23(Text23, { dimColor: true, children: "No task selected" }) })
3653
3780
  ]
3654
3781
  }
3655
3782
  ) })
3656
3783
  ] })),
3657
- !state.confirmDelete && state.activeView === ViewType.TaskDetail && state.selectedTask && /* @__PURE__ */ jsx21(
3784
+ !state.confirmDelete && state.activeView === ViewType.TaskDetail && state.selectedTask && /* @__PURE__ */ jsx23(
3658
3785
  TaskDetail,
3659
3786
  {
3660
3787
  task: state.selectedTask,
3788
+ parentTask: state.releases.find((r) => r.id === state.selectedTask.parentId) ?? null,
3789
+ panelWidth: termWidth,
3661
3790
  blockers: state.depBlockers,
3662
3791
  dependents: state.depDependents,
3663
3792
  related: state.depRelated,
@@ -3665,7 +3794,7 @@ ${state.selectedTask.additionalRequirements}`;
3665
3794
  scrollOffset: state.detailScrollOffset
3666
3795
  }
3667
3796
  ),
3668
- !state.confirmDelete && state.activeView === ViewType.DependencyList && state.selectedTask && /* @__PURE__ */ jsx21(
3797
+ !state.confirmDelete && state.activeView === ViewType.DependencyList && state.selectedTask && /* @__PURE__ */ jsx23(
3669
3798
  DependencyList,
3670
3799
  {
3671
3800
  task: state.selectedTask,
@@ -3678,7 +3807,7 @@ ${state.selectedTask.additionalRequirements}`;
3678
3807
  addDepInput: state.addDepInput
3679
3808
  }
3680
3809
  ),
3681
- !state.confirmDelete && (state.activeView === ViewType.TaskCreate || state.activeView === ViewType.TaskEdit) && /* @__PURE__ */ jsx21(
3810
+ !state.confirmDelete && (state.activeView === ViewType.TaskCreate || state.activeView === ViewType.TaskEdit) && /* @__PURE__ */ jsx23(
3682
3811
  TaskForm,
3683
3812
  {
3684
3813
  editingTask: state.activeView === ViewType.TaskEdit ? state.selectedTask : null,
@@ -3688,16 +3817,16 @@ ${state.selectedTask.additionalRequirements}`;
3688
3817
  onCancel: handleFormCancel
3689
3818
  }
3690
3819
  ),
3691
- !state.confirmDelete && state.activeView === ViewType.EpicPicker && state.selectedTask && /* @__PURE__ */ jsx21(
3692
- EpicPicker,
3820
+ !state.confirmDelete && state.activeView === ViewType.ReleasePicker && state.selectedTask && /* @__PURE__ */ jsx23(
3821
+ ReleasePicker,
3693
3822
  {
3694
- epics: state.epics,
3695
- currentEpicId: state.selectedTask.parentId,
3696
- onSelect: handleEpicPickerSelect,
3697
- onCancel: handleEpicPickerCancel
3823
+ releases: state.releases,
3824
+ currentReleaseId: state.selectedTask.parentId,
3825
+ onSelect: handleReleasePickerSelect,
3826
+ onCancel: handleReleasePickerCancel
3698
3827
  }
3699
3828
  ),
3700
- !state.confirmDelete && state.activeView === ViewType.ProjectSelector && /* @__PURE__ */ jsx21(
3829
+ !state.confirmDelete && state.activeView === ViewType.ProjectSelector && /* @__PURE__ */ jsx23(
3701
3830
  ProjectSelector,
3702
3831
  {
3703
3832
  projects: state.projects,
@@ -3710,7 +3839,7 @@ ${state.selectedTask.additionalRequirements}`;
3710
3839
  onCancel: handleProjectCancel
3711
3840
  }
3712
3841
  ),
3713
- !state.confirmDelete && state.activeView === ViewType.ProjectCreate && /* @__PURE__ */ jsx21(
3842
+ !state.confirmDelete && state.activeView === ViewType.ProjectCreate && /* @__PURE__ */ jsx23(
3714
3843
  ProjectForm,
3715
3844
  {
3716
3845
  initialGitRemote: state.detectedGitRemote ?? void 0,
@@ -3718,7 +3847,7 @@ ${state.selectedTask.additionalRequirements}`;
3718
3847
  onCancel: handleProjectFormCancel
3719
3848
  }
3720
3849
  ),
3721
- !state.confirmDelete && state.activeView === ViewType.ProjectEdit && state.editingProject && /* @__PURE__ */ jsx21(
3850
+ !state.confirmDelete && state.activeView === ViewType.ProjectEdit && state.editingProject && /* @__PURE__ */ jsx23(
3722
3851
  ProjectForm,
3723
3852
  {
3724
3853
  editingProject: state.editingProject,
@@ -3726,7 +3855,7 @@ ${state.selectedTask.additionalRequirements}`;
3726
3855
  onCancel: handleProjectFormCancel
3727
3856
  }
3728
3857
  ),
3729
- !state.confirmDelete && state.activeView === ViewType.ProjectLink && state.linkingProject && /* @__PURE__ */ jsx21(
3858
+ !state.confirmDelete && state.activeView === ViewType.ProjectLink && state.linkingProject && /* @__PURE__ */ jsx23(
3730
3859
  ProjectLinkForm,
3731
3860
  {
3732
3861
  project: state.linkingProject,
@@ -3736,18 +3865,18 @@ ${state.selectedTask.additionalRequirements}`;
3736
3865
  onCancel: handleLinkCancel
3737
3866
  }
3738
3867
  ),
3739
- !state.confirmDelete && state.activeView === ViewType.Help && /* @__PURE__ */ jsx21(HelpOverlay, {})
3868
+ !state.confirmDelete && state.activeView === ViewType.Help && /* @__PURE__ */ jsx23(HelpOverlay, {})
3740
3869
  ] }),
3741
- /* @__PURE__ */ jsx21(Crumbs, { breadcrumbs: state.breadcrumbs }),
3742
- state.flash && /* @__PURE__ */ jsx21(FlashMessage, { message: state.flash.message, level: state.flash.level })
3870
+ /* @__PURE__ */ jsx23(Crumbs, { breadcrumbs: state.breadcrumbs }),
3871
+ state.flash && /* @__PURE__ */ jsx23(FlashMessage, { message: state.flash.message, level: state.flash.level })
3743
3872
  ] });
3744
3873
  }
3745
3874
 
3746
3875
  // src/tui/index.tsx
3747
- import { jsx as jsx22 } from "react/jsx-runtime";
3876
+ import { jsx as jsx24 } from "react/jsx-runtime";
3748
3877
  async function launchTUI(container, initialProject, latestVersion) {
3749
3878
  const instance = render(
3750
- /* @__PURE__ */ jsx22(App, { container, initialProject, latestVersion }),
3879
+ /* @__PURE__ */ jsx24(App, { container, initialProject, latestVersion }),
3751
3880
  {
3752
3881
  exitOnCtrlC: true
3753
3882
  }
@@ -3757,4 +3886,4 @@ async function launchTUI(container, initialProject, latestVersion) {
3757
3886
  export {
3758
3887
  launchTUI
3759
3888
  };
3760
- //# sourceMappingURL=tui-6TXG3VUA.js.map
3889
+ //# sourceMappingURL=tui-U7ATVUJU.js.map