epoch-tui 0.1.8 → 0.1.10

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.
Files changed (2) hide show
  1. package/dist/index.js +1964 -222
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { render } from "ink";
5
5
 
6
6
  // src/App.tsx
7
- import { Box as Box18, Text as Text15 } from "ink";
7
+ import { Box as Box21, Text as Text18 } from "ink";
8
8
 
9
9
  // src/contexts/ThemeContext.tsx
10
10
  import { createContext as createContext2, useContext as useContext2, useState as useState2, useEffect as useEffect2 } from "react";
@@ -1453,7 +1453,8 @@ var getDefaultSchema = () => ({
1453
1453
  theme: "dark",
1454
1454
  defaultStartTime: "now",
1455
1455
  dateFormat: "MMMM do, yyyy",
1456
- timeFormat: "12h"
1456
+ timeFormat: "12h",
1457
+ autoMoveUnfinishedTasks: true
1457
1458
  }
1458
1459
  });
1459
1460
  var StorageService = class {
@@ -1527,6 +1528,10 @@ var StorageService = class {
1527
1528
  updatedAt: new Date(task.updatedAt),
1528
1529
  startTime: task.startTime ? new Date(task.startTime) : void 0,
1529
1530
  endTime: task.endTime ? new Date(task.endTime) : void 0,
1531
+ recurrence: task.recurrence ? {
1532
+ ...task.recurrence,
1533
+ endDate: task.recurrence.endDate ? new Date(task.recurrence.endDate) : void 0
1534
+ } : void 0,
1530
1535
  children: task.children ? task.children.map((child) => this.hydrateTask(child)) : []
1531
1536
  };
1532
1537
  }
@@ -1553,6 +1558,10 @@ var StorageService = class {
1553
1558
  updatedAt: task.updatedAt.toISOString(),
1554
1559
  startTime: task.startTime ? task.startTime.toISOString() : void 0,
1555
1560
  endTime: task.endTime ? task.endTime.toISOString() : void 0,
1561
+ recurrence: task.recurrence ? {
1562
+ ...task.recurrence,
1563
+ endDate: task.recurrence.endDate ? task.recurrence.endDate.toISOString() : void 0
1564
+ } : void 0,
1556
1565
  children: task.children ? task.children.map((child) => this.serializeTask(child)) : []
1557
1566
  };
1558
1567
  }
@@ -1769,8 +1778,185 @@ var useUndo = () => {
1769
1778
  return context;
1770
1779
  };
1771
1780
 
1781
+ // src/utils/date.ts
1782
+ import { format, startOfMonth, endOfMonth, eachDayOfInterval, startOfWeek, endOfWeek } from "date-fns";
1783
+ var formatDate = (date, formatStr = "MMM d, yyyy") => {
1784
+ return format(date, formatStr);
1785
+ };
1786
+ var getDateString = (date) => {
1787
+ return format(date, "yyyy-MM-dd");
1788
+ };
1789
+ var parseDateString = (dateStr) => {
1790
+ const [year, month, day] = dateStr.split("-").map(Number);
1791
+ return new Date(year, month - 1, day);
1792
+ };
1793
+ var generateMonthCalendar = (year, month) => {
1794
+ const monthStart = startOfMonth(new Date(year, month, 1));
1795
+ const monthEnd = endOfMonth(monthStart);
1796
+ const calendarStart = startOfWeek(monthStart, { weekStartsOn: 1 });
1797
+ const calendarEnd = endOfWeek(monthEnd, { weekStartsOn: 1 });
1798
+ const days = eachDayOfInterval({ start: calendarStart, end: calendarEnd });
1799
+ const weeks = [];
1800
+ for (let i = 0; i < days.length; i += 7) {
1801
+ weeks.push(days.slice(i, i + 7));
1802
+ }
1803
+ return weeks;
1804
+ };
1805
+ var isToday = (date) => {
1806
+ const today = /* @__PURE__ */ new Date();
1807
+ return date.getDate() === today.getDate() && date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear();
1808
+ };
1809
+
1810
+ // src/utils/logger.ts
1811
+ import fs2 from "fs";
1812
+ import path from "path";
1813
+ var Logger = class {
1814
+ constructor() {
1815
+ this.stream = null;
1816
+ this.logFile = path.join(process.cwd(), "debug.log");
1817
+ this.initializeLog();
1818
+ }
1819
+ initializeLog() {
1820
+ if (fs2.existsSync(this.logFile)) {
1821
+ fs2.unlinkSync(this.logFile);
1822
+ }
1823
+ this.stream = fs2.createWriteStream(this.logFile, { flags: "a" });
1824
+ this.log("Logger initialized");
1825
+ }
1826
+ log(message, data) {
1827
+ if (!this.stream) return;
1828
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1829
+ const logMessage = data ? `[${timestamp}] ${message} ${JSON.stringify(data, null, 2)}
1830
+ ` : `[${timestamp}] ${message}
1831
+ `;
1832
+ this.stream.write(logMessage);
1833
+ }
1834
+ cleanup() {
1835
+ this.log("Cleaning up logger");
1836
+ if (this.stream) {
1837
+ this.stream.end();
1838
+ this.stream = null;
1839
+ }
1840
+ if (fs2.existsSync(this.logFile)) {
1841
+ fs2.unlinkSync(this.logFile);
1842
+ }
1843
+ }
1844
+ };
1845
+ var logger = new Logger();
1846
+ process.on("exit", () => {
1847
+ logger.cleanup();
1848
+ });
1849
+ process.on("SIGINT", () => {
1850
+ logger.cleanup();
1851
+ process.exit(0);
1852
+ });
1853
+ process.on("SIGTERM", () => {
1854
+ logger.cleanup();
1855
+ process.exit(0);
1856
+ });
1857
+
1858
+ // src/services/taskMoveService.ts
1859
+ var TaskMoveService = class {
1860
+ /**
1861
+ * Get all unfinished tasks from a specific date
1862
+ */
1863
+ getUnfinishedTasks(tasks) {
1864
+ const unfinished = [];
1865
+ const traverse = (taskList) => {
1866
+ for (const task of taskList) {
1867
+ if (task.state === "todo") {
1868
+ unfinished.push(task);
1869
+ }
1870
+ if (task.children.length > 0) {
1871
+ traverse(task.children);
1872
+ }
1873
+ }
1874
+ };
1875
+ traverse(tasks);
1876
+ return unfinished;
1877
+ }
1878
+ /**
1879
+ * Move unfinished tasks from a previous date to a new date
1880
+ */
1881
+ moveUnfinishedTasksToDate(tasks, fromDate, toDate) {
1882
+ const sourceTasks = tasks[fromDate] || [];
1883
+ const unfinishedTasks = this.getUnfinishedTasks(sourceTasks);
1884
+ if (unfinishedTasks.length === 0) {
1885
+ logger.log(`No unfinished tasks to move from ${fromDate} to ${toDate}`);
1886
+ return tasks;
1887
+ }
1888
+ logger.log(`Moving ${unfinishedTasks.length} tasks from ${fromDate} to ${toDate}`, {
1889
+ taskIds: unfinishedTasks.map((t) => t.id),
1890
+ taskTitles: unfinishedTasks.map((t) => t.title)
1891
+ });
1892
+ const movedTasks = unfinishedTasks.map((task) => ({
1893
+ ...task,
1894
+ date: toDate,
1895
+ startTime: void 0,
1896
+ endTime: void 0,
1897
+ updatedAt: /* @__PURE__ */ new Date()
1898
+ }));
1899
+ const targetTasks = tasks[toDate] || [];
1900
+ const newTargetTasks = [...targetTasks, ...movedTasks];
1901
+ const newSourceTasks = sourceTasks.filter(
1902
+ (task) => !unfinishedTasks.some((ut) => ut.id === task.id)
1903
+ );
1904
+ return {
1905
+ ...tasks,
1906
+ [fromDate]: newSourceTasks,
1907
+ [toDate]: newTargetTasks
1908
+ };
1909
+ }
1910
+ /**
1911
+ * Get all dates that have unfinished tasks before a given date
1912
+ */
1913
+ getDatesWithUnfinishedTasks(tasks, beforeDate) {
1914
+ const dates = [];
1915
+ const beforeDateStr = getDateString(beforeDate);
1916
+ for (const [dateStr, taskList] of Object.entries(tasks)) {
1917
+ if (dateStr < beforeDateStr) {
1918
+ const unfinished = this.getUnfinishedTasks(taskList);
1919
+ if (unfinished.length > 0) {
1920
+ dates.push(dateStr);
1921
+ }
1922
+ }
1923
+ }
1924
+ return dates.sort();
1925
+ }
1926
+ /**
1927
+ * Auto-move all unfinished tasks from previous days to today
1928
+ */
1929
+ /**
1930
+ * Auto-move all unfinished tasks from previous days to today
1931
+ */
1932
+ autoMoveUnfinishedTasksToToday(tasks) {
1933
+ const today = /* @__PURE__ */ new Date();
1934
+ const todayStr = getDateString(today);
1935
+ const datesWithUnfinished = this.getDatesWithUnfinishedTasks(tasks, today);
1936
+ logger.log("Starting auto-move of unfinished tasks", {
1937
+ today: todayStr,
1938
+ datesWithUnfinished
1939
+ });
1940
+ let updatedTasks = { ...tasks };
1941
+ for (const dateStr of datesWithUnfinished) {
1942
+ updatedTasks = this.moveUnfinishedTasksToDate(
1943
+ updatedTasks,
1944
+ dateStr,
1945
+ todayStr
1946
+ );
1947
+ logger.log(`Moved tasks from ${dateStr} to ${todayStr}`);
1948
+ }
1949
+ logger.log("Auto-move completed", {
1950
+ tasksMoved: datesWithUnfinished.length,
1951
+ fromDates: datesWithUnfinished
1952
+ });
1953
+ return updatedTasks;
1954
+ }
1955
+ };
1956
+ var taskMoveService = new TaskMoveService();
1957
+
1772
1958
  // src/utils/version.ts
1773
- var CURRENT_VERSION = "0.1.8";
1959
+ var CURRENT_VERSION = "0.1.9";
1774
1960
  var PACKAGE_NAME = "epoch-tui";
1775
1961
  async function checkForUpdate() {
1776
1962
  try {
@@ -1826,6 +2012,11 @@ var AppProvider = ({ children }) => {
1826
2012
  const [exitConfirmation, setExitConfirmation] = useState4(false);
1827
2013
  const [showThemeDialog, setShowThemeDialog] = useState4(false);
1828
2014
  const [showClearTimelineDialog, setShowClearTimelineDialog] = useState4(false);
2015
+ const [showSettingsDialog, setShowSettingsDialog] = useState4(false);
2016
+ const [showRecurringTaskDialog, setShowRecurringTaskDialog] = useState4(false);
2017
+ const [recurringTaskId, setRecurringTaskId] = useState4(null);
2018
+ const [showRecurringEditDialog, setShowRecurringEditDialog] = useState4(false);
2019
+ const [recurringEditConfig, setRecurringEditConfig] = useState4(null);
1829
2020
  const [showUpdateDialog, setShowUpdateDialog] = useState4(false);
1830
2021
  const [updateInfo, setUpdateInfo] = useState4(null);
1831
2022
  const clearTimelineForDate = useCallback3((dateStr) => {
@@ -1873,7 +2064,25 @@ var AppProvider = ({ children }) => {
1873
2064
  }, [data, save]);
1874
2065
  useEffect3(() => {
1875
2066
  if (data && !initialLoadDone.current) {
1876
- setTasks(data.tasks);
2067
+ logger.log("Loading data from storage", {
2068
+ settings: data.settings,
2069
+ taskDates: Object.keys(data.tasks),
2070
+ timelineDates: Object.keys(data.timeline)
2071
+ });
2072
+ let tasksToSet = data.tasks;
2073
+ if (data.settings?.autoMoveUnfinishedTasks !== false) {
2074
+ logger.log("Auto-move enabled, moving unfinished tasks");
2075
+ tasksToSet = taskMoveService.autoMoveUnfinishedTasksToToday(data.tasks);
2076
+ if (tasksToSet !== data.tasks) {
2077
+ saveRef.current({
2078
+ ...data,
2079
+ tasks: tasksToSet
2080
+ });
2081
+ }
2082
+ } else {
2083
+ logger.log("Auto-move disabled, skipping");
2084
+ }
2085
+ setTasks(tasksToSet);
1877
2086
  setTimeline(data.timeline);
1878
2087
  initialLoadDone.current = true;
1879
2088
  checkForUpdate().then((info) => {
@@ -1894,6 +2103,10 @@ var AppProvider = ({ children }) => {
1894
2103
  tasks,
1895
2104
  timeline
1896
2105
  });
2106
+ logger.log("Auto-saving data", {
2107
+ taskCount: Object.keys(tasks).reduce((acc, date) => acc + tasks[date].length, 0),
2108
+ timelineCount: Object.keys(timeline).reduce((acc, date) => acc + (timeline[date]?.length || 0), 0)
2109
+ });
1897
2110
  }
1898
2111
  }, [tasks, timeline]);
1899
2112
  return /* @__PURE__ */ jsx4(
@@ -1922,8 +2135,18 @@ var AppProvider = ({ children }) => {
1922
2135
  setShowThemeDialog,
1923
2136
  showClearTimelineDialog,
1924
2137
  setShowClearTimelineDialog,
2138
+ showSettingsDialog,
2139
+ setShowSettingsDialog,
2140
+ showRecurringTaskDialog,
2141
+ setShowRecurringTaskDialog,
2142
+ recurringTaskId,
2143
+ setRecurringTaskId,
2144
+ showRecurringEditDialog,
2145
+ setShowRecurringEditDialog,
2146
+ recurringEditConfig,
2147
+ setRecurringEditConfig,
1925
2148
  clearTimelineForDate,
1926
- isModalOpen: showHelp || showThemeDialog || showOverview || showClearTimelineDialog || showUpdateDialog,
2149
+ isModalOpen: showHelp || showThemeDialog || showOverview || showClearTimelineDialog || showUpdateDialog || showSettingsDialog || showRecurringTaskDialog || showRecurringEditDialog,
1927
2150
  saveNow,
1928
2151
  pushUndoableAction,
1929
2152
  performUndo,
@@ -1949,7 +2172,7 @@ var useApp = () => {
1949
2172
  import { useInput } from "ink";
1950
2173
  import { useEffect as useEffect4 } from "react";
1951
2174
  var useKeyboardNav = () => {
1952
- const { showHelp, setShowHelp, activePane, setActivePane, isInputMode, showOverview, setShowOverview, overviewMonth, setOverviewMonth, exitConfirmation, setExitConfirmation, showThemeDialog, setShowThemeDialog, showClearTimelineDialog, setShowClearTimelineDialog, saveNow, performUndo, canUndo } = useApp();
2175
+ const { showHelp, setShowHelp, activePane, setActivePane, isInputMode, showOverview, setShowOverview, overviewMonth, setOverviewMonth, exitConfirmation, setExitConfirmation, showThemeDialog, setShowThemeDialog, showClearTimelineDialog, setShowClearTimelineDialog, showSettingsDialog, setShowSettingsDialog, showRecurringTaskDialog, showRecurringEditDialog, showUpdateDialog, saveNow, performUndo, canUndo } = useApp();
1953
2176
  useEffect4(() => {
1954
2177
  if (exitConfirmation) {
1955
2178
  const timer = setTimeout(() => {
@@ -1958,10 +2181,11 @@ var useKeyboardNav = () => {
1958
2181
  return () => clearTimeout(timer);
1959
2182
  }
1960
2183
  }, [exitConfirmation, setExitConfirmation]);
1961
- const isActive = !isInputMode && !showThemeDialog && !showClearTimelineDialog;
2184
+ const isActive = !isInputMode && !showThemeDialog && !showClearTimelineDialog && !showSettingsDialog && !showRecurringTaskDialog && !showRecurringEditDialog && !showUpdateDialog;
1962
2185
  useInput((input, key) => {
1963
2186
  if (key.ctrl && input === "c") {
1964
2187
  if (exitConfirmation) {
2188
+ logger.log("Exiting application");
1965
2189
  saveNow().then(() => {
1966
2190
  const inkApp = global.__inkApp;
1967
2191
  if (inkApp) {
@@ -1985,39 +2209,55 @@ var useKeyboardNav = () => {
1985
2209
  });
1986
2210
  return;
1987
2211
  } else {
2212
+ logger.log("Exit confirmation requested");
1988
2213
  setExitConfirmation(true);
1989
2214
  return;
1990
2215
  }
1991
2216
  }
2217
+ });
2218
+ useInput((input, key) => {
1992
2219
  if (key.ctrl && input === "u" && canUndo) {
2220
+ logger.log("Performing undo");
1993
2221
  performUndo();
1994
2222
  return;
1995
2223
  }
1996
2224
  if (input === ":") {
2225
+ logger.log("Toggling overview", { show: !showOverview });
1997
2226
  setShowOverview(!showOverview);
1998
2227
  return;
1999
2228
  }
2000
2229
  if (input === "?") {
2230
+ logger.log("Toggling help dialog", { show: !showHelp });
2001
2231
  setShowHelp(!showHelp);
2002
2232
  return;
2003
2233
  }
2004
2234
  if (key.ctrl && input === "t") {
2235
+ logger.log("Opening theme dialog");
2005
2236
  setShowThemeDialog(true);
2006
2237
  return;
2007
2238
  }
2239
+ if (key.ctrl && input === "s") {
2240
+ logger.log("Opening settings dialog");
2241
+ setShowSettingsDialog(true);
2242
+ return;
2243
+ }
2008
2244
  if (input === "C" && activePane === "timeline") {
2245
+ logger.log("Opening clear timeline dialog");
2009
2246
  setShowClearTimelineDialog(true);
2010
2247
  return;
2011
2248
  }
2012
2249
  if (input === "1") {
2250
+ logger.log("Switching to calendar pane");
2013
2251
  setActivePane("calendar");
2014
2252
  return;
2015
2253
  }
2016
2254
  if (input === "2" || key.tab && !key.shift) {
2255
+ logger.log("Switching to tasks pane");
2017
2256
  setActivePane("tasks");
2018
2257
  return;
2019
2258
  }
2020
2259
  if (input === "3" || key.tab && key.shift) {
2260
+ logger.log("Switching to timeline pane");
2021
2261
  setActivePane("timeline");
2022
2262
  return;
2023
2263
  }
@@ -2270,31 +2510,6 @@ var KeyboardHints = ({ hints }) => {
2270
2510
  );
2271
2511
  };
2272
2512
 
2273
- // src/utils/date.ts
2274
- import { format, startOfMonth, endOfMonth, eachDayOfInterval, startOfWeek, endOfWeek } from "date-fns";
2275
- var formatDate = (date, formatStr = "MMM d, yyyy") => {
2276
- return format(date, formatStr);
2277
- };
2278
- var getDateString = (date) => {
2279
- return format(date, "yyyy-MM-dd");
2280
- };
2281
- var generateMonthCalendar = (year, month) => {
2282
- const monthStart = startOfMonth(new Date(year, month, 1));
2283
- const monthEnd = endOfMonth(monthStart);
2284
- const calendarStart = startOfWeek(monthStart);
2285
- const calendarEnd = endOfWeek(monthEnd);
2286
- const days = eachDayOfInterval({ start: calendarStart, end: calendarEnd });
2287
- const weeks = [];
2288
- for (let i = 0; i < days.length; i += 7) {
2289
- weeks.push(days.slice(i, i + 7));
2290
- }
2291
- return weeks;
2292
- };
2293
- var isToday = (date) => {
2294
- const today = /* @__PURE__ */ new Date();
2295
- return date.getDate() === today.getDate() && date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear();
2296
- };
2297
-
2298
2513
  // src/services/calendarService.ts
2299
2514
  var CalendarService = class {
2300
2515
  generateMonthView(year, month, selectedDate, tasks) {
@@ -2490,9 +2705,9 @@ import { useState as useState7, useEffect as useEffect8, useMemo } from "react";
2490
2705
  import { Box as Box8, Text as Text7, useInput as useInput4 } from "ink";
2491
2706
 
2492
2707
  // src/components/common/ControlledTextInput.tsx
2493
- import { useRef as useRef3, useEffect as useEffect7 } from "react";
2708
+ import { useRef as useRef3, useReducer, useEffect as useEffect7 } from "react";
2494
2709
  import { Text as Text5, useInput as useInput3 } from "ink";
2495
- import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
2710
+ import { Fragment, jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
2496
2711
  var ControlledTextInput = ({
2497
2712
  value,
2498
2713
  onChange,
@@ -2505,22 +2720,39 @@ var ControlledTextInput = ({
2505
2720
  color,
2506
2721
  maxLength
2507
2722
  }) => {
2508
- const pendingValueRef = useRef3(value);
2723
+ const valueRef = useRef3(value);
2724
+ const cursorRef = useRef3(value.length);
2725
+ const [, forceRender] = useReducer((x) => x + 1, 0);
2509
2726
  const sentValuesRef = useRef3(/* @__PURE__ */ new Set([value]));
2510
2727
  useEffect7(() => {
2511
- if (value === pendingValueRef.current) {
2728
+ if (value === valueRef.current) {
2512
2729
  return;
2513
2730
  }
2514
2731
  if (sentValuesRef.current.has(value)) {
2515
2732
  return;
2516
2733
  }
2517
- pendingValueRef.current = value;
2734
+ valueRef.current = value;
2735
+ cursorRef.current = value.length;
2518
2736
  sentValuesRef.current.clear();
2519
2737
  sentValuesRef.current.add(value);
2738
+ forceRender();
2520
2739
  }, [value]);
2740
+ const findWordStart = (text, pos) => {
2741
+ let i = pos - 1;
2742
+ while (i >= 0 && text[i] === " ") i--;
2743
+ while (i >= 0 && text[i] !== " ") i--;
2744
+ return i + 1;
2745
+ };
2746
+ const findWordEnd = (text, pos) => {
2747
+ let i = pos;
2748
+ while (i < text.length && text[i] === " ") i++;
2749
+ while (i < text.length && text[i] !== " ") i++;
2750
+ return i;
2751
+ };
2521
2752
  useInput3(
2522
2753
  (input, key) => {
2523
- const currentValue = pendingValueRef.current;
2754
+ const currentValue = valueRef.current;
2755
+ const cursor2 = cursorRef.current;
2524
2756
  if (key.escape) {
2525
2757
  onCancel?.();
2526
2758
  return;
@@ -2531,32 +2763,92 @@ var ControlledTextInput = ({
2531
2763
  }
2532
2764
  if (input === "\r" || input === "\n") return;
2533
2765
  let nextValue = currentValue;
2534
- if (key.backspace || key.delete) {
2535
- nextValue = currentValue.slice(0, -1);
2766
+ let nextCursor = cursor2;
2767
+ if (key.backspace) {
2768
+ if (key.ctrl) {
2769
+ const wordStart = findWordStart(currentValue, cursor2);
2770
+ nextValue = currentValue.slice(0, wordStart) + currentValue.slice(cursor2);
2771
+ nextCursor = wordStart;
2772
+ } else if (cursor2 > 0) {
2773
+ nextValue = currentValue.slice(0, cursor2 - 1) + currentValue.slice(cursor2);
2774
+ nextCursor = cursor2 - 1;
2775
+ }
2776
+ } else if (key.delete) {
2777
+ if (cursor2 >= currentValue.length && cursor2 > 0) {
2778
+ nextValue = currentValue.slice(0, cursor2 - 1) + currentValue.slice(cursor2);
2779
+ nextCursor = cursor2 - 1;
2780
+ } else if (cursor2 < currentValue.length) {
2781
+ nextValue = currentValue.slice(0, cursor2) + currentValue.slice(cursor2 + 1);
2782
+ }
2783
+ } else if (key.leftArrow && !key.ctrl) {
2784
+ nextCursor = Math.max(0, cursor2 - 1);
2785
+ } else if (key.rightArrow && !key.ctrl) {
2786
+ nextCursor = Math.min(currentValue.length, cursor2 + 1);
2787
+ } else if (key.leftArrow && key.ctrl) {
2788
+ const wordStart = findWordStart(currentValue, cursor2);
2789
+ nextCursor = wordStart;
2790
+ } else if (key.rightArrow && key.ctrl) {
2791
+ const wordEnd = findWordEnd(currentValue, cursor2);
2792
+ nextCursor = wordEnd;
2793
+ } else if (key.meta && input === "b") {
2794
+ const wordStart = findWordStart(currentValue, cursor2);
2795
+ nextCursor = wordStart;
2796
+ } else if (key.meta && input === "f") {
2797
+ const wordEnd = findWordEnd(currentValue, cursor2);
2798
+ nextCursor = wordEnd;
2799
+ } else if (key.ctrl && input === "a") {
2800
+ nextCursor = 0;
2801
+ } else if (key.ctrl && input === "e") {
2802
+ nextCursor = currentValue.length;
2803
+ } else if (key.ctrl && input === "u") {
2804
+ nextValue = currentValue.slice(cursor2);
2805
+ nextCursor = 0;
2806
+ } else if (key.ctrl && input === "k") {
2807
+ nextValue = currentValue.slice(0, cursor2);
2808
+ } else if (key.ctrl && input === "w") {
2809
+ const wordStart = findWordStart(currentValue, cursor2);
2810
+ nextValue = currentValue.slice(0, wordStart) + currentValue.slice(cursor2);
2811
+ nextCursor = wordStart;
2536
2812
  } else if (!key.ctrl && !key.meta && input.length > 0) {
2537
2813
  const filtered = input.replace(/[\x00-\x1F\x7F]/g, "");
2538
2814
  if (filtered) {
2539
- const possibleValue = currentValue + filtered;
2540
- if (maxLength && possibleValue.length > maxLength) {
2541
- return;
2815
+ const possibleValue = currentValue.slice(0, cursor2) + filtered + currentValue.slice(cursor2);
2816
+ if (!maxLength || possibleValue.length <= maxLength) {
2817
+ nextValue = possibleValue;
2818
+ nextCursor = cursor2 + filtered.length;
2542
2819
  }
2543
- nextValue = possibleValue;
2544
2820
  }
2821
+ } else {
2822
+ return;
2545
2823
  }
2546
- if (nextValue !== currentValue) {
2547
- pendingValueRef.current = nextValue;
2548
- sentValuesRef.current.add(nextValue);
2549
- onChange(nextValue);
2824
+ const valueChanged = nextValue !== currentValue;
2825
+ const cursorChanged = nextCursor !== cursor2;
2826
+ if (valueChanged || cursorChanged) {
2827
+ valueRef.current = nextValue;
2828
+ cursorRef.current = nextCursor;
2829
+ if (valueChanged) {
2830
+ sentValuesRef.current.add(nextValue);
2831
+ onChange(nextValue);
2832
+ }
2833
+ forceRender();
2550
2834
  }
2551
2835
  },
2552
2836
  { isActive: focus }
2553
2837
  );
2554
- const displayValue = pendingValueRef.current || "";
2838
+ const displayValue = valueRef.current;
2839
+ const cursor = cursorRef.current;
2555
2840
  const showPlaceholder = !displayValue && placeholder;
2556
- return /* @__PURE__ */ jsxs7(Text5, { children: [
2557
- showPlaceholder ? /* @__PURE__ */ jsx12(Text5, { dimColor: true, color: placeholderColor, children: placeholder }) : /* @__PURE__ */ jsx12(Text5, { color, children: displayValue }),
2841
+ const beforeCursor = displayValue.slice(0, cursor);
2842
+ const atCursor = displayValue[cursor] || " ";
2843
+ const afterCursor = displayValue.slice(cursor + 1);
2844
+ return /* @__PURE__ */ jsx12(Text5, { children: showPlaceholder ? /* @__PURE__ */ jsxs7(Fragment, { children: [
2845
+ /* @__PURE__ */ jsx12(Text5, { dimColor: true, color: placeholderColor, children: placeholder }),
2558
2846
  focus && /* @__PURE__ */ jsx12(Text5, { backgroundColor: cursorColor, children: " " })
2559
- ] });
2847
+ ] }) : /* @__PURE__ */ jsxs7(Fragment, { children: [
2848
+ /* @__PURE__ */ jsx12(Text5, { color, children: beforeCursor }),
2849
+ focus ? /* @__PURE__ */ jsx12(Text5, { color, backgroundColor: cursorColor, children: atCursor }) : /* @__PURE__ */ jsx12(Text5, { color, children: atCursor }),
2850
+ /* @__PURE__ */ jsx12(Text5, { color, children: afterCursor })
2851
+ ] }) });
2560
2852
  };
2561
2853
 
2562
2854
  // src/components/tasks/TaskHeader.tsx
@@ -2589,8 +2881,20 @@ var findTaskById = (tasks, id) => {
2589
2881
  return null;
2590
2882
  };
2591
2883
  var updateTaskInTree = (tasks, id, updates) => {
2592
- return tasks.map((task) => {
2884
+ logger.log("[updateTaskInTree] Called", {
2885
+ id,
2886
+ updates,
2887
+ tasksCount: tasks.length,
2888
+ tasksIds: tasks.map((t) => ({ id: t.id, title: t.title, childrenCount: t.children.length }))
2889
+ });
2890
+ const result = tasks.map((task) => {
2593
2891
  if (task.id === id) {
2892
+ logger.log("[updateTaskInTree] Found task to update", {
2893
+ id,
2894
+ taskTitle: task.title,
2895
+ currentState: task.state,
2896
+ updates
2897
+ });
2594
2898
  return { ...task, ...updates, updatedAt: /* @__PURE__ */ new Date() };
2595
2899
  }
2596
2900
  return {
@@ -2598,6 +2902,11 @@ var updateTaskInTree = (tasks, id, updates) => {
2598
2902
  children: updateTaskInTree(task.children, id, updates)
2599
2903
  };
2600
2904
  });
2905
+ logger.log("[updateTaskInTree] Completed", {
2906
+ id,
2907
+ resultCount: result.length
2908
+ });
2909
+ return result;
2601
2910
  };
2602
2911
  var deleteTaskFromTree = (tasks, id) => {
2603
2912
  return tasks.filter((task) => task.id !== id).map((task) => ({
@@ -2684,6 +2993,7 @@ var TaskService = class {
2684
2993
  if (!validation.valid) {
2685
2994
  throw new Error(validation.error);
2686
2995
  }
2996
+ logger.log("Creating new task", { title, date, state });
2687
2997
  const now = /* @__PURE__ */ new Date();
2688
2998
  return {
2689
2999
  id: uuid(),
@@ -2696,6 +3006,7 @@ var TaskService = class {
2696
3006
  };
2697
3007
  }
2698
3008
  updateTask(tasks, taskId, updates) {
3009
+ logger.log("Updating task", { taskId, updates });
2699
3010
  const dateStr = Object.keys(tasks).find(
2700
3011
  (date) => findTaskById(tasks[date], taskId)
2701
3012
  );
@@ -2727,11 +3038,13 @@ var TaskService = class {
2727
3038
  };
2728
3039
  }
2729
3040
  deleteTask(tasks, taskId) {
3041
+ logger.log("Deleting task", { taskId });
2730
3042
  const dateStr = Object.keys(tasks).find(
2731
3043
  (date) => findTaskById(tasks[date], taskId)
2732
3044
  );
2733
3045
  if (!dateStr) {
2734
- throw new Error("Task not found");
3046
+ logger.log("Task not found for deletion, it might be an ephemeral recurring instance", { taskId });
3047
+ return tasks;
2735
3048
  }
2736
3049
  return {
2737
3050
  ...tasks,
@@ -2759,6 +3072,7 @@ var TaskService = class {
2759
3072
  };
2760
3073
  }
2761
3074
  changeTaskState(tasks, taskId, newState) {
3075
+ logger.log("Changing task state", { taskId, newState });
2762
3076
  return this.updateTask(tasks, taskId, {
2763
3077
  state: newState,
2764
3078
  endTime: ["completed", "delegated", "delayed"].includes(newState) ? /* @__PURE__ */ new Date() : void 0
@@ -2781,6 +3095,36 @@ var TaskService = class {
2781
3095
  getTaskStats(tasks, date) {
2782
3096
  return getTaskStats(this.getTasksForDate(tasks, date));
2783
3097
  }
3098
+ excludeRecurringInstance(tasks, parentTaskId, dateStr) {
3099
+ logger.log("Excluding recurring instance", { parentTaskId, dateStr });
3100
+ const parentDateStr = Object.keys(tasks).find(
3101
+ (date) => tasks[date].some((t) => t.id === parentTaskId)
3102
+ );
3103
+ if (!parentDateStr) {
3104
+ throw new Error("Parent recurring task not found");
3105
+ }
3106
+ return {
3107
+ ...tasks,
3108
+ [parentDateStr]: tasks[parentDateStr].map((task) => {
3109
+ if (task.id === parentTaskId) {
3110
+ const recurrence = task.recurrence;
3111
+ if (!recurrence) return task;
3112
+ const excludedDates = recurrence.excludedDates || [];
3113
+ if (!excludedDates.includes(dateStr)) {
3114
+ return {
3115
+ ...task,
3116
+ recurrence: {
3117
+ ...recurrence,
3118
+ excludedDates: [...excludedDates, dateStr]
3119
+ },
3120
+ updatedAt: /* @__PURE__ */ new Date()
3121
+ };
3122
+ }
3123
+ }
3124
+ return task;
3125
+ })
3126
+ };
3127
+ }
2784
3128
  };
2785
3129
  var taskService = new TaskService();
2786
3130
 
@@ -2855,8 +3199,208 @@ var TimelineService = class {
2855
3199
  };
2856
3200
  var timelineService = new TimelineService();
2857
3201
 
3202
+ // src/services/recurringTaskService.ts
3203
+ import { addDays as addDays2, addWeeks as addWeeks2, addMonths, addYears, isWeekend, isBefore, isAfter, isSameDay as isSameDay2 } from "date-fns";
3204
+ import { v4 as uuid3 } from "uuid";
3205
+ var RecurringTaskService = class {
3206
+ /**
3207
+ * Check if a date matches the recurrence pattern
3208
+ */
3209
+ shouldGenerateOnDate(pattern, baseDate, targetDate) {
3210
+ const targetDateStr = getDateString(targetDate);
3211
+ if (pattern.excludedDates && pattern.excludedDates.includes(targetDateStr)) {
3212
+ return false;
3213
+ }
3214
+ if (isBefore(targetDate, baseDate)) {
3215
+ return false;
3216
+ }
3217
+ if (pattern.endDate && isAfter(targetDate, pattern.endDate)) {
3218
+ return false;
3219
+ }
3220
+ if (isSameDay2(baseDate, targetDate)) {
3221
+ return false;
3222
+ }
3223
+ switch (pattern.frequency) {
3224
+ case "daily":
3225
+ return true;
3226
+ case "weekdays":
3227
+ return !isWeekend(targetDate);
3228
+ case "weekly": {
3229
+ const daysDiff = Math.floor(
3230
+ (targetDate.getTime() - baseDate.getTime()) / (1e3 * 60 * 60 * 24)
3231
+ );
3232
+ return daysDiff % 7 === 0;
3233
+ }
3234
+ case "monthly": {
3235
+ return targetDate.getDate() === baseDate.getDate();
3236
+ }
3237
+ case "yearly": {
3238
+ return targetDate.getMonth() === baseDate.getMonth() && targetDate.getDate() === baseDate.getDate();
3239
+ }
3240
+ case "custom": {
3241
+ if (pattern.daysOfWeek && pattern.daysOfWeek.length > 0) {
3242
+ return pattern.daysOfWeek.includes(targetDate.getDay());
3243
+ }
3244
+ if (pattern.interval) {
3245
+ const daysDiff = Math.floor(
3246
+ (targetDate.getTime() - baseDate.getTime()) / (1e3 * 60 * 60 * 24)
3247
+ );
3248
+ return daysDiff % pattern.interval === 0;
3249
+ }
3250
+ return false;
3251
+ }
3252
+ default:
3253
+ return false;
3254
+ }
3255
+ }
3256
+ /**
3257
+ * Clone children tasks recursively for recurring instances
3258
+ */
3259
+ cloneChildren(children, targetDateStr) {
3260
+ return children.map((child) => ({
3261
+ ...child,
3262
+ id: uuid3(),
3263
+ date: targetDateStr,
3264
+ state: "todo",
3265
+ createdAt: /* @__PURE__ */ new Date(),
3266
+ updatedAt: /* @__PURE__ */ new Date(),
3267
+ startTime: void 0,
3268
+ endTime: void 0,
3269
+ // Recursively clone nested children
3270
+ children: this.cloneChildren(child.children, targetDateStr)
3271
+ }));
3272
+ }
3273
+ /**
3274
+ * Generate recurring task instance for a specific date
3275
+ */
3276
+ generateRecurringInstance(parentTask, targetDate) {
3277
+ const targetDateStr = getDateString(targetDate);
3278
+ const parentDateStr = parentTask.date;
3279
+ logger.log("Generating recurring instance", {
3280
+ parentTaskId: parentTask.id,
3281
+ parentTaskTitle: parentTask.title,
3282
+ parentDate: parentDateStr,
3283
+ targetDate: targetDateStr,
3284
+ recurrence: parentTask.recurrence,
3285
+ childrenCount: parentTask.children.length
3286
+ });
3287
+ return {
3288
+ ...parentTask,
3289
+ id: uuid3(),
3290
+ date: targetDateStr,
3291
+ state: "todo",
3292
+ createdAt: /* @__PURE__ */ new Date(),
3293
+ updatedAt: /* @__PURE__ */ new Date(),
3294
+ startTime: void 0,
3295
+ endTime: void 0,
3296
+ isRecurringInstance: true,
3297
+ recurringParentId: parentTask.id,
3298
+ // Clone children for recurring instances
3299
+ children: this.cloneChildren(parentTask.children, targetDateStr)
3300
+ };
3301
+ }
3302
+ /**
3303
+ * Get the next occurrence date for a recurring task
3304
+ */
3305
+ getNextOccurrence(pattern, baseDate) {
3306
+ let nextDate = new Date(baseDate);
3307
+ switch (pattern.frequency) {
3308
+ case "daily":
3309
+ nextDate = addDays2(baseDate, 1);
3310
+ break;
3311
+ case "weekdays": {
3312
+ nextDate = addDays2(baseDate, 1);
3313
+ while (isWeekend(nextDate)) {
3314
+ nextDate = addDays2(nextDate, 1);
3315
+ }
3316
+ break;
3317
+ }
3318
+ case "weekly":
3319
+ nextDate = addWeeks2(baseDate, 1);
3320
+ break;
3321
+ case "monthly":
3322
+ nextDate = addMonths(baseDate, 1);
3323
+ break;
3324
+ case "yearly":
3325
+ nextDate = addYears(baseDate, 1);
3326
+ break;
3327
+ case "custom": {
3328
+ if (pattern.daysOfWeek && pattern.daysOfWeek.length > 0) {
3329
+ nextDate = addDays2(baseDate, 1);
3330
+ while (!pattern.daysOfWeek.includes(nextDate.getDay())) {
3331
+ nextDate = addDays2(nextDate, 1);
3332
+ if (nextDate.getTime() - baseDate.getTime() > 7 * 24 * 60 * 60 * 1e3) {
3333
+ return null;
3334
+ }
3335
+ }
3336
+ } else if (pattern.interval) {
3337
+ nextDate = addDays2(baseDate, pattern.interval);
3338
+ } else {
3339
+ return null;
3340
+ }
3341
+ break;
3342
+ }
3343
+ default:
3344
+ return null;
3345
+ }
3346
+ if (pattern.endDate && isAfter(nextDate, pattern.endDate)) {
3347
+ return null;
3348
+ }
3349
+ return nextDate;
3350
+ }
3351
+ /**
3352
+ * Generate frequency description for display
3353
+ */
3354
+ getFrequencyDescription(pattern) {
3355
+ switch (pattern.frequency) {
3356
+ case "daily":
3357
+ return "Daily";
3358
+ case "weekdays":
3359
+ return "Weekdays (Mon-Fri)";
3360
+ case "weekly":
3361
+ return "Weekly";
3362
+ case "monthly":
3363
+ return "Monthly";
3364
+ case "yearly":
3365
+ return "Yearly";
3366
+ case "custom": {
3367
+ if (pattern.daysOfWeek && pattern.daysOfWeek.length > 0) {
3368
+ const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
3369
+ const days = pattern.daysOfWeek.map((d) => dayNames[d]).join(", ");
3370
+ return `Custom (${days})`;
3371
+ }
3372
+ if (pattern.interval) {
3373
+ return `Every ${pattern.interval} days`;
3374
+ }
3375
+ return "Custom";
3376
+ }
3377
+ default:
3378
+ return "Unknown";
3379
+ }
3380
+ }
3381
+ /**
3382
+ * Check if a task should generate an instance for a specific date
3383
+ */
3384
+ shouldTaskGenerateForDate(task, targetDate) {
3385
+ if (!task.recurrence) {
3386
+ return false;
3387
+ }
3388
+ const baseDate = parseDateString(task.date);
3389
+ const shouldGenerate = this.shouldGenerateOnDate(task.recurrence, baseDate, targetDate);
3390
+ logger.log("Checking if task should generate for date", {
3391
+ taskId: task.id,
3392
+ taskTitle: task.title,
3393
+ targetDate: getDateString(targetDate),
3394
+ recurrence: task.recurrence,
3395
+ shouldGenerate
3396
+ });
3397
+ return shouldGenerate;
3398
+ }
3399
+ };
3400
+ var recurringTaskService = new RecurringTaskService();
3401
+
2858
3402
  // src/components/tasks/TasksPane.tsx
2859
- import { Fragment, jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
3403
+ import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
2860
3404
  var TasksPane = () => {
2861
3405
  const {
2862
3406
  tasks,
@@ -2868,7 +3412,12 @@ var TasksPane = () => {
2868
3412
  isInputMode,
2869
3413
  setIsInputMode,
2870
3414
  isModalOpen,
2871
- pushUndoableAction
3415
+ pushUndoableAction,
3416
+ setShowRecurringTaskDialog,
3417
+ setRecurringTaskId,
3418
+ setShowRecurringEditDialog,
3419
+ setRecurringEditConfig,
3420
+ recurringEditConfig
2872
3421
  } = useApp();
2873
3422
  const { theme } = useTheme();
2874
3423
  const [expandedIds, setExpandedIds] = useState7(/* @__PURE__ */ new Set());
@@ -2877,14 +3426,72 @@ var TasksPane = () => {
2877
3426
  const [editValue, setEditValue] = useState7("");
2878
3427
  const [parentTaskId, setParentTaskId] = useState7(null);
2879
3428
  const [scrollOffset, setScrollOffset] = useState7(0);
3429
+ const [editingTaskId, setEditingTaskId] = useState7(null);
3430
+ const [pendingAction, setPendingAction] = useState7(null);
2880
3431
  const { height: terminalHeight } = useTerminalSize();
2881
3432
  const visibleRows = useMemo(() => {
2882
3433
  return Math.max(5, terminalHeight - 11);
2883
3434
  }, [terminalHeight]);
2884
- const selectedDateObj = new Date(selectedDate.year, selectedDate.month, selectedDate.day);
3435
+ const selectedDateObj = useMemo(
3436
+ () => new Date(selectedDate.year, selectedDate.month, selectedDate.day),
3437
+ [selectedDate.year, selectedDate.month, selectedDate.day]
3438
+ );
2885
3439
  const dateStr = getDateString(selectedDateObj);
2886
3440
  const isSelectedDateToday = isToday(selectedDateObj);
2887
- const dayTasks = useMemo(() => tasks[dateStr] || [], [tasks, dateStr]);
3441
+ const dayTasks = useMemo(() => {
3442
+ const existingTasks = tasks[dateStr] || [];
3443
+ const recurringInstances = [];
3444
+ const currentDateObj = new Date(selectedDate.year, selectedDate.month, selectedDate.day);
3445
+ logger.log("[dayTasks] Calculating dayTasks with recurring instances", {
3446
+ dateStr,
3447
+ existingTaskCount: existingTasks.length,
3448
+ existingTasks: existingTasks.map((t) => ({
3449
+ id: t.id,
3450
+ title: t.title,
3451
+ state: t.state,
3452
+ isRecurringInstance: t.isRecurringInstance,
3453
+ recurringParentId: t.recurringParentId,
3454
+ hasRecurrence: !!t.recurrence
3455
+ }))
3456
+ });
3457
+ for (const [taskDate, taskList] of Object.entries(tasks)) {
3458
+ for (const task of taskList) {
3459
+ if (task.recurrence && !task.isRecurringInstance) {
3460
+ if (recurringTaskService.shouldTaskGenerateForDate(task, currentDateObj)) {
3461
+ const instanceExists = existingTasks.some(
3462
+ (t) => t.recurringParentId === task.id
3463
+ );
3464
+ if (!instanceExists) {
3465
+ const instance = recurringTaskService.generateRecurringInstance(
3466
+ task,
3467
+ currentDateObj
3468
+ );
3469
+ logger.log("[dayTasks] Generated recurring instance", {
3470
+ instanceId: instance.id,
3471
+ instanceTitle: instance.title,
3472
+ parentId: task.id,
3473
+ parentDate: taskDate,
3474
+ targetDate: dateStr
3475
+ });
3476
+ recurringInstances.push(instance);
3477
+ } else {
3478
+ logger.log("[dayTasks] Instance already exists for parent", {
3479
+ parentId: task.id,
3480
+ parentTitle: task.title,
3481
+ targetDate: dateStr
3482
+ });
3483
+ }
3484
+ }
3485
+ }
3486
+ }
3487
+ }
3488
+ logger.log("[dayTasks] Recurring instances generated", {
3489
+ dateStr,
3490
+ recurringInstanceCount: recurringInstances.length,
3491
+ totalTasks: existingTasks.length + recurringInstances.length
3492
+ });
3493
+ return [...existingTasks, ...recurringInstances];
3494
+ }, [tasks, dateStr, selectedDate.year, selectedDate.month, selectedDate.day]);
2888
3495
  const stats = taskService.getTaskStats(tasks, dateStr);
2889
3496
  const isFocused = activePane === "tasks" && !isModalOpen;
2890
3497
  const flatTasks = useMemo(() => {
@@ -2921,9 +3528,20 @@ var TasksPane = () => {
2921
3528
  return prev;
2922
3529
  });
2923
3530
  }, [dayTasks]);
3531
+ useEffect8(() => {
3532
+ logger.log("[TasksPane] Edit mode or input mode changed", {
3533
+ editMode,
3534
+ isInputMode,
3535
+ parentTaskId,
3536
+ editValue,
3537
+ isModalOpen,
3538
+ isFocused
3539
+ });
3540
+ }, [editMode, isInputMode, parentTaskId, editValue, isModalOpen, isFocused]);
2924
3541
  useEffect8(() => {
2925
3542
  setSelectedIndex(0);
2926
3543
  setEditMode("none");
3544
+ setEditingTaskId(null);
2927
3545
  }, [dateStr]);
2928
3546
  useEffect8(() => {
2929
3547
  if (selectedIndex >= flatTasks.length && flatTasks.length > 0) {
@@ -2943,6 +3561,240 @@ var TasksPane = () => {
2943
3561
  useEffect8(() => {
2944
3562
  setScrollOffset(0);
2945
3563
  }, [dateStr]);
3564
+ const isRecurringTask = (task) => {
3565
+ return !!(task.recurrence || task.isRecurringInstance);
3566
+ };
3567
+ const findRootParent = (task) => {
3568
+ if (!task.parentId) {
3569
+ return task;
3570
+ }
3571
+ for (const taskList of Object.values(tasks)) {
3572
+ for (const potentialParent of taskList) {
3573
+ if (potentialParent.id === task.parentId) {
3574
+ return findRootParent(potentialParent);
3575
+ }
3576
+ const foundInChildren = findInChildren(potentialParent.children, task.parentId);
3577
+ if (foundInChildren) {
3578
+ return findRootParent(foundInChildren);
3579
+ }
3580
+ }
3581
+ }
3582
+ return null;
3583
+ };
3584
+ const findInChildren = (children, id) => {
3585
+ for (const child of children) {
3586
+ if (child.id === id) {
3587
+ return child;
3588
+ }
3589
+ const found = findInChildren(child.children, id);
3590
+ if (found) {
3591
+ return found;
3592
+ }
3593
+ }
3594
+ return null;
3595
+ };
3596
+ const hasRecurringAncestor = (task) => {
3597
+ const root = findRootParent(task);
3598
+ return root ? isRecurringTask(root) : false;
3599
+ };
3600
+ const executePendingAction = (action, thisOnly) => {
3601
+ if (!action) return;
3602
+ const task = flatTasks.find((ft) => ft.task.id === action.taskId)?.task;
3603
+ if (!task) return;
3604
+ logger.log("[TasksPane] executePendingAction called", {
3605
+ action: action.action,
3606
+ thisOnly,
3607
+ taskId: action.taskId,
3608
+ currentEditMode: editMode,
3609
+ currentIsInputMode: isInputMode
3610
+ });
3611
+ switch (action.action) {
3612
+ case "delete":
3613
+ performDelete(action.taskId, task, thisOnly);
3614
+ break;
3615
+ case "edit":
3616
+ performEdit(action.taskId, task, thisOnly);
3617
+ break;
3618
+ }
3619
+ logger.log("[TasksPane] executePendingAction completed", {
3620
+ newEditMode: editMode,
3621
+ newIsInputMode: isInputMode
3622
+ });
3623
+ };
3624
+ const performDelete = (taskId, task, thisOnly) => {
3625
+ try {
3626
+ pushUndoableAction("TASK_DELETE");
3627
+ if (thisOnly) {
3628
+ let updated = tasks;
3629
+ const isMaterialized = Object.values(tasks).some(
3630
+ (taskList) => findTaskById(taskList, taskId)
3631
+ );
3632
+ if (isMaterialized) {
3633
+ updated = taskService.deleteTask(tasks, taskId);
3634
+ }
3635
+ if (task.isRecurringInstance && task.recurringParentId) {
3636
+ updated = taskService.excludeRecurringInstance(updated, task.recurringParentId, dateStr);
3637
+ } else if (task.recurrence) {
3638
+ updated = taskService.excludeRecurringInstance(updated, taskId, dateStr);
3639
+ }
3640
+ setTasks(updated);
3641
+ const updatedTimeline = timelineService.removeEventsByTaskId(
3642
+ timeline,
3643
+ taskId
3644
+ );
3645
+ setTimeline(updatedTimeline);
3646
+ } else {
3647
+ const parentIdToDelete = task.recurringParentId || taskId;
3648
+ const updated = taskService.deleteTask(tasks, parentIdToDelete);
3649
+ setTasks(updated);
3650
+ const updatedTimeline = timelineService.removeEventsByTaskId(
3651
+ timeline,
3652
+ taskId
3653
+ );
3654
+ if (task.recurringParentId) {
3655
+ const parentTimeline = timelineService.removeEventsByTaskId(updatedTimeline, task.recurringParentId);
3656
+ setTimeline(parentTimeline);
3657
+ } else {
3658
+ setTimeline(updatedTimeline);
3659
+ }
3660
+ }
3661
+ } catch (err) {
3662
+ console.error("Error deleting task:", err);
3663
+ }
3664
+ };
3665
+ const performEdit = (taskId, task, thisOnly) => {
3666
+ if (thisOnly || task.isRecurringInstance) {
3667
+ setEditMode("edit");
3668
+ setEditValue(task.title);
3669
+ setIsInputMode(true);
3670
+ } else {
3671
+ setEditMode("edit");
3672
+ setEditValue(task.title);
3673
+ setIsInputMode(true);
3674
+ }
3675
+ };
3676
+ const performStateChange = (taskId, task, newState, thisOnly) => {
3677
+ logger.log("[performStateChange] Called", {
3678
+ taskId,
3679
+ taskTitle: task.title,
3680
+ taskDate: task.date,
3681
+ previousState: task.state,
3682
+ newState,
3683
+ thisOnly,
3684
+ isRecurringInstance: task.isRecurringInstance,
3685
+ recurringParentId: task.recurringParentId,
3686
+ hasRecurrence: !!task.recurrence,
3687
+ parentId: task.parentId
3688
+ });
3689
+ try {
3690
+ pushUndoableAction("TASK_UPDATE");
3691
+ const previousState = task.state;
3692
+ if (task.isRecurringInstance && task.recurringParentId) {
3693
+ const existingTasks = tasks[task.date] || [];
3694
+ const taskExists = existingTasks.some((t) => t.id === taskId);
3695
+ if (!taskExists) {
3696
+ logger.log("[performStateChange] Materializing recurring instance", {
3697
+ taskId,
3698
+ taskDate: task.date
3699
+ });
3700
+ const materializedTask = {
3701
+ ...task,
3702
+ state: newState,
3703
+ endTime: ["completed", "delegated", "delayed"].includes(newState) ? /* @__PURE__ */ new Date() : void 0,
3704
+ updatedAt: /* @__PURE__ */ new Date()
3705
+ };
3706
+ const updated = {
3707
+ ...tasks,
3708
+ [task.date]: [...existingTasks, materializedTask]
3709
+ };
3710
+ logger.log("[performStateChange] Materialized task added to tasks", {
3711
+ taskId,
3712
+ date: task.date,
3713
+ tasksForDate: updated[task.date].length
3714
+ });
3715
+ setTasks(updated);
3716
+ } else {
3717
+ logger.log("[performStateChange] Task already materialized, updating normally", {
3718
+ taskId,
3719
+ newState
3720
+ });
3721
+ const updated = taskService.changeTaskState(
3722
+ tasks,
3723
+ taskId,
3724
+ newState
3725
+ );
3726
+ logger.log("[performStateChange] Task state updated, setting tasks", {
3727
+ taskId,
3728
+ updatedTasksKeys: Object.keys(updated),
3729
+ tasksForDate: updated[task.date]?.length || 0
3730
+ });
3731
+ setTasks(updated);
3732
+ }
3733
+ } else {
3734
+ logger.log("[performStateChange] Calling taskService.changeTaskState", {
3735
+ taskId,
3736
+ newState
3737
+ });
3738
+ const updated = taskService.changeTaskState(
3739
+ tasks,
3740
+ taskId,
3741
+ newState
3742
+ );
3743
+ logger.log("[performStateChange] Task state updated, setting tasks", {
3744
+ taskId,
3745
+ updatedTasksKeys: Object.keys(updated),
3746
+ tasksForDate: updated[task.date]?.length || 0
3747
+ });
3748
+ setTasks(updated);
3749
+ }
3750
+ if (newState === "todo") {
3751
+ const eventTypeToRemove = {
3752
+ todo: "started" /* STARTED */,
3753
+ // shouldn't happen
3754
+ completed: "completed" /* COMPLETED */,
3755
+ delegated: "delegated" /* DELEGATED */,
3756
+ delayed: "delayed" /* DELAYED */
3757
+ };
3758
+ const updatedTimeline = timelineService.removeLastEventByType(
3759
+ timeline,
3760
+ taskId,
3761
+ eventTypeToRemove[previousState]
3762
+ );
3763
+ setTimeline(updatedTimeline);
3764
+ logger.log("[performStateChange] Toggled back to todo, removed timeline event");
3765
+ } else if (isSelectedDateToday) {
3766
+ const eventTypeMap = {
3767
+ todo: "started" /* STARTED */,
3768
+ // shouldn't happen
3769
+ completed: "completed" /* COMPLETED */,
3770
+ delegated: "delegated" /* DELEGATED */,
3771
+ delayed: "delayed" /* DELAYED */
3772
+ };
3773
+ const event = timelineService.createEvent(
3774
+ taskId,
3775
+ task.title,
3776
+ eventTypeMap[newState],
3777
+ /* @__PURE__ */ new Date(),
3778
+ previousState,
3779
+ newState
3780
+ );
3781
+ const updatedTimeline = timelineService.addEvent(timeline, event);
3782
+ setTimeline(updatedTimeline);
3783
+ logger.log("[performStateChange] Created timeline event for today", {
3784
+ eventType: eventTypeMap[newState]
3785
+ });
3786
+ } else {
3787
+ logger.log("[performStateChange] Not today, skipping timeline event", {
3788
+ isSelectedDateToday,
3789
+ selectedDate: dateStr
3790
+ });
3791
+ }
3792
+ logger.log("[performStateChange] Completed successfully");
3793
+ } catch (err) {
3794
+ logger.log("[performStateChange] Error", { error: err });
3795
+ console.error("Error changing task state:", err);
3796
+ }
3797
+ };
2946
3798
  const handleAddTask = () => {
2947
3799
  setEditMode("add");
2948
3800
  setEditValue("");
@@ -2952,6 +3804,7 @@ var TasksPane = () => {
2952
3804
  if (selectedTask) {
2953
3805
  setEditMode("edit");
2954
3806
  setEditValue(selectedTask.title);
3807
+ setEditingTaskId(selectedTask.id);
2955
3808
  setIsInputMode(true);
2956
3809
  }
2957
3810
  };
@@ -2965,73 +3818,266 @@ var TasksPane = () => {
2965
3818
  }
2966
3819
  };
2967
3820
  const handleDeleteTask = () => {
2968
- if (selectedTaskId) {
2969
- try {
2970
- pushUndoableAction("TASK_DELETE");
2971
- const updated = taskService.deleteTask(tasks, selectedTaskId);
2972
- setTasks(updated);
2973
- const updatedTimeline = timelineService.removeEventsByTaskId(
2974
- timeline,
2975
- selectedTaskId
2976
- );
2977
- setTimeline(updatedTimeline);
2978
- } catch (err) {
2979
- console.error("Error deleting task:", err);
2980
- }
2981
- }
2982
- };
2983
- const handleChangeState = (newState) => {
2984
3821
  if (selectedTaskId && selectedTask) {
2985
- try {
2986
- pushUndoableAction("TASK_UPDATE");
2987
- const previousState = selectedTask.state;
2988
- const updated = taskService.changeTaskState(
2989
- tasks,
2990
- selectedTaskId,
2991
- newState
2992
- );
2993
- setTasks(updated);
2994
- if (newState === "todo") {
2995
- const eventTypeToRemove = {
2996
- todo: "started" /* STARTED */,
2997
- // shouldn't happen
2998
- completed: "completed" /* COMPLETED */,
2999
- delegated: "delegated" /* DELEGATED */,
3000
- delayed: "delayed" /* DELAYED */
3001
- };
3002
- const updatedTimeline = timelineService.removeLastEventByType(
3822
+ logger.log("handleDeleteTask called", {
3823
+ taskId: selectedTaskId,
3824
+ isRecurring: isRecurringTask(selectedTask),
3825
+ hasRecurrence: !!selectedTask.recurrence,
3826
+ isInstance: !!selectedTask.isRecurringInstance,
3827
+ hasRecurringAncestor: hasRecurringAncestor(selectedTask)
3828
+ });
3829
+ if (hasRecurringAncestor(selectedTask)) {
3830
+ const action = { action: "delete", taskId: selectedTaskId };
3831
+ const savedIndex = selectedIndex;
3832
+ const rootParent = findRootParent(selectedTask);
3833
+ setPendingAction(action);
3834
+ setRecurringEditConfig({
3835
+ taskId: selectedTaskId,
3836
+ taskTitle: selectedTask.title,
3837
+ actionType: "delete",
3838
+ onConfirm: (choice) => {
3839
+ logger.log("Recurring delete confirmed", { choice, action });
3840
+ pushUndoableAction("TASK_DELETE");
3841
+ if (choice === "this") {
3842
+ let updated = tasks;
3843
+ const isMaterialized = Object.values(tasks).some(
3844
+ (taskList) => findTaskById(taskList, selectedTaskId)
3845
+ );
3846
+ if (isMaterialized) {
3847
+ updated = taskService.deleteTask(tasks, selectedTaskId);
3848
+ }
3849
+ if (selectedTask.isRecurringInstance && selectedTask.recurringParentId) {
3850
+ updated = taskService.excludeRecurringInstance(updated, selectedTask.recurringParentId, dateStr);
3851
+ } else if (selectedTask.recurrence) {
3852
+ updated = taskService.excludeRecurringInstance(updated, selectedTaskId, dateStr);
3853
+ }
3854
+ setTasks(updated);
3855
+ const updatedTimeline = timelineService.removeEventsByTaskId(
3856
+ timeline,
3857
+ selectedTaskId
3858
+ );
3859
+ setTimeline(updatedTimeline);
3860
+ } else if (choice === "all" && rootParent) {
3861
+ if (selectedTask.parentId) {
3862
+ const deleteSubtaskFromTree = (task, targetId) => {
3863
+ return {
3864
+ ...task,
3865
+ children: task.children.filter((child) => child.id !== targetId).map((child) => deleteSubtaskFromTree(child, targetId))
3866
+ };
3867
+ };
3868
+ let updated = { ...tasks };
3869
+ for (const [date, taskList] of Object.entries(tasks)) {
3870
+ const rootIndex = taskList.findIndex((t) => t.id === rootParent.id);
3871
+ if (rootIndex !== -1) {
3872
+ const updatedRoot = deleteSubtaskFromTree(taskList[rootIndex], selectedTaskId);
3873
+ updated[date] = [
3874
+ ...taskList.slice(0, rootIndex),
3875
+ updatedRoot,
3876
+ ...taskList.slice(rootIndex + 1)
3877
+ ];
3878
+ break;
3879
+ }
3880
+ }
3881
+ setTasks(updated);
3882
+ } else {
3883
+ const parentIdToDelete = selectedTask.recurringParentId || selectedTaskId;
3884
+ const updated = taskService.deleteTask(tasks, parentIdToDelete);
3885
+ setTasks(updated);
3886
+ }
3887
+ const updatedTimeline = timelineService.removeEventsByTaskId(
3888
+ timeline,
3889
+ selectedTaskId
3890
+ );
3891
+ if (selectedTask.recurringParentId) {
3892
+ const parentTimeline = timelineService.removeEventsByTaskId(updatedTimeline, selectedTask.recurringParentId);
3893
+ setTimeline(parentTimeline);
3894
+ } else {
3895
+ setTimeline(updatedTimeline);
3896
+ }
3897
+ } else if (choice === "from-today" && rootParent) {
3898
+ const todayDateObj = /* @__PURE__ */ new Date();
3899
+ todayDateObj.setHours(0, 0, 0, 0);
3900
+ if (selectedTask.parentId) {
3901
+ const deleteSubtaskFromTree = (task, targetId) => {
3902
+ return {
3903
+ ...task,
3904
+ children: task.children.filter((child) => child.id !== targetId).map((child) => deleteSubtaskFromTree(child, targetId))
3905
+ };
3906
+ };
3907
+ let updated = { ...tasks };
3908
+ for (const [date, taskList] of Object.entries(tasks)) {
3909
+ const rootIndex = taskList.findIndex((t) => t.id === rootParent.id);
3910
+ if (rootIndex !== -1) {
3911
+ const findSubtaskByTitlePath = (task, targetTask, path2 = []) => {
3912
+ if (task.title === targetTask.title && path2.length > 0) {
3913
+ return path2;
3914
+ }
3915
+ for (const child of task.children) {
3916
+ const result = findSubtaskByTitlePath(child, targetTask, [...path2, child.title]);
3917
+ if (result) return result;
3918
+ }
3919
+ return null;
3920
+ };
3921
+ const deleteByTitlePath = (task, titlePath2, currentDepth = 0) => {
3922
+ if (currentDepth >= titlePath2.length) return task;
3923
+ return {
3924
+ ...task,
3925
+ children: task.children.filter((child) => child.title !== titlePath2[currentDepth]).map((child) => deleteByTitlePath(child, titlePath2, currentDepth + 1))
3926
+ };
3927
+ };
3928
+ const titlePath = findSubtaskByTitlePath(taskList[rootIndex], selectedTask);
3929
+ if (titlePath) {
3930
+ const updatedRoot = deleteByTitlePath(taskList[rootIndex], titlePath);
3931
+ updated[date] = [
3932
+ ...taskList.slice(0, rootIndex),
3933
+ updatedRoot,
3934
+ ...taskList.slice(rootIndex + 1)
3935
+ ];
3936
+ logger.log("Deleting subtask from root parent by title path (from-today)", {
3937
+ rootParentId: rootParent.id,
3938
+ titlePath
3939
+ });
3940
+ } else {
3941
+ const updatedRoot = deleteSubtaskFromTree(taskList[rootIndex], selectedTaskId);
3942
+ updated[date] = [
3943
+ ...taskList.slice(0, rootIndex),
3944
+ updatedRoot,
3945
+ ...taskList.slice(rootIndex + 1)
3946
+ ];
3947
+ logger.log("Deleting subtask from root parent by ID (from-today)", {
3948
+ rootParentId: rootParent.id,
3949
+ subtaskId: selectedTaskId
3950
+ });
3951
+ }
3952
+ break;
3953
+ }
3954
+ }
3955
+ for (const [date, taskList] of Object.entries(updated)) {
3956
+ const dateObj = new Date(date);
3957
+ dateObj.setHours(0, 0, 0, 0);
3958
+ if (dateObj >= todayDateObj) {
3959
+ let hasChanges = false;
3960
+ const updatedTaskList = taskList.map((task) => {
3961
+ if (task.isRecurringInstance && task.recurringParentId === rootParent.id) {
3962
+ logger.log("Deleting subtask from materialized instance (from-today)", {
3963
+ instanceId: task.id,
3964
+ instanceDate: date,
3965
+ subtaskTitle: selectedTask.title
3966
+ });
3967
+ hasChanges = true;
3968
+ const deleteByTitle = (t, targetTitle) => {
3969
+ return {
3970
+ ...t,
3971
+ children: t.children.filter((child) => child.title !== targetTitle).map((child) => deleteByTitle(child, targetTitle))
3972
+ };
3973
+ };
3974
+ return deleteByTitle(task, selectedTask.title);
3975
+ }
3976
+ return task;
3977
+ });
3978
+ if (hasChanges) {
3979
+ updated[date] = updatedTaskList;
3980
+ }
3981
+ }
3982
+ }
3983
+ setTasks(updated);
3984
+ } else {
3985
+ let updated = { ...tasks };
3986
+ const isMaterialized = Object.values(tasks).some(
3987
+ (taskList) => taskList.some((t) => t.id === selectedTaskId)
3988
+ );
3989
+ if (isMaterialized) {
3990
+ updated = taskService.deleteTask(tasks, selectedTaskId);
3991
+ logger.log("Deleting materialized root task (from-today)", {
3992
+ taskId: selectedTaskId
3993
+ });
3994
+ } else {
3995
+ const parentId = selectedTask.recurringParentId;
3996
+ if (parentId) {
3997
+ updated = taskService.deleteTask(tasks, parentId);
3998
+ logger.log("Deleting parent recurring task (from-today)", {
3999
+ parentId,
4000
+ ephemeralTaskId: selectedTaskId
4001
+ });
4002
+ }
4003
+ }
4004
+ const recurringParentToDelete = selectedTask.recurringParentId || selectedTaskId;
4005
+ for (const [date, taskList] of Object.entries(updated)) {
4006
+ const dateObj = new Date(date);
4007
+ dateObj.setHours(0, 0, 0, 0);
4008
+ if (dateObj >= todayDateObj) {
4009
+ const filteredTaskList = taskList.filter((task) => {
4010
+ if (task.isRecurringInstance && task.recurringParentId === recurringParentToDelete) {
4011
+ logger.log("Deleting materialized instance (from-today)", {
4012
+ instanceId: task.id,
4013
+ instanceDate: date
4014
+ });
4015
+ return false;
4016
+ }
4017
+ return true;
4018
+ });
4019
+ if (filteredTaskList.length !== taskList.length) {
4020
+ updated[date] = filteredTaskList;
4021
+ }
4022
+ }
4023
+ }
4024
+ setTasks(updated);
4025
+ }
4026
+ const updatedTimeline = timelineService.removeEventsByTaskId(
4027
+ timeline,
4028
+ selectedTaskId
4029
+ );
4030
+ setTimeline(updatedTimeline);
4031
+ }
4032
+ setPendingAction(null);
4033
+ }
4034
+ });
4035
+ setShowRecurringEditDialog(true);
4036
+ } else {
4037
+ logger.log("Not recurring, deleting directly");
4038
+ try {
4039
+ pushUndoableAction("TASK_DELETE");
4040
+ const updated = taskService.deleteTask(tasks, selectedTaskId);
4041
+ setTasks(updated);
4042
+ const updatedTimeline = timelineService.removeEventsByTaskId(
3003
4043
  timeline,
3004
- selectedTaskId,
3005
- eventTypeToRemove[previousState]
4044
+ selectedTaskId
3006
4045
  );
3007
4046
  setTimeline(updatedTimeline);
3008
- } else if (isSelectedDateToday) {
3009
- const eventTypeMap = {
3010
- todo: "started" /* STARTED */,
3011
- // shouldn't happen
3012
- completed: "completed" /* COMPLETED */,
3013
- delegated: "delegated" /* DELEGATED */,
3014
- delayed: "delayed" /* DELAYED */
3015
- };
3016
- const event = timelineService.createEvent(
3017
- selectedTaskId,
3018
- selectedTask.title,
3019
- eventTypeMap[newState],
3020
- /* @__PURE__ */ new Date(),
3021
- previousState,
3022
- newState
3023
- );
3024
- const updatedTimeline = timelineService.addEvent(timeline, event);
3025
- setTimeline(updatedTimeline);
4047
+ } catch (err) {
4048
+ console.error("Error deleting task:", err);
3026
4049
  }
3027
- } catch (err) {
3028
- console.error("Error changing task state:", err);
3029
4050
  }
3030
4051
  }
3031
4052
  };
4053
+ const handleChangeState = (newState) => {
4054
+ if (selectedTaskId && selectedTask) {
4055
+ logger.log("[handleChangeState] Called", {
4056
+ selectedTaskId,
4057
+ taskTitle: selectedTask.title,
4058
+ taskDate: selectedTask.date,
4059
+ currentState: selectedTask.state,
4060
+ newState,
4061
+ isRecurringInstance: selectedTask.isRecurringInstance,
4062
+ recurringParentId: selectedTask.recurringParentId,
4063
+ hasRecurrence: !!selectedTask.recurrence,
4064
+ parentId: selectedTask.parentId
4065
+ });
4066
+ performStateChange(selectedTaskId, selectedTask, newState, true);
4067
+ }
4068
+ };
3032
4069
  const handleToggleComplete = () => {
3033
4070
  if (selectedTask) {
3034
4071
  const newState = selectedTask.state === "completed" ? "todo" : "completed";
4072
+ logger.log("[handleToggleComplete] Called", {
4073
+ selectedTaskId,
4074
+ taskTitle: selectedTask.title,
4075
+ taskDate: selectedTask.date,
4076
+ currentState: selectedTask.state,
4077
+ newState,
4078
+ isRecurringInstance: selectedTask.isRecurringInstance,
4079
+ recurringParentId: selectedTask.recurringParentId
4080
+ });
3035
4081
  handleChangeState(newState);
3036
4082
  }
3037
4083
  };
@@ -3041,6 +4087,7 @@ var TasksPane = () => {
3041
4087
  setEditMode("none");
3042
4088
  setEditValue("");
3043
4089
  setParentTaskId(null);
4090
+ setEditingTaskId(null);
3044
4091
  setIsInputMode(false);
3045
4092
  return;
3046
4093
  }
@@ -3054,35 +4101,335 @@ var TasksPane = () => {
3054
4101
  };
3055
4102
  setTasks(newTasks);
3056
4103
  setSelectedIndex(flatTasks.length);
4104
+ setEditMode("none");
4105
+ setEditValue("");
4106
+ setParentTaskId(null);
4107
+ setEditingTaskId(null);
4108
+ setIsInputMode(false);
3057
4109
  } else if (editMode === "addSubtask" && parentTaskId) {
3058
- pushUndoableAction("TASK_ADD");
3059
- const updated = taskService.addSubtask(tasks, parentTaskId, trimmed);
3060
- setTasks(updated);
3061
- const parentIndex = flatTasks.findIndex(
3062
- (ft) => ft.task.id === parentTaskId
3063
- );
3064
- if (parentIndex !== -1) {
3065
- setSelectedIndex(parentIndex + 1);
4110
+ const parentTask = flatTasks.find((ft) => ft.task.id === parentTaskId)?.task;
4111
+ if (parentTask && isRecurringTask(parentTask)) {
4112
+ const savedValue = trimmed;
4113
+ const savedParentId = parentTaskId;
4114
+ const savedIndex = selectedIndex;
4115
+ setRecurringEditConfig({
4116
+ taskId: savedParentId,
4117
+ taskTitle: parentTask.title,
4118
+ actionType: "add-subtask",
4119
+ onConfirm: (choice) => {
4120
+ logger.log("Recurring subtask save confirmed", { choice, newSubtask: savedValue });
4121
+ pushUndoableAction("TASK_ADD");
4122
+ if (choice === "this") {
4123
+ const existingTasks = tasks[dateStr] || [];
4124
+ const taskExists = existingTasks.some((t) => t.id === savedParentId);
4125
+ if (taskExists) {
4126
+ logger.log("Adding subtask to already materialized task (this only)", {
4127
+ parentId: savedParentId,
4128
+ subtaskTitle: savedValue
4129
+ });
4130
+ const updated = taskService.addSubtask(tasks, savedParentId, savedValue);
4131
+ setTasks(updated);
4132
+ } else {
4133
+ const ephemeralParent = dayTasks.find((t) => t.id === savedParentId);
4134
+ if (ephemeralParent && ephemeralParent.isRecurringInstance) {
4135
+ logger.log("Materializing ephemeral recurring instance for subtask addition (this only)", {
4136
+ parentId: savedParentId,
4137
+ recurringParentId: ephemeralParent.recurringParentId,
4138
+ subtaskTitle: savedValue
4139
+ });
4140
+ const newSubtask = taskService.createTask(savedValue, dateStr);
4141
+ const materializedTask = {
4142
+ ...ephemeralParent,
4143
+ children: [...ephemeralParent.children, { ...newSubtask, parentId: savedParentId }]
4144
+ };
4145
+ const updated = {
4146
+ ...tasks,
4147
+ [dateStr]: [...existingTasks, materializedTask]
4148
+ };
4149
+ setTasks(updated);
4150
+ } else if (ephemeralParent) {
4151
+ logger.log("Parent is original recurring task, adding subtask (this only - but will affect template)", {
4152
+ parentId: savedParentId,
4153
+ subtaskTitle: savedValue
4154
+ });
4155
+ const updated = taskService.addSubtask(tasks, savedParentId, savedValue);
4156
+ setTasks(updated);
4157
+ } else {
4158
+ logger.log("Parent task not found in ephemeral dayTasks", {
4159
+ parentId: savedParentId
4160
+ });
4161
+ }
4162
+ }
4163
+ } else if (choice === "all") {
4164
+ const recurringParentId = parentTask.recurringParentId || savedParentId;
4165
+ let updated = taskService.addSubtask(tasks, recurringParentId, savedValue);
4166
+ logger.log("Adding subtask to parent and materialized instances", {
4167
+ recurringParentId,
4168
+ subtaskTitle: savedValue
4169
+ });
4170
+ for (const [date, taskList] of Object.entries(updated)) {
4171
+ for (let i = 0; i < taskList.length; i++) {
4172
+ const task = taskList[i];
4173
+ if (task.isRecurringInstance && task.recurringParentId === recurringParentId) {
4174
+ logger.log("Adding subtask to materialized instance", {
4175
+ instanceId: task.id,
4176
+ instanceDate: date,
4177
+ subtaskTitle: savedValue
4178
+ });
4179
+ updated = taskService.addSubtask(updated, task.id, savedValue);
4180
+ }
4181
+ }
4182
+ }
4183
+ setTasks(updated);
4184
+ } else if (choice === "from-today") {
4185
+ const todayDateObj = /* @__PURE__ */ new Date();
4186
+ todayDateObj.setHours(0, 0, 0, 0);
4187
+ const recurringParentId = parentTask.recurringParentId || savedParentId;
4188
+ let updated = taskService.addSubtask(tasks, recurringParentId, savedValue);
4189
+ logger.log("Adding subtask to parent and future materialized instances (from-today)", {
4190
+ recurringParentId,
4191
+ subtaskTitle: savedValue
4192
+ });
4193
+ for (const [date, taskList] of Object.entries(updated)) {
4194
+ const dateObj = new Date(date);
4195
+ dateObj.setHours(0, 0, 0, 0);
4196
+ if (dateObj >= todayDateObj) {
4197
+ for (let i = 0; i < taskList.length; i++) {
4198
+ const task = taskList[i];
4199
+ if (task.isRecurringInstance && task.recurringParentId === recurringParentId) {
4200
+ logger.log("Adding subtask to materialized instance (from-today)", {
4201
+ instanceId: task.id,
4202
+ instanceDate: date,
4203
+ subtaskTitle: savedValue
4204
+ });
4205
+ updated = taskService.addSubtask(updated, task.id, savedValue);
4206
+ }
4207
+ }
4208
+ }
4209
+ }
4210
+ setTasks(updated);
4211
+ }
4212
+ setTimeout(() => {
4213
+ const parentIndex = flatTasks.findIndex(
4214
+ (ft) => ft.task.id === savedParentId
4215
+ );
4216
+ if (parentIndex !== -1) {
4217
+ setSelectedIndex(parentIndex + 1);
4218
+ }
4219
+ setEditMode("none");
4220
+ setEditValue("");
4221
+ setParentTaskId(null);
4222
+ setEditingTaskId(null);
4223
+ setIsInputMode(false);
4224
+ }, 0);
4225
+ }
4226
+ });
4227
+ setShowRecurringEditDialog(true);
4228
+ } else {
4229
+ pushUndoableAction("TASK_ADD");
4230
+ const updated = taskService.addSubtask(tasks, parentTaskId, trimmed);
4231
+ setTasks(updated);
4232
+ const parentIndex = flatTasks.findIndex(
4233
+ (ft) => ft.task.id === parentTaskId
4234
+ );
4235
+ if (parentIndex !== -1) {
4236
+ setSelectedIndex(parentIndex + 1);
4237
+ }
4238
+ setEditMode("none");
4239
+ setEditValue("");
4240
+ setParentTaskId(null);
4241
+ setEditingTaskId(null);
4242
+ setIsInputMode(false);
4243
+ }
4244
+ } else if (editMode === "edit" && editingTaskId) {
4245
+ const taskBeingEdited = flatTasks.find((ft) => ft.task.id === editingTaskId)?.task;
4246
+ if (taskBeingEdited && hasRecurringAncestor(taskBeingEdited)) {
4247
+ const savedValue = trimmed;
4248
+ const savedTaskId = editingTaskId;
4249
+ const savedIndex = selectedIndex;
4250
+ const rootParent = findRootParent(taskBeingEdited);
4251
+ setRecurringEditConfig({
4252
+ taskId: savedTaskId,
4253
+ taskTitle: taskBeingEdited.title,
4254
+ actionType: "edit",
4255
+ onConfirm: (choice) => {
4256
+ logger.log("Recurring edit save confirmed", { choice, newTitle: savedValue });
4257
+ pushUndoableAction("TASK_UPDATE");
4258
+ if (choice === "this") {
4259
+ const updated = taskService.updateTask(tasks, savedTaskId, {
4260
+ title: savedValue
4261
+ });
4262
+ setTasks(updated);
4263
+ } else if (choice === "all" && rootParent) {
4264
+ let updated = { ...tasks };
4265
+ if (taskBeingEdited.parentId) {
4266
+ const updateSubtaskInTree = (task, targetId, newTitle) => {
4267
+ if (task.id === targetId) {
4268
+ return { ...task, title: newTitle, updatedAt: /* @__PURE__ */ new Date() };
4269
+ }
4270
+ return {
4271
+ ...task,
4272
+ children: task.children.map((child) => updateSubtaskInTree(child, targetId, newTitle))
4273
+ };
4274
+ };
4275
+ logger.log("Updating subtask in root parent and materialized instances", {
4276
+ subtaskId: savedTaskId,
4277
+ newTitle: savedValue,
4278
+ rootParentId: rootParent.id
4279
+ });
4280
+ for (const [date, taskList] of Object.entries(tasks)) {
4281
+ const rootIndex = taskList.findIndex((t) => t.id === rootParent.id);
4282
+ if (rootIndex !== -1) {
4283
+ const updatedRoot = updateSubtaskInTree(taskList[rootIndex], savedTaskId, savedValue);
4284
+ updated[date] = [
4285
+ ...taskList.slice(0, rootIndex),
4286
+ updatedRoot,
4287
+ ...taskList.slice(rootIndex + 1)
4288
+ ];
4289
+ break;
4290
+ }
4291
+ }
4292
+ for (const [date, taskList] of Object.entries(updated)) {
4293
+ let hasChanges = false;
4294
+ const updatedTaskList = taskList.map((task) => {
4295
+ if (task.isRecurringInstance && task.recurringParentId === rootParent.id) {
4296
+ logger.log("Updating subtask in materialized instance", {
4297
+ instanceId: task.id,
4298
+ instanceDate: date,
4299
+ subtaskId: savedTaskId,
4300
+ newTitle: savedValue
4301
+ });
4302
+ hasChanges = true;
4303
+ return updateSubtaskInTree(task, savedTaskId, savedValue);
4304
+ }
4305
+ return task;
4306
+ });
4307
+ if (hasChanges) {
4308
+ updated[date] = updatedTaskList;
4309
+ }
4310
+ }
4311
+ setTasks(updated);
4312
+ } else {
4313
+ const updated2 = taskService.updateTask(tasks, savedTaskId, {
4314
+ title: savedValue
4315
+ });
4316
+ setTasks(updated2);
4317
+ }
4318
+ } else if (choice === "from-today" && rootParent) {
4319
+ const todayDateObj = /* @__PURE__ */ new Date();
4320
+ todayDateObj.setHours(0, 0, 0, 0);
4321
+ let updated = { ...tasks };
4322
+ if (taskBeingEdited.parentId) {
4323
+ const updateSubtaskInTree = (task, targetId, newTitle) => {
4324
+ if (task.id === targetId) {
4325
+ return { ...task, title: newTitle, updatedAt: /* @__PURE__ */ new Date() };
4326
+ }
4327
+ return {
4328
+ ...task,
4329
+ children: task.children.map((child) => updateSubtaskInTree(child, targetId, newTitle))
4330
+ };
4331
+ };
4332
+ logger.log("Updating subtask in root parent and future materialized instances (from-today)", {
4333
+ subtaskId: savedTaskId,
4334
+ newTitle: savedValue,
4335
+ rootParentId: rootParent.id
4336
+ });
4337
+ for (const [date, taskList] of Object.entries(tasks)) {
4338
+ const rootIndex = taskList.findIndex((t) => t.id === rootParent.id);
4339
+ if (rootIndex !== -1) {
4340
+ const updatedRoot = updateSubtaskInTree(taskList[rootIndex], savedTaskId, savedValue);
4341
+ updated[date] = [
4342
+ ...taskList.slice(0, rootIndex),
4343
+ updatedRoot,
4344
+ ...taskList.slice(rootIndex + 1)
4345
+ ];
4346
+ break;
4347
+ }
4348
+ }
4349
+ for (const [date, taskList] of Object.entries(updated)) {
4350
+ const dateObj = new Date(date);
4351
+ dateObj.setHours(0, 0, 0, 0);
4352
+ if (dateObj >= todayDateObj) {
4353
+ let hasChanges = false;
4354
+ const updatedTaskList = taskList.map((task) => {
4355
+ if (task.isRecurringInstance && task.recurringParentId === rootParent.id) {
4356
+ logger.log("Updating subtask in materialized instance (from-today)", {
4357
+ instanceId: task.id,
4358
+ instanceDate: date,
4359
+ subtaskId: savedTaskId,
4360
+ newTitle: savedValue
4361
+ });
4362
+ hasChanges = true;
4363
+ return updateSubtaskInTree(task, savedTaskId, savedValue);
4364
+ }
4365
+ return task;
4366
+ });
4367
+ if (hasChanges) {
4368
+ updated[date] = updatedTaskList;
4369
+ }
4370
+ }
4371
+ }
4372
+ setTasks(updated);
4373
+ } else {
4374
+ let updated2 = taskService.updateTask(tasks, savedTaskId, {
4375
+ title: savedValue
4376
+ });
4377
+ for (const [date, taskList] of Object.entries(updated2)) {
4378
+ const dateObj = new Date(date);
4379
+ dateObj.setHours(0, 0, 0, 0);
4380
+ if (dateObj >= todayDateObj) {
4381
+ let hasChanges = false;
4382
+ const updatedTaskList = taskList.map((task) => {
4383
+ if (task.isRecurringInstance && task.recurringParentId === savedTaskId) {
4384
+ logger.log("Updating materialized instance (from-today)", {
4385
+ instanceId: task.id,
4386
+ instanceDate: date,
4387
+ newTitle: savedValue
4388
+ });
4389
+ hasChanges = true;
4390
+ return { ...task, title: savedValue, updatedAt: /* @__PURE__ */ new Date() };
4391
+ }
4392
+ return task;
4393
+ });
4394
+ if (hasChanges) {
4395
+ updated2[date] = updatedTaskList;
4396
+ }
4397
+ }
4398
+ }
4399
+ setTasks(updated2);
4400
+ }
4401
+ }
4402
+ setTimeout(() => {
4403
+ setEditMode("none");
4404
+ setEditValue("");
4405
+ setEditingTaskId(null);
4406
+ setIsInputMode(false);
4407
+ setSelectedIndex(savedIndex);
4408
+ }, 0);
4409
+ }
4410
+ });
4411
+ setShowRecurringEditDialog(true);
4412
+ } else {
4413
+ pushUndoableAction("TASK_UPDATE");
4414
+ const updated = taskService.updateTask(tasks, editingTaskId, {
4415
+ title: trimmed
4416
+ });
4417
+ setTasks(updated);
4418
+ setEditMode("none");
4419
+ setEditValue("");
4420
+ setEditingTaskId(null);
4421
+ setIsInputMode(false);
3066
4422
  }
3067
- } else if (editMode === "edit" && selectedTaskId) {
3068
- pushUndoableAction("TASK_UPDATE");
3069
- const updated = taskService.updateTask(tasks, selectedTaskId, {
3070
- title: trimmed
3071
- });
3072
- setTasks(updated);
3073
4423
  }
3074
4424
  } catch (err) {
3075
4425
  console.error("Error saving task:", err);
3076
4426
  }
3077
- setEditMode("none");
3078
- setEditValue("");
3079
- setParentTaskId(null);
3080
- setIsInputMode(false);
3081
4427
  };
3082
4428
  const handleCancelEdit = () => {
3083
4429
  setEditMode("none");
3084
4430
  setEditValue("");
3085
4431
  setParentTaskId(null);
4432
+ setEditingTaskId(null);
3086
4433
  setIsInputMode(false);
3087
4434
  };
3088
4435
  const handleToggleExpand = () => {
@@ -3215,6 +4562,15 @@ var TasksPane = () => {
3215
4562
  }
3216
4563
  return;
3217
4564
  }
4565
+ if (input === "r" && selectedTask) {
4566
+ if (selectedTask.parentId) {
4567
+ logger.log("Cannot mark nested task as recurring", { taskId: selectedTask.id, parentId: selectedTask.parentId });
4568
+ return;
4569
+ }
4570
+ setRecurringTaskId(selectedTaskId);
4571
+ setShowRecurringTaskDialog(true);
4572
+ return;
4573
+ }
3218
4574
  if (key.return && selectedTask) {
3219
4575
  handleToggleExpand();
3220
4576
  return;
@@ -3309,7 +4665,7 @@ var TasksPane = () => {
3309
4665
  }
3310
4666
  ),
3311
4667
  /* @__PURE__ */ jsx14(Text7, { children: " " }),
3312
- task.children.length > 0 && /* @__PURE__ */ jsxs9(Fragment, { children: [
4668
+ task.children.length > 0 && /* @__PURE__ */ jsxs9(Fragment2, { children: [
3313
4669
  /* @__PURE__ */ jsx14(Text7, { color: theme.colors.foreground, children: isExpanded ? "\u25BC" : "\u25B6" }),
3314
4670
  /* @__PURE__ */ jsx14(Text7, { children: " " })
3315
4671
  ] }),
@@ -3322,6 +4678,16 @@ var TasksPane = () => {
3322
4678
  children: task.title
3323
4679
  }
3324
4680
  ),
4681
+ task.recurrence && /* @__PURE__ */ jsxs9(
4682
+ Text7,
4683
+ {
4684
+ color: isSelected ? theme.colors.focusIndicator : theme.colors.timelineEventStarted,
4685
+ children: [
4686
+ " ",
4687
+ "\u21BA"
4688
+ ]
4689
+ }
4690
+ ),
3325
4691
  task.startTime && !task.endTime && /* @__PURE__ */ jsxs9(
3326
4692
  Text7,
3327
4693
  {
@@ -3349,6 +4715,7 @@ var TasksPane = () => {
3349
4715
  { key: "D", description: "delegate" },
3350
4716
  { key: "x", description: "delay" },
3351
4717
  { key: "s", description: "start" },
4718
+ { key: "r", description: "recurring" },
3352
4719
  { key: "\u2190/\u2192", description: "collapse/expand" },
3353
4720
  { key: "Cmd+\u2190/\u2192", description: "all" }
3354
4721
  ]
@@ -3576,7 +4943,7 @@ var TimelinePane = () => {
3576
4943
  };
3577
4944
 
3578
4945
  // src/components/common/HelpDialog.tsx
3579
- import { Box as Box13, Text as Text10 } from "ink";
4946
+ import { Box as Box13, Text as Text10, useInput as useInput6 } from "ink";
3580
4947
 
3581
4948
  // src/components/common/Modal.tsx
3582
4949
  import { Box as Box12, useStdout as useStdout3 } from "ink";
@@ -3630,12 +4997,23 @@ var Modal = ({ children }) => {
3630
4997
  };
3631
4998
 
3632
4999
  // src/components/common/HelpDialog.tsx
3633
- import { Fragment as Fragment2, jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
5000
+ import { Fragment as Fragment3, jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
3634
5001
  var HelpDialog = () => {
3635
5002
  const { theme } = useTheme();
5003
+ const { setShowHelp, setShowSettingsDialog } = useApp();
5004
+ useInput6((input, key) => {
5005
+ if (key.escape || input === "?") {
5006
+ setShowHelp(false);
5007
+ }
5008
+ if (key.ctrl && input === "s") {
5009
+ setShowHelp(false);
5010
+ setShowSettingsDialog(true);
5011
+ }
5012
+ }, { isActive: true });
3636
5013
  const shortcuts = [
3637
5014
  { key: "Ctrl+C (twice)", action: "Quit application" },
3638
5015
  { key: "Ctrl+U", action: "Undo last action" },
5016
+ { key: "Ctrl+S", action: "Open settings" },
3639
5017
  { key: "?", action: "Toggle help dialog" },
3640
5018
  { key: "Shift+;", action: "Show month overview" },
3641
5019
  { key: "t", action: "Select theme" },
@@ -3658,11 +5036,22 @@ var HelpDialog = () => {
3658
5036
  { key: "s", action: "Start task" },
3659
5037
  { key: "D", action: "Mark delegated" },
3660
5038
  { key: "x", action: "Mark delayed/cancelled" },
5039
+ { key: "r", action: "Make task recurring" },
3661
5040
  { key: "Enter", action: "Expand/collapse subtasks" },
3662
5041
  { key: "", action: "" },
3663
5042
  { key: "Timeline Pane", action: "" },
3664
5043
  { key: "j/k", action: "Scroll timeline" },
3665
- { key: "Shift+C", action: "Clear timeline" }
5044
+ { key: "Shift+C", action: "Clear timeline" },
5045
+ { key: "", action: "" },
5046
+ { key: "Input Editing", action: "" },
5047
+ { key: "\u2190/\u2192", action: "Move cursor left/right" },
5048
+ { key: "Ctrl+\u2190/\u2192", action: "Move by word" },
5049
+ { key: "Ctrl+A", action: "Move to start of line" },
5050
+ { key: "Ctrl+E", action: "Move to end of line" },
5051
+ { key: "Delete/Backspace", action: "Delete character" },
5052
+ { key: "Ctrl+W", action: "Delete word before cursor" },
5053
+ { key: "Ctrl+U", action: "Delete to start of line" },
5054
+ { key: "Ctrl+K", action: "Delete to end of line" }
3666
5055
  ];
3667
5056
  return /* @__PURE__ */ jsx19(Modal, { children: /* @__PURE__ */ jsxs12(
3668
5057
  Box13,
@@ -3675,11 +5064,11 @@ var HelpDialog = () => {
3675
5064
  backgroundColor: theme.colors.modalBackground || theme.colors.background,
3676
5065
  children: [
3677
5066
  /* @__PURE__ */ jsx19(Text10, { bold: true, color: theme.colors.calendarHeader, children: "Keyboard Shortcuts" }),
3678
- /* @__PURE__ */ jsx19(Box13, { flexDirection: "column", marginTop: 1, children: shortcuts.map((item, idx) => /* @__PURE__ */ jsx19(Box13, { marginY: 0, children: item.key ? /* @__PURE__ */ jsxs12(Fragment2, { children: [
5067
+ /* @__PURE__ */ jsx19(Box13, { flexDirection: "column", marginTop: 1, children: shortcuts.map((item, idx) => /* @__PURE__ */ jsx19(Box13, { marginY: 0, children: item.key ? /* @__PURE__ */ jsxs12(Fragment3, { children: [
3679
5068
  /* @__PURE__ */ jsx19(Box13, { width: 20, children: /* @__PURE__ */ jsx19(Text10, { color: theme.colors.timelineEventStarted, children: item.key }) }),
3680
5069
  /* @__PURE__ */ jsx19(Text10, { color: theme.colors.foreground, children: item.action })
3681
5070
  ] }) : /* @__PURE__ */ jsx19(Text10, { color: theme.colors.separator, children: "\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" }) }, idx)) }),
3682
- /* @__PURE__ */ jsx19(Box13, { marginY: 1, children: /* @__PURE__ */ jsx19(Text10, { color: theme.colors.keyboardHint, dimColor: true, children: "Press '?' to close" }) })
5071
+ /* @__PURE__ */ jsx19(Box13, { marginY: 1, children: /* @__PURE__ */ jsx19(Text10, { color: theme.colors.keyboardHint, dimColor: true, children: "Press '?' or Esc to close" }) })
3683
5072
  ]
3684
5073
  }
3685
5074
  ) });
@@ -3687,7 +5076,7 @@ var HelpDialog = () => {
3687
5076
 
3688
5077
  // src/components/common/ThemeDialog.tsx
3689
5078
  import { useState as useState9, useMemo as useMemo3, useEffect as useEffect10 } from "react";
3690
- import { Box as Box14, Text as Text11, useInput as useInput6 } from "ink";
5079
+ import { Box as Box14, Text as Text11, useInput as useInput7 } from "ink";
3691
5080
  import { jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
3692
5081
  var ThemeDialog = () => {
3693
5082
  const { theme, setTheme, themeName } = useTheme();
@@ -3731,7 +5120,7 @@ var ThemeDialog = () => {
3731
5120
  useEffect10(() => {
3732
5121
  setSelectedIndex(0);
3733
5122
  }, [searchQuery]);
3734
- useInput6(
5123
+ useInput7(
3735
5124
  (input, key) => {
3736
5125
  if (key.escape) {
3737
5126
  setShowThemeDialog(false);
@@ -3842,10 +5231,299 @@ var ThemeDialog = () => {
3842
5231
  ) });
3843
5232
  };
3844
5233
 
5234
+ // src/components/common/SettingsDialog.tsx
5235
+ import { useState as useState10 } from "react";
5236
+ import { Box as Box15, Text as Text12, useInput as useInput8 } from "ink";
5237
+ import { jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
5238
+ var SettingsDialog = () => {
5239
+ const { theme } = useTheme();
5240
+ const { setShowSettingsDialog } = useApp();
5241
+ const { data, save } = useStorage();
5242
+ const [selectedIndex, setSelectedIndex] = useState10(0);
5243
+ const settings = [
5244
+ {
5245
+ label: "Auto-move unfinished tasks to next day",
5246
+ key: "autoMoveUnfinishedTasks",
5247
+ value: data?.settings?.autoMoveUnfinishedTasks ?? true
5248
+ }
5249
+ ];
5250
+ const toggleSetting = (key) => {
5251
+ if (!data) return;
5252
+ const newValue = !data.settings[key];
5253
+ logger.log("Toggling setting", { key, newValue });
5254
+ save({
5255
+ ...data,
5256
+ settings: {
5257
+ ...data.settings,
5258
+ [key]: newValue
5259
+ }
5260
+ });
5261
+ };
5262
+ useInput8(
5263
+ (input, key) => {
5264
+ if (key.escape) {
5265
+ logger.log("Closing settings dialog");
5266
+ setShowSettingsDialog(false);
5267
+ return;
5268
+ }
5269
+ if (key.upArrow || input === "k") {
5270
+ setSelectedIndex((prev) => prev > 0 ? prev - 1 : settings.length - 1);
5271
+ return;
5272
+ }
5273
+ if (key.downArrow || input === "j") {
5274
+ setSelectedIndex((prev) => prev < settings.length - 1 ? prev + 1 : 0);
5275
+ return;
5276
+ }
5277
+ if (key.return || input === " ") {
5278
+ toggleSetting(settings[selectedIndex].key);
5279
+ return;
5280
+ }
5281
+ },
5282
+ { isActive: true }
5283
+ );
5284
+ return /* @__PURE__ */ jsx21(Modal, { children: /* @__PURE__ */ jsxs14(
5285
+ Box15,
5286
+ {
5287
+ flexDirection: "column",
5288
+ borderStyle: "double",
5289
+ borderColor: theme.colors.helpDialogBorder,
5290
+ paddingX: 2,
5291
+ paddingY: 1,
5292
+ width: 70,
5293
+ backgroundColor: theme.colors.modalBackground || theme.colors.background,
5294
+ children: [
5295
+ /* @__PURE__ */ jsx21(Text12, { bold: true, color: theme.colors.calendarHeader, underline: true, children: "Settings" }),
5296
+ /* @__PURE__ */ jsx21(Box15, { flexDirection: "column", marginTop: 1, children: settings.map((setting, idx) => {
5297
+ const isSelected = idx === selectedIndex;
5298
+ const isEnabled = setting.value;
5299
+ return /* @__PURE__ */ jsx21(Box15, { marginY: 0, children: /* @__PURE__ */ jsxs14(
5300
+ Text12,
5301
+ {
5302
+ color: isSelected ? theme.colors.focusIndicator : theme.colors.foreground,
5303
+ bold: isSelected,
5304
+ children: [
5305
+ isSelected ? "\u279C " : " ",
5306
+ setting.label,
5307
+ ": ",
5308
+ /* @__PURE__ */ jsx21(
5309
+ Text12,
5310
+ {
5311
+ color: isEnabled ? theme.colors.taskStateCompleted : theme.colors.taskStateDelayed,
5312
+ children: isEnabled ? "ON" : "OFF"
5313
+ }
5314
+ )
5315
+ ]
5316
+ }
5317
+ ) }, setting.key);
5318
+ }) }),
5319
+ /* @__PURE__ */ jsx21(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx21(Text12, { color: theme.colors.keyboardHint, dimColor: true, children: "\u2191/\u2193 or k/j to navigate \u2022 Space/Enter to toggle \u2022 Esc to close" }) })
5320
+ ]
5321
+ }
5322
+ ) });
5323
+ };
5324
+
5325
+ // src/components/common/RecurringTaskDialog.tsx
5326
+ import { useState as useState11 } from "react";
5327
+ import { Box as Box16, Text as Text13, useInput as useInput9 } from "ink";
5328
+ import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
5329
+ var RecurringTaskDialog = ({
5330
+ onConfirm,
5331
+ onCancel
5332
+ }) => {
5333
+ const { theme } = useTheme();
5334
+ const frequencies = [
5335
+ { value: "daily", label: "Daily" },
5336
+ { value: "weekdays", label: "Weekdays (Mon-Fri)" },
5337
+ { value: "weekly", label: "Weekly" },
5338
+ { value: "monthly", label: "Monthly" },
5339
+ { value: "yearly", label: "Yearly" }
5340
+ ];
5341
+ const [selectedIndex, setSelectedIndex] = useState11(0);
5342
+ useInput9(
5343
+ (input, key) => {
5344
+ if (key.escape) {
5345
+ logger.log("Cancelling recurring task dialog");
5346
+ onCancel();
5347
+ return;
5348
+ }
5349
+ if (key.upArrow || input === "k") {
5350
+ setSelectedIndex(
5351
+ (prev) => prev > 0 ? prev - 1 : frequencies.length - 1
5352
+ );
5353
+ return;
5354
+ }
5355
+ if (key.downArrow || input === "j") {
5356
+ setSelectedIndex(
5357
+ (prev) => prev < frequencies.length - 1 ? prev + 1 : 0
5358
+ );
5359
+ return;
5360
+ }
5361
+ if (key.return) {
5362
+ const selectedFrequency = frequencies[selectedIndex].value;
5363
+ const pattern = {
5364
+ frequency: selectedFrequency
5365
+ };
5366
+ logger.log("Setting task recurrence", { frequency: selectedFrequency });
5367
+ onConfirm(pattern);
5368
+ return;
5369
+ }
5370
+ },
5371
+ { isActive: true }
5372
+ );
5373
+ return /* @__PURE__ */ jsx22(Modal, { children: /* @__PURE__ */ jsxs15(
5374
+ Box16,
5375
+ {
5376
+ flexDirection: "column",
5377
+ borderStyle: "double",
5378
+ borderColor: theme.colors.helpDialogBorder,
5379
+ paddingX: 2,
5380
+ paddingY: 1,
5381
+ width: 44,
5382
+ backgroundColor: theme.colors.modalBackground || theme.colors.background,
5383
+ children: [
5384
+ /* @__PURE__ */ jsx22(Text13, { bold: true, color: theme.colors.calendarHeader, underline: true, children: "Make Task Recurring" }),
5385
+ /* @__PURE__ */ jsx22(Box16, { flexDirection: "column", marginTop: 1, children: frequencies.map((freq, idx) => {
5386
+ const isSelected = idx === selectedIndex;
5387
+ return /* @__PURE__ */ jsx22(Box16, { marginY: 0, children: /* @__PURE__ */ jsxs15(
5388
+ Text13,
5389
+ {
5390
+ color: isSelected ? theme.colors.focusIndicator : theme.colors.foreground,
5391
+ bold: isSelected,
5392
+ children: [
5393
+ isSelected ? "\u279C " : " ",
5394
+ freq.label
5395
+ ]
5396
+ }
5397
+ ) }, freq.value);
5398
+ }) }),
5399
+ /* @__PURE__ */ jsx22(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx22(Text13, { color: theme.colors.keyboardHint, dimColor: true, children: "\u2191/\u2193 or k/j to navigate \u2022 Enter to confirm \u2022 Esc to cancel" }) })
5400
+ ]
5401
+ }
5402
+ ) });
5403
+ };
5404
+
5405
+ // src/components/common/RecurringEditDialog.tsx
5406
+ import { useState as useState12 } from "react";
5407
+ import { Box as Box17, Text as Text14, useInput as useInput10 } from "ink";
5408
+ import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
5409
+ var RecurringEditDialog = ({
5410
+ taskTitle,
5411
+ actionType,
5412
+ onConfirm
5413
+ }) => {
5414
+ const { theme } = useTheme();
5415
+ const [selectedIndex, setSelectedIndex] = useState12(0);
5416
+ const options = [
5417
+ { value: "this", label: getThisOptionLabel(actionType) },
5418
+ { value: "all", label: "All occurrences" },
5419
+ { value: "from-today", label: "All occurrences from today" }
5420
+ ];
5421
+ function getThisOptionLabel(action) {
5422
+ switch (action) {
5423
+ case "edit":
5424
+ return "This task only";
5425
+ case "delete":
5426
+ return "This task only";
5427
+ case "complete":
5428
+ return "This task only";
5429
+ case "state-change":
5430
+ return "This task only";
5431
+ case "add-subtask":
5432
+ return "This task only";
5433
+ default:
5434
+ return "This task only";
5435
+ }
5436
+ }
5437
+ function getActionDescription(action) {
5438
+ switch (action) {
5439
+ case "edit":
5440
+ return "editing";
5441
+ case "delete":
5442
+ return "deleting";
5443
+ case "complete":
5444
+ return "completing";
5445
+ case "state-change":
5446
+ return "changing the state of";
5447
+ case "add-subtask":
5448
+ return "adding a subtask to";
5449
+ default:
5450
+ return "modifying";
5451
+ }
5452
+ }
5453
+ useInput10(
5454
+ (input, key) => {
5455
+ if (key.escape) {
5456
+ logger.log("Cancelling recurring edit dialog");
5457
+ onConfirm("cancel");
5458
+ return;
5459
+ }
5460
+ if (key.upArrow || input === "k") {
5461
+ setSelectedIndex((prev) => prev > 0 ? prev - 1 : options.length - 1);
5462
+ return;
5463
+ }
5464
+ if (key.downArrow || input === "j") {
5465
+ setSelectedIndex((prev) => prev < options.length - 1 ? prev + 1 : 0);
5466
+ return;
5467
+ }
5468
+ if (key.return) {
5469
+ const selectedAction = options[selectedIndex].value;
5470
+ logger.log("Recurring edit action selected", {
5471
+ actionType,
5472
+ selectedAction,
5473
+ taskTitle
5474
+ });
5475
+ onConfirm(selectedAction);
5476
+ return;
5477
+ }
5478
+ },
5479
+ { isActive: true }
5480
+ );
5481
+ return /* @__PURE__ */ jsx23(Modal, { children: /* @__PURE__ */ jsxs16(
5482
+ Box17,
5483
+ {
5484
+ flexDirection: "column",
5485
+ borderStyle: "double",
5486
+ borderColor: theme.colors.helpDialogBorder,
5487
+ paddingX: 2,
5488
+ paddingY: 1,
5489
+ width: 54,
5490
+ backgroundColor: theme.colors.modalBackground || theme.colors.background,
5491
+ children: [
5492
+ /* @__PURE__ */ jsx23(Text14, { bold: true, color: theme.colors.calendarHeader, children: "Modify Recurring Task" }),
5493
+ /* @__PURE__ */ jsx23(Box17, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsxs16(Text14, { color: theme.colors.foreground, children: [
5494
+ "You are ",
5495
+ getActionDescription(actionType),
5496
+ " a recurring task:"
5497
+ ] }) }),
5498
+ /* @__PURE__ */ jsx23(Box17, { marginBottom: 1, children: /* @__PURE__ */ jsxs16(Text14, { color: theme.colors.focusIndicator, bold: true, children: [
5499
+ '"',
5500
+ taskTitle,
5501
+ '"'
5502
+ ] }) }),
5503
+ /* @__PURE__ */ jsx23(Box17, { flexDirection: "column", marginTop: 1, children: options.map((option, idx) => {
5504
+ const isSelected = idx === selectedIndex;
5505
+ return /* @__PURE__ */ jsx23(Box17, { marginY: 0, children: /* @__PURE__ */ jsxs16(
5506
+ Text14,
5507
+ {
5508
+ color: isSelected ? theme.colors.focusIndicator : theme.colors.foreground,
5509
+ bold: isSelected,
5510
+ children: [
5511
+ isSelected ? "\u279C " : " ",
5512
+ option.label
5513
+ ]
5514
+ }
5515
+ ) }, option.value);
5516
+ }) }),
5517
+ /* @__PURE__ */ jsx23(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx23(Text14, { color: theme.colors.keyboardHint, dimColor: true, children: "\u2191/\u2193 or k/j to navigate \u2022 Enter to confirm \u2022 Esc to cancel" }) })
5518
+ ]
5519
+ }
5520
+ ) });
5521
+ };
5522
+
3845
5523
  // src/components/common/ClearTimelineDialog.tsx
3846
- import { Box as Box15, Text as Text12, useInput as useInput7 } from "ink";
5524
+ import { Box as Box18, Text as Text15, useInput as useInput11 } from "ink";
3847
5525
  import { format as format2 } from "date-fns";
3848
- import { jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
5526
+ import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
3849
5527
  var ClearTimelineDialog = () => {
3850
5528
  const { theme } = useTheme();
3851
5529
  const {
@@ -3865,15 +5543,15 @@ var ClearTimelineDialog = () => {
3865
5543
  const handleCancel = () => {
3866
5544
  setShowClearTimelineDialog(false);
3867
5545
  };
3868
- useInput7((input, key) => {
5546
+ useInput11((input, key) => {
3869
5547
  if (input.toLowerCase() === "y" || key.return) {
3870
5548
  handleConfirm();
3871
5549
  } else if (input.toLowerCase() === "n" || key.escape) {
3872
5550
  handleCancel();
3873
5551
  }
3874
5552
  });
3875
- return /* @__PURE__ */ jsx21(Modal, { children: /* @__PURE__ */ jsxs14(
3876
- Box15,
5553
+ return /* @__PURE__ */ jsx24(Modal, { children: /* @__PURE__ */ jsxs17(
5554
+ Box18,
3877
5555
  {
3878
5556
  flexDirection: "column",
3879
5557
  borderStyle: "double",
@@ -3882,34 +5560,34 @@ var ClearTimelineDialog = () => {
3882
5560
  paddingY: 2,
3883
5561
  backgroundColor: theme.colors.modalBackground || theme.colors.background,
3884
5562
  children: [
3885
- /* @__PURE__ */ jsx21(Box15, { justifyContent: "center", marginBottom: 1, children: /* @__PURE__ */ jsx21(Text12, { bold: true, color: theme.colors.taskStateDelayed, children: "Clear Timeline" }) }),
3886
- /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", marginY: 1, children: [
3887
- /* @__PURE__ */ jsx21(Text12, { color: theme.colors.foreground, children: "You are about to clear the timeline for" }),
3888
- /* @__PURE__ */ jsx21(Box15, { justifyContent: "center", marginY: 1, children: /* @__PURE__ */ jsx21(Text12, { bold: true, color: theme.colors.calendarHeader, children: formattedDate }) }),
3889
- eventCount > 0 ? /* @__PURE__ */ jsxs14(Text12, { color: theme.colors.keyboardHint, children: [
5563
+ /* @__PURE__ */ jsx24(Box18, { justifyContent: "center", marginBottom: 1, children: /* @__PURE__ */ jsx24(Text15, { bold: true, color: theme.colors.taskStateDelayed, children: "Clear Timeline" }) }),
5564
+ /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", marginY: 1, children: [
5565
+ /* @__PURE__ */ jsx24(Text15, { color: theme.colors.foreground, children: "You are about to clear the timeline for" }),
5566
+ /* @__PURE__ */ jsx24(Box18, { justifyContent: "center", marginY: 1, children: /* @__PURE__ */ jsx24(Text15, { bold: true, color: theme.colors.calendarHeader, children: formattedDate }) }),
5567
+ eventCount > 0 ? /* @__PURE__ */ jsxs17(Text15, { color: theme.colors.keyboardHint, children: [
3890
5568
  "This will remove ",
3891
5569
  eventCount,
3892
5570
  " event",
3893
5571
  eventCount !== 1 ? "s" : "",
3894
5572
  " from the timeline."
3895
- ] }) : /* @__PURE__ */ jsx21(Text12, { color: theme.colors.keyboardHint, dimColor: true, children: "The timeline is already empty." })
5573
+ ] }) : /* @__PURE__ */ jsx24(Text15, { color: theme.colors.keyboardHint, dimColor: true, children: "The timeline is already empty." })
3896
5574
  ] }),
3897
- /* @__PURE__ */ jsxs14(Box15, { marginTop: 2, justifyContent: "center", children: [
3898
- /* @__PURE__ */ jsx21(Text12, { color: theme.colors.foreground, children: "Are you sure? " }),
3899
- /* @__PURE__ */ jsx21(Text12, { color: theme.colors.taskStateCompleted, bold: true, children: "[Y]es" }),
3900
- /* @__PURE__ */ jsx21(Text12, { color: theme.colors.foreground, children: " / " }),
3901
- /* @__PURE__ */ jsx21(Text12, { color: theme.colors.taskStateDelayed, bold: true, children: "[N]o" })
5575
+ /* @__PURE__ */ jsxs17(Box18, { marginTop: 2, justifyContent: "center", children: [
5576
+ /* @__PURE__ */ jsx24(Text15, { color: theme.colors.foreground, children: "Are you sure? " }),
5577
+ /* @__PURE__ */ jsx24(Text15, { color: theme.colors.taskStateCompleted, bold: true, children: "[Y]es" }),
5578
+ /* @__PURE__ */ jsx24(Text15, { color: theme.colors.foreground, children: " / " }),
5579
+ /* @__PURE__ */ jsx24(Text15, { color: theme.colors.taskStateDelayed, bold: true, children: "[N]o" })
3902
5580
  ] }),
3903
- /* @__PURE__ */ jsx21(Box15, { marginTop: 2, justifyContent: "center", children: /* @__PURE__ */ jsx21(Text12, { color: theme.colors.keyboardHint, dimColor: true, children: "Press Y to confirm, N or Esc to cancel" }) })
5581
+ /* @__PURE__ */ jsx24(Box18, { marginTop: 2, justifyContent: "center", children: /* @__PURE__ */ jsx24(Text15, { color: theme.colors.keyboardHint, dimColor: true, children: "Press Y to confirm, N or Esc to cancel" }) })
3904
5582
  ]
3905
5583
  }
3906
5584
  ) });
3907
5585
  };
3908
5586
 
3909
5587
  // src/components/common/UpdateDialog.tsx
3910
- import { useState as useState10 } from "react";
3911
- import { Box as Box16, Text as Text13, useInput as useInput8 } from "ink";
3912
- import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
5588
+ import { useState as useState13 } from "react";
5589
+ import { Box as Box19, Text as Text16, useInput as useInput12 } from "ink";
5590
+ import { jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
3913
5591
  var UpdateDialog = ({
3914
5592
  currentVersion,
3915
5593
  latestVersion,
@@ -3917,12 +5595,12 @@ var UpdateDialog = ({
3917
5595
  onSkipVersion
3918
5596
  }) => {
3919
5597
  const { theme } = useTheme();
3920
- const [selectedOption, setSelectedOption] = useState10(0);
5598
+ const [selectedOption, setSelectedOption] = useState13(0);
3921
5599
  const options = [
3922
5600
  { label: "Dismiss", action: onDismiss },
3923
5601
  { label: "Skip this version", action: () => onSkipVersion(latestVersion) }
3924
5602
  ];
3925
- useInput8((input, key) => {
5603
+ useInput12((input, key) => {
3926
5604
  if (key.escape) {
3927
5605
  onDismiss();
3928
5606
  return;
@@ -3940,8 +5618,8 @@ var UpdateDialog = ({
3940
5618
  return;
3941
5619
  }
3942
5620
  });
3943
- return /* @__PURE__ */ jsx22(Modal, { children: /* @__PURE__ */ jsxs15(
3944
- Box16,
5621
+ return /* @__PURE__ */ jsx25(Modal, { children: /* @__PURE__ */ jsxs18(
5622
+ Box19,
3945
5623
  {
3946
5624
  flexDirection: "column",
3947
5625
  borderStyle: "double",
@@ -3951,27 +5629,27 @@ var UpdateDialog = ({
3951
5629
  width: 50,
3952
5630
  backgroundColor: theme.colors.modalBackground || theme.colors.background,
3953
5631
  children: [
3954
- /* @__PURE__ */ jsx22(Text13, { bold: true, color: theme.colors.calendarHeader, children: "Update Available" }),
3955
- /* @__PURE__ */ jsxs15(Box16, { marginTop: 1, flexDirection: "column", children: [
3956
- /* @__PURE__ */ jsx22(Text13, { color: theme.colors.foreground, children: "A new version of Epoch is available!" }),
3957
- /* @__PURE__ */ jsxs15(Box16, { marginTop: 1, children: [
3958
- /* @__PURE__ */ jsxs15(Text13, { color: theme.colors.foreground, dimColor: true, children: [
5632
+ /* @__PURE__ */ jsx25(Text16, { bold: true, color: theme.colors.calendarHeader, children: "Update Available" }),
5633
+ /* @__PURE__ */ jsxs18(Box19, { marginTop: 1, flexDirection: "column", children: [
5634
+ /* @__PURE__ */ jsx25(Text16, { color: theme.colors.foreground, children: "A new version of Epoch is available!" }),
5635
+ /* @__PURE__ */ jsxs18(Box19, { marginTop: 1, children: [
5636
+ /* @__PURE__ */ jsxs18(Text16, { color: theme.colors.foreground, dimColor: true, children: [
3959
5637
  "Current:",
3960
5638
  " "
3961
5639
  ] }),
3962
- /* @__PURE__ */ jsx22(Text13, { color: theme.colors.taskDelayed, children: currentVersion }),
3963
- /* @__PURE__ */ jsx22(Text13, { color: theme.colors.foreground, dimColor: true, children: " \u2192 " }),
3964
- /* @__PURE__ */ jsx22(Text13, { color: theme.colors.taskCompleted, bold: true, children: latestVersion })
5640
+ /* @__PURE__ */ jsx25(Text16, { color: theme.colors.taskDelayed, children: currentVersion }),
5641
+ /* @__PURE__ */ jsx25(Text16, { color: theme.colors.foreground, dimColor: true, children: " \u2192 " }),
5642
+ /* @__PURE__ */ jsx25(Text16, { color: theme.colors.taskCompleted, bold: true, children: latestVersion })
3965
5643
  ] })
3966
5644
  ] }),
3967
- /* @__PURE__ */ jsxs15(Box16, { marginTop: 1, flexDirection: "column", children: [
3968
- /* @__PURE__ */ jsx22(Text13, { color: theme.colors.foreground, dimColor: true, children: "To update, run:" }),
3969
- /* @__PURE__ */ jsx22(Box16, { marginTop: 0, paddingLeft: 1, children: /* @__PURE__ */ jsx22(Text13, { color: theme.colors.timelineEventStarted, children: "npm install -g epoch-tui@latest" }) })
5645
+ /* @__PURE__ */ jsxs18(Box19, { marginTop: 1, flexDirection: "column", children: [
5646
+ /* @__PURE__ */ jsx25(Text16, { color: theme.colors.foreground, dimColor: true, children: "To update, run:" }),
5647
+ /* @__PURE__ */ jsx25(Box19, { marginTop: 0, paddingLeft: 1, children: /* @__PURE__ */ jsx25(Text16, { color: theme.colors.timelineEventStarted, children: "npm install -g epoch-tui@latest" }) })
3970
5648
  ] }),
3971
- /* @__PURE__ */ jsx22(Box16, { marginTop: 1, flexDirection: "column", children: options.map((option, idx) => {
5649
+ /* @__PURE__ */ jsx25(Box19, { marginTop: 1, flexDirection: "column", children: options.map((option, idx) => {
3972
5650
  const isSelected = idx === selectedOption;
3973
- return /* @__PURE__ */ jsx22(Box16, { children: /* @__PURE__ */ jsxs15(
3974
- Text13,
5651
+ return /* @__PURE__ */ jsx25(Box19, { children: /* @__PURE__ */ jsxs18(
5652
+ Text16,
3975
5653
  {
3976
5654
  color: isSelected ? theme.colors.focusIndicator : theme.colors.foreground,
3977
5655
  bold: isSelected,
@@ -3982,21 +5660,21 @@ var UpdateDialog = ({
3982
5660
  }
3983
5661
  ) }, option.label);
3984
5662
  }) }),
3985
- /* @__PURE__ */ jsx22(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx22(Text13, { color: theme.colors.keyboardHint, dimColor: true, children: "\u2191/\u2193 to navigate \u2022 Enter to select \u2022 Esc to dismiss" }) })
5663
+ /* @__PURE__ */ jsx25(Box19, { marginTop: 1, children: /* @__PURE__ */ jsx25(Text16, { color: theme.colors.keyboardHint, dimColor: true, children: "\u2191/\u2193 to navigate \u2022 Enter to select \u2022 Esc to dismiss" }) })
3986
5664
  ]
3987
5665
  }
3988
5666
  ) });
3989
5667
  };
3990
5668
 
3991
5669
  // src/components/overview/OverviewScreen.tsx
3992
- import React11, { useMemo as useMemo4 } from "react";
3993
- import { Box as Box17, Text as Text14, useInput as useInput9 } from "ink";
5670
+ import React14, { useMemo as useMemo4 } from "react";
5671
+ import { Box as Box20, Text as Text17, useInput as useInput13 } from "ink";
3994
5672
  import { startOfMonth as startOfMonth2, endOfMonth as endOfMonth2, eachDayOfInterval as eachDayOfInterval2 } from "date-fns";
3995
- import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
5673
+ import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
3996
5674
  var OverviewScreen = () => {
3997
5675
  const { theme } = useTheme();
3998
5676
  const { tasks, overviewMonth, setOverviewMonth, setShowOverview } = useApp();
3999
- const [scrollOffset, setScrollOffset] = React11.useState(0);
5677
+ const [scrollOffset, setScrollOffset] = React14.useState(0);
4000
5678
  const { height: terminalHeight } = useTerminalSize();
4001
5679
  const visibleRows = useMemo4(() => {
4002
5680
  return Math.max(2, Math.floor((terminalHeight - 9) / 3));
@@ -4032,10 +5710,10 @@ var OverviewScreen = () => {
4032
5710
  setOverviewMonth({ ...overviewMonth, month: newMonth });
4033
5711
  }
4034
5712
  };
4035
- React11.useEffect(() => {
5713
+ React14.useEffect(() => {
4036
5714
  setScrollOffset(0);
4037
5715
  }, [overviewMonth]);
4038
- useInput9((input, key) => {
5716
+ useInput13((input, key) => {
4039
5717
  if (key.escape) {
4040
5718
  setShowOverview(false);
4041
5719
  return;
@@ -4071,21 +5749,21 @@ var OverviewScreen = () => {
4071
5749
  );
4072
5750
  const canScrollUp = scrollOffset > 0;
4073
5751
  const canScrollDown = scrollOffset + visibleRows < rows;
4074
- return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", padding: 1, width: "100%", height: "100%", children: [
4075
- /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", marginBottom: 1, children: [
4076
- /* @__PURE__ */ jsx23(Text14, { bold: true, color: theme.colors.focusIndicator, children: "Overview" }),
4077
- /* @__PURE__ */ jsx23(Text14, { color: theme.colors.foreground, children: monthName })
5752
+ return /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", padding: 1, width: "100%", height: "100%", children: [
5753
+ /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", marginBottom: 1, children: [
5754
+ /* @__PURE__ */ jsx26(Text17, { bold: true, color: theme.colors.focusIndicator, children: "Overview" }),
5755
+ /* @__PURE__ */ jsx26(Text17, { color: theme.colors.foreground, children: monthName })
4078
5756
  ] }),
4079
- /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", flexGrow: 1, children: [
4080
- canScrollUp && /* @__PURE__ */ jsx23(Box17, { justifyContent: "center", marginBottom: 1, children: /* @__PURE__ */ jsx23(Text14, { color: theme.colors.keyboardHint, dimColor: true, children: "-- more above --" }) }),
5757
+ /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", flexGrow: 1, children: [
5758
+ canScrollUp && /* @__PURE__ */ jsx26(Box20, { justifyContent: "center", marginBottom: 1, children: /* @__PURE__ */ jsx26(Text17, { color: theme.colors.keyboardHint, dimColor: true, children: "-- more above --" }) }),
4081
5759
  visibleRowData.map((_, index) => {
4082
5760
  const rowIndex = scrollOffset + index;
4083
- return /* @__PURE__ */ jsx23(Box17, { flexDirection: "row", marginBottom: 1, children: Array.from({ length: columns }).map((_2, colIndex) => {
5761
+ return /* @__PURE__ */ jsx26(Box20, { flexDirection: "row", marginBottom: 1, children: Array.from({ length: columns }).map((_2, colIndex) => {
4084
5762
  const dateIndex = rowIndex * columns + colIndex;
4085
5763
  const date = monthDates[dateIndex];
4086
5764
  if (!date) {
4087
- return /* @__PURE__ */ jsx23(
4088
- Box17,
5765
+ return /* @__PURE__ */ jsx26(
5766
+ Box20,
4089
5767
  {
4090
5768
  flexDirection: "column",
4091
5769
  flexGrow: 1,
@@ -4105,17 +5783,17 @@ var OverviewScreen = () => {
4105
5783
  }
4106
5784
  };
4107
5785
  traverse(dayTasks, 0);
4108
- return /* @__PURE__ */ jsxs16(
4109
- Box17,
5786
+ return /* @__PURE__ */ jsxs19(
5787
+ Box20,
4110
5788
  {
4111
5789
  flexDirection: "column",
4112
5790
  flexGrow: 1,
4113
5791
  flexBasis: 0,
4114
5792
  marginRight: colIndex === columns - 1 ? 0 : 2,
4115
5793
  children: [
4116
- /* @__PURE__ */ jsx23(Text14, { bold: true, color: theme.colors.calendarSelected, children: formatDate(date, "do MMM") }),
4117
- /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", children: [
4118
- flatTasksWithDepth.length === 0 ? /* @__PURE__ */ jsx23(Text14, { dimColor: true, color: theme.colors.keyboardHint, children: "No tasks" }) : flatTasksWithDepth.slice(0, 10).map(({ task, depth }) => /* @__PURE__ */ jsx23(
5794
+ /* @__PURE__ */ jsx26(Text17, { bold: true, color: theme.colors.calendarSelected, children: formatDate(date, "do MMM") }),
5795
+ /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", children: [
5796
+ flatTasksWithDepth.length === 0 ? /* @__PURE__ */ jsx26(Text17, { dimColor: true, color: theme.colors.keyboardHint, children: "No tasks" }) : flatTasksWithDepth.slice(0, 10).map(({ task, depth }) => /* @__PURE__ */ jsx26(
4119
5797
  TaskItem,
4120
5798
  {
4121
5799
  task,
@@ -4124,7 +5802,7 @@ var OverviewScreen = () => {
4124
5802
  },
4125
5803
  task.id
4126
5804
  )),
4127
- flatTasksWithDepth.length > 10 && /* @__PURE__ */ jsxs16(Text14, { dimColor: true, color: theme.colors.keyboardHint, children: [
5805
+ flatTasksWithDepth.length > 10 && /* @__PURE__ */ jsxs19(Text17, { dimColor: true, color: theme.colors.keyboardHint, children: [
4128
5806
  "+",
4129
5807
  flatTasksWithDepth.length - 10,
4130
5808
  " more..."
@@ -4136,22 +5814,22 @@ var OverviewScreen = () => {
4136
5814
  );
4137
5815
  }) }, rowIndex);
4138
5816
  }),
4139
- canScrollDown && /* @__PURE__ */ jsx23(Box17, { justifyContent: "center", marginTop: 1, children: /* @__PURE__ */ jsx23(Text14, { color: theme.colors.keyboardHint, dimColor: true, children: "-- more below --" }) })
5817
+ canScrollDown && /* @__PURE__ */ jsx26(Box20, { justifyContent: "center", marginTop: 1, children: /* @__PURE__ */ jsx26(Text17, { color: theme.colors.keyboardHint, dimColor: true, children: "-- more below --" }) })
4140
5818
  ] }),
4141
- /* @__PURE__ */ jsx23(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx23(Text14, { color: theme.colors.keyboardHint, dimColor: true, children: "n/p or \u2190/\u2192: month | j/k or \u2193/\u2191: scroll | Esc: close | Shift+;: toggle" }) })
5819
+ /* @__PURE__ */ jsx26(Box20, { marginTop: 1, children: /* @__PURE__ */ jsx26(Text17, { color: theme.colors.keyboardHint, dimColor: true, children: "n/p or \u2190/\u2192: month | j/k or \u2193/\u2191: scroll | Esc: close | Shift+;: toggle" }) })
4142
5820
  ] });
4143
5821
  };
4144
5822
  var TaskItem = ({ task, theme, depth }) => {
4145
5823
  const checkbox = getCheckbox2(task.state);
4146
5824
  const color = getStateColor2(task.state, theme);
4147
- return /* @__PURE__ */ jsxs16(Box17, { children: [
4148
- /* @__PURE__ */ jsxs16(Text14, { color, children: [
5825
+ return /* @__PURE__ */ jsxs19(Box20, { children: [
5826
+ /* @__PURE__ */ jsxs19(Text17, { color, children: [
4149
5827
  checkbox,
4150
5828
  " "
4151
5829
  ] }),
4152
- depth > 0 && /* @__PURE__ */ jsx23(Text14, { children: " ".repeat(depth) }),
4153
- /* @__PURE__ */ jsx23(
4154
- Text14,
5830
+ depth > 0 && /* @__PURE__ */ jsx26(Text17, { children: " ".repeat(depth) }),
5831
+ /* @__PURE__ */ jsx26(
5832
+ Text17,
4155
5833
  {
4156
5834
  color,
4157
5835
  strikethrough: task.state === "completed",
@@ -4184,7 +5862,7 @@ function getStateColor2(state, theme) {
4184
5862
  }
4185
5863
 
4186
5864
  // src/App.tsx
4187
- import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
5865
+ import { jsx as jsx27, jsxs as jsxs20 } from "react/jsx-runtime";
4188
5866
  var AppContent = () => {
4189
5867
  const {
4190
5868
  showHelp,
@@ -4193,16 +5871,28 @@ var AppContent = () => {
4193
5871
  activePane,
4194
5872
  showThemeDialog,
4195
5873
  showClearTimelineDialog,
5874
+ showSettingsDialog,
5875
+ showRecurringTaskDialog,
5876
+ showRecurringEditDialog,
4196
5877
  showUpdateDialog,
4197
5878
  setShowUpdateDialog,
4198
5879
  updateInfo,
4199
- skipVersion
5880
+ skipVersion,
5881
+ recurringTaskId,
5882
+ setRecurringTaskId,
5883
+ setShowRecurringTaskDialog,
5884
+ recurringEditConfig,
5885
+ setRecurringEditConfig,
5886
+ setShowRecurringEditDialog,
5887
+ tasks,
5888
+ setTasks,
5889
+ pushUndoableAction
4200
5890
  } = useApp();
4201
5891
  const { theme } = useTheme();
4202
5892
  useKeyboardNav();
4203
5893
  const { width, height } = useTerminalSize();
4204
5894
  if (showUpdateDialog && updateInfo) {
4205
- return /* @__PURE__ */ jsx24(
5895
+ return /* @__PURE__ */ jsx27(
4206
5896
  UpdateDialog,
4207
5897
  {
4208
5898
  currentVersion: updateInfo.currentVersion,
@@ -4213,26 +5903,78 @@ var AppContent = () => {
4213
5903
  );
4214
5904
  }
4215
5905
  if (showThemeDialog) {
4216
- return /* @__PURE__ */ jsx24(ThemeDialog, {});
5906
+ return /* @__PURE__ */ jsx27(ThemeDialog, {});
4217
5907
  }
4218
5908
  if (showHelp) {
4219
- return /* @__PURE__ */ jsx24(HelpDialog, {});
5909
+ return /* @__PURE__ */ jsx27(HelpDialog, {});
4220
5910
  }
4221
5911
  if (showClearTimelineDialog) {
4222
- return /* @__PURE__ */ jsx24(ClearTimelineDialog, {});
5912
+ return /* @__PURE__ */ jsx27(ClearTimelineDialog, {});
5913
+ }
5914
+ if (showSettingsDialog) {
5915
+ return /* @__PURE__ */ jsx27(SettingsDialog, {});
5916
+ }
5917
+ if (showRecurringTaskDialog && recurringTaskId) {
5918
+ const handleConfirm = (pattern) => {
5919
+ try {
5920
+ pushUndoableAction("TASK_UPDATE");
5921
+ const updated = taskService.updateTask(tasks, recurringTaskId, {
5922
+ recurrence: pattern
5923
+ });
5924
+ setTasks(updated);
5925
+ setShowRecurringTaskDialog(false);
5926
+ setRecurringTaskId(null);
5927
+ } catch (err) {
5928
+ console.error("Error setting task recurrence:", err);
5929
+ }
5930
+ };
5931
+ const handleCancel = () => {
5932
+ setShowRecurringTaskDialog(false);
5933
+ setRecurringTaskId(null);
5934
+ };
5935
+ return /* @__PURE__ */ jsx27(RecurringTaskDialog, { onConfirm: handleConfirm, onCancel: handleCancel });
5936
+ }
5937
+ if (showRecurringEditDialog && recurringEditConfig) {
5938
+ const handleConfirm = (action) => {
5939
+ logger.log("[App] RecurringEditDialog handleConfirm called", { action, actionType: recurringEditConfig.actionType });
5940
+ if (action === "cancel") {
5941
+ logger.log("[App] User cancelled, closing dialog");
5942
+ setShowRecurringEditDialog(false);
5943
+ setRecurringEditConfig(null);
5944
+ return;
5945
+ }
5946
+ if (action === "this" || action === "all" || action === "from-today") {
5947
+ logger.log("[App] Calling onConfirm callback from TasksPane", { action });
5948
+ recurringEditConfig.onConfirm(action);
5949
+ }
5950
+ logger.log("[App] Scheduling dialog close");
5951
+ setTimeout(() => {
5952
+ logger.log("[App] Closing dialog now");
5953
+ setShowRecurringEditDialog(false);
5954
+ setRecurringEditConfig(null);
5955
+ }, 0);
5956
+ };
5957
+ return /* @__PURE__ */ jsx27(
5958
+ RecurringEditDialog,
5959
+ {
5960
+ taskTitle: recurringEditConfig.taskTitle,
5961
+ actionType: recurringEditConfig.actionType,
5962
+ onConfirm: handleConfirm
5963
+ }
5964
+ );
4223
5965
  }
4224
- return /* @__PURE__ */ jsx24(FullscreenBackground, { backgroundColor: theme.colors.background || "black", children: /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", width, height, padding: 1, backgroundColor: theme.colors.background, children: [
4225
- showOverview ? /* @__PURE__ */ jsx24(OverviewScreen, {}) : /* @__PURE__ */ jsx24(
5966
+ return /* @__PURE__ */ jsx27(FullscreenBackground, { backgroundColor: theme.colors.background || "black", children: /* @__PURE__ */ jsxs20(Box21, { flexDirection: "column", width, height, padding: 1, backgroundColor: theme.colors.background, children: [
5967
+ showOverview ? /* @__PURE__ */ jsx27(OverviewScreen, {}) : /* @__PURE__ */ jsx27(
4226
5968
  ThreeColumnLayout,
4227
5969
  {
4228
- leftPane: /* @__PURE__ */ jsx24(CalendarPane, {}),
4229
- centerPane: /* @__PURE__ */ jsx24(TasksPane, {}),
4230
- rightPane: /* @__PURE__ */ jsx24(TimelinePane, {}),
5970
+ leftPane: /* @__PURE__ */ jsx27(CalendarPane, {}),
5971
+ centerPane: /* @__PURE__ */ jsx27(TasksPane, {}),
5972
+ rightPane: /* @__PURE__ */ jsx27(TimelinePane, {}),
4231
5973
  activePane,
4232
5974
  height: height - 2
4233
5975
  }
4234
5976
  ),
4235
- exitConfirmation && /* @__PURE__ */ jsx24(Box18, { width: "100%", justifyContent: "center", paddingY: 1, children: /* @__PURE__ */ jsxs17(Text15, { backgroundColor: "red", color: "white", bold: true, children: [
5977
+ exitConfirmation && /* @__PURE__ */ jsx27(Box21, { width: "100%", justifyContent: "center", paddingY: 1, children: /* @__PURE__ */ jsxs20(Text18, { backgroundColor: "red", color: "white", bold: true, children: [
4236
5978
  " ",
4237
5979
  "Press Ctrl+C again to exit Epoch",
4238
5980
  " "
@@ -4240,12 +5982,12 @@ var AppContent = () => {
4240
5982
  ] }) });
4241
5983
  };
4242
5984
  var App = () => {
4243
- return /* @__PURE__ */ jsx24(StorageProvider, { children: /* @__PURE__ */ jsx24(ThemeProvider, { initialTheme: "dark", children: /* @__PURE__ */ jsx24(UndoProvider, { children: /* @__PURE__ */ jsx24(AppProvider, { children: /* @__PURE__ */ jsx24(AppContent, {}) }) }) }) });
5985
+ return /* @__PURE__ */ jsx27(StorageProvider, { children: /* @__PURE__ */ jsx27(ThemeProvider, { initialTheme: "dark", children: /* @__PURE__ */ jsx27(UndoProvider, { children: /* @__PURE__ */ jsx27(AppProvider, { children: /* @__PURE__ */ jsx27(AppContent, {}) }) }) }) });
4244
5986
  };
4245
5987
  var App_default = App;
4246
5988
 
4247
5989
  // src/index.tsx
4248
- import { jsx as jsx25 } from "react/jsx-runtime";
5990
+ import { jsx as jsx28 } from "react/jsx-runtime";
4249
5991
  console.clear();
4250
- var app = render(/* @__PURE__ */ jsx25(App_default, {}), { exitOnCtrlC: false });
5992
+ var app = render(/* @__PURE__ */ jsx28(App_default, {}), { exitOnCtrlC: false });
4251
5993
  global.__inkApp = app;