@gamflaz/opencode-todolist 1.0.5 → 1.0.6

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.
@@ -0,0 +1,742 @@
1
+ import { memo as _$memo } from "@opentui/solid";
2
+ import { createTextNode as _$createTextNode } from "@opentui/solid";
3
+ import { effect as _$effect } from "@opentui/solid";
4
+ import { insertNode as _$insertNode } from "@opentui/solid";
5
+ import { insert as _$insert } from "@opentui/solid";
6
+ import { createComponent as _$createComponent } from "@opentui/solid";
7
+ import { setProp as _$setProp } from "@opentui/solid";
8
+ import { use as _$use } from "@opentui/solid";
9
+ import { createElement as _$createElement } from "@opentui/solid";
10
+ import { mkdirSync, watch } from "node:fs";
11
+ import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "solid-js";
12
+ import { supportsImageRendering } from "../icons/image-capabilities";
13
+ import { SvgIcon } from "../icons/svg-icon";
14
+ import { arrowDownSvgIcon, arrowRightSvgIcon, continuePromptSvgIcon, removeSvgIcon } from "../icons/svg-icons";
15
+ import { StatusLabel } from "../schemas/status";
16
+ import { getTodosDirectory, hashSessionID, readTodosSync, writeTodos } from "../storage/todo-store";
17
+ import { getTodoDisplaySummary, hasOpenTodos } from "../todos/display";
18
+ import { TodoTree } from "../todos/todo-tree";
19
+ import { shorten } from "./text";
20
+ import { ERROR_PREFIX, ErrorRowWidth, RowWidth } from "./layout";
21
+
22
+ /** How many open items the Continue prompt lists before collapsing the rest. */
23
+ export const CONTINUE_PROMPT_MAX_ITEMS = 3;
24
+
25
+ /** Per-field caps so one long note cannot flood the prompt. */
26
+ export const CONTINUE_PROMPT_MAX_CONTENT = 60;
27
+ export const CONTINUE_PROMPT_MAX_NOTE = 60;
28
+ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
29
+ const SPINNER_INTERVAL_MS = 100;
30
+ const WATCH_DEBOUNCE_MS = 50;
31
+
32
+ /**
33
+ * Maximum number of todo rows the panel shows before it starts scrolling.
34
+ * Keeping the panel compact protects the composer; scrolling guarantees no
35
+ * row is ever silently dropped.
36
+ */
37
+ export const TODO_PANEL_MAX_ROWS = 8;
38
+
39
+ /** A renderable line: either a task row or its on-demand error row. */
40
+
41
+ const isOpen = status => status !== "completed" && status !== "cancelled";
42
+ function ContinuePrompt(summary) {
43
+ const open = summary?.items.filter(item => isOpen(item.status)) ?? [];
44
+ const shown = open.slice(0, CONTINUE_PROMPT_MAX_ITEMS);
45
+ const hidden = open.length - shown.length;
46
+ const lines = ["Continue the current todolist.", "First call manage_todo_list [read] and reconcile the stored list with this summary."];
47
+ if (summary) {
48
+ lines.push(`Progress: ${summary.completed}/${summary.total} completed.`);
49
+ }
50
+ if (open.length) {
51
+ lines.push("Open items that still need work (short summary):");
52
+ for (const item of shown) {
53
+ const note = item.note ? ` — ${shorten(item.note, CONTINUE_PROMPT_MAX_NOTE)}` : "";
54
+ lines.push(`${item.depth ? " " : ""}- ${item.id} (${StatusLabel[item.status]}) ${shorten(item.content, CONTINUE_PROMPT_MAX_CONTENT)}${note}`);
55
+ }
56
+ if (hidden > 0) lines.push(`... (${hidden} more open item(s))`);
57
+ } else {
58
+ lines.push("No open items are listed. Verify the stored state before stopping.");
59
+ }
60
+ lines.push("Continue the next open item immediately. Keep exactly one item in_progress and update the todo list after verification.");
61
+ return lines.join("\n");
62
+ }
63
+
64
+ /**
65
+ * Flattens rows into renderable lines, inserting an error detail right below
66
+ * an expanded error row (click to toggle).
67
+ */
68
+ export function TodoListEntries(rows, expandedIds) {
69
+ const expanded = new Set(expandedIds);
70
+ const entries = [];
71
+ for (const row of rows) {
72
+ entries.push({
73
+ kind: "row",
74
+ row
75
+ });
76
+ if (row.status === "error" && expanded.has(row.id)) {
77
+ entries.push({
78
+ kind: "error",
79
+ row
80
+ });
81
+ }
82
+ }
83
+ return entries;
84
+ }
85
+
86
+ /**
87
+ * Follow the most advanced active row: the last `in_progress` entry is the
88
+ * task currently being executed. Error lines are skipped.
89
+ */
90
+ export function FindActiveEntryIndex(entries) {
91
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
92
+ const entry = entries[index];
93
+ if (entry.kind === "row" && entry.row.status === "in_progress") {
94
+ return index;
95
+ }
96
+ }
97
+ return -1;
98
+ }
99
+
100
+ /**
101
+ * Scroll offset needed to reveal the active row, or `undefined` when nothing
102
+ * should move (no active row, already visible, or the list fits).
103
+ *
104
+ * Kept pure so the follow behaviour is testable without a live renderer.
105
+ */
106
+ export function NextFollowScrollTop(input) {
107
+ const {
108
+ currentTop,
109
+ activeIndex,
110
+ rowCount,
111
+ viewportHeight
112
+ } = input;
113
+ if (activeIndex < 0) return undefined;
114
+ const viewport = Math.max(1, viewportHeight);
115
+ const maxTop = Math.max(0, rowCount - viewport);
116
+ if (maxTop === 0) return undefined;
117
+ const activeBottom = activeIndex + 1;
118
+ const visibleBottom = currentTop + viewport;
119
+ if (activeIndex >= currentTop && activeBottom <= visibleBottom) {
120
+ return undefined;
121
+ }
122
+ const targetTop = activeIndex >= visibleBottom ? activeBottom - viewport : activeIndex;
123
+ return Math.min(Math.max(0, targetTop), maxTop);
124
+ }
125
+
126
+ /**
127
+ * Presentational, context-free todo list.
128
+ *
129
+ * The panel is deliberately chrome-free: no indicator row, and the horizontal
130
+ * scrollbar stays hidden. Long titles never wrap — they are reached by
131
+ * scrolling left/right, and the vertical list scrolls when it exceeds
132
+ * `TODO_PANEL_MAX_ROWS`. When a row is `in_progress` the panel follows it.
133
+ *
134
+ * `activeIcon` is an accessor so the spinner updates only the icon text node
135
+ * instead of forcing every row object to be rebuilt on each animation frame.
136
+ */
137
+ export function TodoList(props) {
138
+ const rowId = id => `todo-row-${id}`;
139
+ const [box, setBox] = createSignal();
140
+ const entries = createMemo(() => TodoListEntries(props.rows, props.expandedIds ?? []));
141
+ const iconOf = row => row.status === "in_progress" ? props.activeIcon?.() ?? row.icon : row.icon;
142
+ const followActiveRow = el => {
143
+ if (el.isDestroyed) return;
144
+ const target = NextFollowScrollTop({
145
+ currentTop: el.scrollTop,
146
+ activeIndex: FindActiveEntryIndex(entries()),
147
+ rowCount: entries().length,
148
+ viewportHeight: el.viewport.height
149
+ });
150
+ if (target !== undefined) el.scrollTo(target);
151
+ };
152
+ const attach = el => {
153
+ setBox(el);
154
+ el.content.maxWidth = undefined;
155
+ const internalOnSizeChange = el.content.onSizeChange;
156
+ el.content.onSizeChange = () => {
157
+ internalOnSizeChange?.call(el.content);
158
+ followActiveRow(el);
159
+ };
160
+ props.ref?.(el);
161
+ };
162
+
163
+ // `entries()` already changes whenever rows or expanded ids change.
164
+ createEffect(() => {
165
+ entries();
166
+ const el = box();
167
+ if (el) followActiveRow(el);
168
+ });
169
+ const scrollbarOptions = () => props.scrollbarColor ? {
170
+ trackOptions: {
171
+ backgroundColor: props.scrollbarColor,
172
+ foregroundColor: props.scrollbarColor
173
+ }
174
+ } : undefined;
175
+ const isExpanded = id => (props.expandedIds ?? []).includes(id);
176
+ return (() => {
177
+ var _el$ = _$createElement("scrollbox"),
178
+ _el$2 = _$createElement("box");
179
+ _$insertNode(_el$, _el$2);
180
+ _$use(attach, _el$);
181
+ _$setProp(_el$, "width", "100%");
182
+ _$setProp(_el$, "horizontalScrollbarOptions", {
183
+ visible: false
184
+ });
185
+ _$setProp(_el$2, "flexDirection", "column");
186
+ _$setProp(_el$2, "flexShrink", 0);
187
+ _$insert(_el$2, _$createComponent(For, {
188
+ get each() {
189
+ return entries();
190
+ },
191
+ children: entry => entry.kind === "row" ? (() => {
192
+ var _el$3 = _$createElement("box"),
193
+ _el$4 = _$createElement("text"),
194
+ _el$7 = _$createElement("span"),
195
+ _el$8 = _$createTextNode(` `),
196
+ _el$9 = _$createElement("span"),
197
+ _el$0 = _$createElement("span");
198
+ _$insertNode(_el$3, _el$4);
199
+ _$setProp(_el$3, "flexDirection", "row");
200
+ _$setProp(_el$3, "height", 1);
201
+ _$setProp(_el$3, "flexShrink", 0);
202
+ _$setProp(_el$3, "onMouseDown", e => {
203
+ if (e.button === 0 && entry.row.status === "error") {
204
+ e.stopPropagation?.();
205
+ props.onToggleRow?.(entry.row.id);
206
+ }
207
+ });
208
+ _$insertNode(_el$4, _el$7);
209
+ _$insertNode(_el$4, _el$9);
210
+ _$insertNode(_el$4, _el$0);
211
+ _$setProp(_el$4, "wrapMode", "none");
212
+ _$insert(_el$4, _$createComponent(Show, {
213
+ get when() {
214
+ return entry.row.depth > 0;
215
+ },
216
+ get children() {
217
+ var _el$5 = _$createElement("span");
218
+ _$insertNode(_el$5, _$createTextNode(` `));
219
+ return _el$5;
220
+ }
221
+ }), _el$7);
222
+ _$insertNode(_el$7, _el$8);
223
+ _$insert(_el$7, () => iconOf(entry.row), _el$8);
224
+ _$insert(_el$9, () => TodoTree.formatIdPrefix(entry.row.id, entry.row.depth));
225
+ _$insert(_el$0, () => entry.row.content);
226
+ _$insert(_el$4, _$createComponent(Show, {
227
+ get when() {
228
+ return entry.row.status === "error";
229
+ },
230
+ get children() {
231
+ var _el$1 = _$createElement("span"),
232
+ _el$10 = _$createTextNode(` `);
233
+ _$insertNode(_el$1, _el$10);
234
+ _$insert(_el$1, () => isExpanded(entry.row.id) ? "⌄" : "›", null);
235
+ _$effect(_$p => _$setProp(_el$1, "style", {
236
+ fg: props.scrollbarColor
237
+ }, _$p));
238
+ return _el$1;
239
+ }
240
+ }), null);
241
+ _$effect(_p$ => {
242
+ var _v$3 = rowId(entry.row.id),
243
+ _v$4 = RowWidth(entry.row),
244
+ _v$5 = {
245
+ fg: entry.row.iconColor
246
+ },
247
+ _v$6 = {
248
+ fg: entry.row.textColor
249
+ },
250
+ _v$7 = {
251
+ fg: entry.row.textColor,
252
+ strikethrough: entry.row.status === "completed"
253
+ };
254
+ _v$3 !== _p$.e && (_p$.e = _$setProp(_el$3, "id", _v$3, _p$.e));
255
+ _v$4 !== _p$.t && (_p$.t = _$setProp(_el$3, "width", _v$4, _p$.t));
256
+ _v$5 !== _p$.a && (_p$.a = _$setProp(_el$7, "style", _v$5, _p$.a));
257
+ _v$6 !== _p$.o && (_p$.o = _$setProp(_el$9, "style", _v$6, _p$.o));
258
+ _v$7 !== _p$.i && (_p$.i = _$setProp(_el$0, "style", _v$7, _p$.i));
259
+ return _p$;
260
+ }, {
261
+ e: undefined,
262
+ t: undefined,
263
+ a: undefined,
264
+ o: undefined,
265
+ i: undefined
266
+ });
267
+ return _el$3;
268
+ })() : (() => {
269
+ var _el$11 = _$createElement("box"),
270
+ _el$12 = _$createElement("text"),
271
+ _el$13 = _$createElement("span"),
272
+ _el$14 = _$createElement("span");
273
+ _$insertNode(_el$11, _el$12);
274
+ _$setProp(_el$11, "flexDirection", "row");
275
+ _$setProp(_el$11, "height", 1);
276
+ _$setProp(_el$11, "flexShrink", 0);
277
+ _$setProp(_el$11, "onMouseDown", e => {
278
+ if (e.button === 0) {
279
+ e.stopPropagation?.();
280
+ props.onToggleRow?.(entry.row.id);
281
+ }
282
+ });
283
+ _$insertNode(_el$12, _el$13);
284
+ _$insertNode(_el$12, _el$14);
285
+ _$setProp(_el$12, "wrapMode", "none");
286
+ _$insert(_el$13, () => entry.row.depth > 0 ? " " : " ", null);
287
+ _$insert(_el$13, ERROR_PREFIX, null);
288
+ _$insert(_el$14, () => entry.row.content);
289
+ _$effect(_p$ => {
290
+ var _v$8 = `${rowId(entry.row.id)}-note`,
291
+ _v$9 = ErrorRowWidth({
292
+ error: entry.row.content,
293
+ depth: entry.row.depth
294
+ }),
295
+ _v$0 = {
296
+ fg: props.scrollbarColor
297
+ },
298
+ _v$1 = {
299
+ fg: entry.row.textColor
300
+ };
301
+ _v$8 !== _p$.e && (_p$.e = _$setProp(_el$11, "id", _v$8, _p$.e));
302
+ _v$9 !== _p$.t && (_p$.t = _$setProp(_el$11, "width", _v$9, _p$.t));
303
+ _v$0 !== _p$.a && (_p$.a = _$setProp(_el$13, "style", _v$0, _p$.a));
304
+ _v$1 !== _p$.o && (_p$.o = _$setProp(_el$14, "style", _v$1, _p$.o));
305
+ return _p$;
306
+ }, {
307
+ e: undefined,
308
+ t: undefined,
309
+ a: undefined,
310
+ o: undefined
311
+ });
312
+ return _el$11;
313
+ })()
314
+ }));
315
+ _$effect(_p$ => {
316
+ var _v$ = Math.min(entries().length, TODO_PANEL_MAX_ROWS),
317
+ _v$2 = scrollbarOptions();
318
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$, "height", _v$, _p$.e));
319
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$, "verticalScrollbarOptions", _v$2, _p$.t));
320
+ return _p$;
321
+ }, {
322
+ e: undefined,
323
+ t: undefined
324
+ });
325
+ return _el$;
326
+ })();
327
+ }
328
+
329
+ /** Small helper so each hover target does not repeat three lines. */
330
+ function createHover() {
331
+ const [hovered, setHovered] = createSignal(false);
332
+ return {
333
+ hovered,
334
+ onMouseOver: () => setHovered(true),
335
+ onMouseOut: () => setHovered(false)
336
+ };
337
+ }
338
+
339
+ /**
340
+ * Watches the todos directory and calls `onChange` (debounced) when a file
341
+ * belonging to the current session changes. Cleans up with the owner scope.
342
+ */
343
+ function watchSessionTodos(sessionID, onChange) {
344
+ let timer;
345
+ let watcher;
346
+ const hashed = createMemo(() => {
347
+ const sid = sessionID();
348
+ if (!sid) return "";
349
+ try {
350
+ return hashSessionID(sid);
351
+ } catch {
352
+ return "";
353
+ }
354
+ });
355
+ const notify = () => {
356
+ clearTimeout(timer);
357
+ timer = setTimeout(onChange, WATCH_DEBOUNCE_MS);
358
+ };
359
+ const belongsToSession = name => {
360
+ const sid = sessionID();
361
+ if (!sid) return true;
362
+ const hash = hashed();
363
+ return hash !== "" && name.startsWith(hash) || name.startsWith(sid) || name.startsWith(`todos-${sid}-`);
364
+ };
365
+ try {
366
+ const baseDir = getTodosDirectory();
367
+ mkdirSync(baseDir, {
368
+ recursive: true
369
+ });
370
+ watcher = watch(baseDir, {
371
+ recursive: true,
372
+ persistent: false
373
+ }, (_, filename) => {
374
+ if (!filename || belongsToSession(String(filename))) notify();
375
+ });
376
+ } catch (err) {
377
+ console.error("[todos] watcher initialization failed:", err);
378
+ }
379
+ onCleanup(() => {
380
+ clearTimeout(timer);
381
+ watcher?.close();
382
+ });
383
+ }
384
+ export function TodoProgress(props) {
385
+ const {
386
+ context
387
+ } = props;
388
+ const sessionID = () => props.sessionID ?? "";
389
+ const theme = () => context.theme;
390
+ const [refresh, setRefresh] = createSignal(0);
391
+ const [isCollapsed, setIsCollapsed] = createSignal(true);
392
+ const [isContinuePending, setIsContinuePending] = createSignal(false);
393
+ const [animFrame, setAnimFrame] = createSignal(0);
394
+ const [expandedIds, setExpandedIds] = createSignal([]);
395
+ const clearHover = createHover();
396
+ const continueHover = createHover();
397
+
398
+ // Image capability
399
+ const [imageSupported, setImageSupported] = createSignal(supportsImageRendering(context.renderer?.capabilities));
400
+ const onCapabilities = caps => setImageSupported(supportsImageRendering(caps));
401
+ context.renderer.on("capabilities", onCapabilities);
402
+ onCleanup(() => context.renderer.off("capabilities", onCapabilities));
403
+
404
+ // Running state
405
+ const readRunning = () => {
406
+ const sid = sessionID();
407
+ if (!sid) return false;
408
+ try {
409
+ return context.data.session.status(sid) === "running";
410
+ } catch {
411
+ return false;
412
+ }
413
+ };
414
+ const [isRunning, setIsRunning] = createSignal(readRunning());
415
+ createEffect(() => setIsRunning(readRunning()));
416
+ const unsubscribe = context.data.listen(({
417
+ details
418
+ }) => {
419
+ const sid = sessionID();
420
+ switch (details.type) {
421
+ case "session.execution.started":
422
+ case "session.execution.interrupted":
423
+ case "session.execution.succeeded":
424
+ case "session.execution.failed":
425
+ case "session.idle":
426
+ if (!sid || details.data.sessionID === sid) {
427
+ setIsRunning(details.type === "session.execution.started");
428
+ setIsContinuePending(false);
429
+ }
430
+ break;
431
+ case "session.status":
432
+ if (!sid || details.data.sessionID === sid) {
433
+ setIsRunning(details.data.status.type !== "idle");
434
+ setIsContinuePending(false);
435
+ }
436
+ break;
437
+ }
438
+ });
439
+ onCleanup(() => {
440
+ try {
441
+ unsubscribe();
442
+ } catch {}
443
+ });
444
+
445
+ // Todo data
446
+ watchSessionTodos(sessionID, () => setRefresh(v => v + 1));
447
+ const summary = createMemo(() => {
448
+ refresh();
449
+ const sid = sessionID();
450
+ if (!sid) return null;
451
+ try {
452
+ return getTodoDisplaySummary(readTodosSync(sid));
453
+ } catch {
454
+ return null;
455
+ }
456
+ });
457
+
458
+ // Spinner: one interval, only while something is actually running.
459
+ const hasInProgress = createMemo(() => summary()?.items.some(i => i.status === "in_progress") ?? false);
460
+ createEffect(() => {
461
+ if (!(hasInProgress() && isRunning())) return;
462
+ const id = setInterval(() => setAnimFrame(prev => (prev + 1) % SPINNER_FRAMES.length), SPINNER_INTERVAL_MS);
463
+ onCleanup(() => clearInterval(id));
464
+ });
465
+ const activeIcon = () => isRunning() ? SPINNER_FRAMES[animFrame()] : "-";
466
+ const itemIcon = (status, fallback) => status === "in_progress" ? activeIcon() : fallback;
467
+ const activeItem = createMemo(() => {
468
+ const items = summary()?.items;
469
+ if (!items) return null;
470
+ return items.find(i => i.status === "in_progress" && i.depth > 0) ?? items.find(i => i.status === "in_progress") ?? null;
471
+ });
472
+
473
+ // Colors
474
+ const getStatusColor = status => {
475
+ const t = theme();
476
+ switch (status) {
477
+ case "in_progress":
478
+ return isRunning() ? t.text.action?.primary?.base ?? t.text.feedback.info?.base ?? t.text.base : t.text.muted;
479
+ case "completed":
480
+ return t.text.feedback.success.base;
481
+ case "error":
482
+ case "cancelled":
483
+ return t.text.feedback.error.base;
484
+ default:
485
+ return t.text.muted;
486
+ }
487
+ };
488
+ const getTextColor = status => status === "completed" || status === "cancelled" || status === "error" ? theme().text.muted : theme().text.base;
489
+
490
+ /**
491
+ * Rows are built only while the panel is expanded and do NOT depend on the
492
+ * spinner frame, so the row objects stay stable between animation ticks.
493
+ */
494
+ const rows = createMemo(() => {
495
+ if (isCollapsed()) return [];
496
+ return (summary()?.items ?? []).map(item => ({
497
+ id: item.id,
498
+ content: item.content,
499
+ icon: item.icon,
500
+ iconColor: getStatusColor(item.status),
501
+ textColor: getTextColor(item.status),
502
+ depth: item.depth,
503
+ status: item.status
504
+ }));
505
+ });
506
+
507
+ // Actions
508
+ const toggleRowNote = id => setExpandedIds(current => current.includes(id) ? current.filter(entry => entry !== id) : [...current, id]);
509
+ const continueTodos = async () => {
510
+ if (isRunning() || isContinuePending()) return;
511
+ const sid = sessionID();
512
+ if (!sid) return;
513
+ setIsContinuePending(true);
514
+ try {
515
+ await context.client.session.prompt({
516
+ sessionID: sid,
517
+ text: ContinuePrompt(summary())
518
+ });
519
+ } catch (error) {
520
+ setIsContinuePending(false);
521
+ console.error("[todos] continue failed:", error);
522
+ }
523
+ };
524
+ const clearTodos = async () => {
525
+ const sid = sessionID();
526
+ if (!sid) return;
527
+ try {
528
+ await writeTodos(sid, []);
529
+ setRefresh(v => v + 1);
530
+ } catch (error) {
531
+ console.error("[todos] clear failed:", error);
532
+ }
533
+ };
534
+ const hoverColor = (hovered, active) => hovered ? active : theme().text.muted;
535
+ return _$createComponent(Show, {
536
+ get when() {
537
+ return summary();
538
+ },
539
+ children: data => (() => {
540
+ var _el$15 = _$createElement("box"),
541
+ _el$16 = _$createElement("box"),
542
+ _el$17 = _$createElement("box"),
543
+ _el$18 = _$createElement("box"),
544
+ _el$19 = _$createElement("box"),
545
+ _el$20 = _$createElement("text"),
546
+ _el$21 = _$createElement("span"),
547
+ _el$23 = _$createElement("span"),
548
+ _el$27 = _$createElement("box");
549
+ _$insertNode(_el$15, _el$16);
550
+ _$setProp(_el$15, "flexDirection", "column");
551
+ _$setProp(_el$15, "border", true);
552
+ _$setProp(_el$15, "borderStyle", "rounded");
553
+ _$setProp(_el$15, "paddingLeft", 1);
554
+ _$setProp(_el$15, "paddingRight", 1);
555
+ _$insertNode(_el$16, _el$17);
556
+ _$insertNode(_el$16, _el$27);
557
+ _$setProp(_el$16, "flexDirection", "row");
558
+ _$setProp(_el$16, "justifyContent", "space-between");
559
+ _$insertNode(_el$17, _el$18);
560
+ _$insertNode(_el$17, _el$19);
561
+ _$setProp(_el$17, "flexDirection", "row");
562
+ _$setProp(_el$17, "flexGrow", 1);
563
+ _$setProp(_el$17, "onMouseDown", e => {
564
+ if (e.button === 0) {
565
+ e.stopPropagation?.();
566
+ setIsCollapsed(v => !v);
567
+ }
568
+ });
569
+ _$setProp(_el$18, "flexDirection", "row");
570
+ _$setProp(_el$18, "width", 2);
571
+ _$setProp(_el$18, "height", 1);
572
+ _$setProp(_el$18, "marginRight", 1);
573
+ _$setProp(_el$18, "alignItems", "flex-start");
574
+ _$insert(_el$18, _$createComponent(SvgIcon, {
575
+ get source() {
576
+ return isCollapsed() ? arrowRightSvgIcon : arrowDownSvgIcon;
577
+ },
578
+ get color() {
579
+ return theme().text.muted;
580
+ },
581
+ get fallback() {
582
+ return isCollapsed() ? "▶" : "▼";
583
+ },
584
+ width: 2,
585
+ height: 1
586
+ }));
587
+ _$insertNode(_el$19, _el$20);
588
+ _$setProp(_el$19, "flexDirection", "row");
589
+ _$setProp(_el$19, "flexGrow", 1);
590
+ _$setProp(_el$19, "flexShrink", 1);
591
+ _$setProp(_el$19, "overflow", "hidden");
592
+ _$setProp(_el$19, "marginRight", 1);
593
+ _$insertNode(_el$20, _el$21);
594
+ _$insertNode(_el$20, _el$23);
595
+ _$setProp(_el$20, "wrapMode", "none");
596
+ _$insertNode(_el$21, _$createTextNode(`Todos `));
597
+ _$insert(_el$23, () => `(${data().completed}/${data().total})`);
598
+ _$insert(_el$20, _$createComponent(Show, {
599
+ get when() {
600
+ return _$memo(() => !!isCollapsed())() && activeItem();
601
+ },
602
+ children: active => [(() => {
603
+ var _el$29 = _$createElement("span");
604
+ _$insertNode(_el$29, _$createTextNode(` `));
605
+ _$effect(_$p => _$setProp(_el$29, "style", {
606
+ fg: theme().text.muted
607
+ }, _$p));
608
+ return _el$29;
609
+ })(), (() => {
610
+ var _el$31 = _$createElement("span"),
611
+ _el$32 = _$createTextNode(` `);
612
+ _$insertNode(_el$31, _el$32);
613
+ _$insert(_el$31, () => itemIcon(active().status, active().icon), _el$32);
614
+ _$effect(_$p => _$setProp(_el$31, "style", {
615
+ fg: getStatusColor(active().status)
616
+ }, _$p));
617
+ return _el$31;
618
+ })(), (() => {
619
+ var _el$33 = _$createElement("span");
620
+ _$insert(_el$33, () => TodoTree.formatIdPrefix(active().id, active().depth), null);
621
+ _$insert(_el$33, () => active().content, null);
622
+ _$effect(_$p => _$setProp(_el$33, "style", {
623
+ fg: getTextColor(active().status)
624
+ }, _$p));
625
+ return _el$33;
626
+ })()]
627
+ }), null);
628
+ _$insert(_el$16, _$createComponent(Show, {
629
+ get when() {
630
+ return _$memo(() => !!(hasOpenTodos(data().items) && !isRunning()))() && !isContinuePending();
631
+ },
632
+ get children() {
633
+ var _el$24 = _$createElement("box"),
634
+ _el$25 = _$createElement("text");
635
+ _$insertNode(_el$24, _el$25);
636
+ _$setProp(_el$24, "flexDirection", "row");
637
+ _$setProp(_el$24, "gap", 1);
638
+ _$setProp(_el$24, "marginRight", 3);
639
+ _$setProp(_el$24, "onMouseDown", e => {
640
+ if (e.button === 0) {
641
+ e.stopPropagation();
642
+ void continueTodos();
643
+ }
644
+ });
645
+ _$insert(_el$24, _$createComponent(SvgIcon, {
646
+ source: continuePromptSvgIcon,
647
+ get color() {
648
+ return hoverColor(continueHover.hovered(), theme().text.action.primary.base);
649
+ },
650
+ fallback: "\u21BB",
651
+ width: 2,
652
+ height: 1
653
+ }), _el$25);
654
+ _$insertNode(_el$25, _$createTextNode(`Continue`));
655
+ _$setProp(_el$25, "wrapMode", "none");
656
+ _$effect(_p$ => {
657
+ var _v$10 = continueHover.onMouseOver,
658
+ _v$11 = continueHover.onMouseOut,
659
+ _v$12 = hoverColor(continueHover.hovered(), theme().text.action.primary.base);
660
+ _v$10 !== _p$.e && (_p$.e = _$setProp(_el$24, "onMouseOver", _v$10, _p$.e));
661
+ _v$11 !== _p$.t && (_p$.t = _$setProp(_el$24, "onMouseOut", _v$11, _p$.t));
662
+ _v$12 !== _p$.a && (_p$.a = _$setProp(_el$25, "fg", _v$12, _p$.a));
663
+ return _p$;
664
+ }, {
665
+ e: undefined,
666
+ t: undefined,
667
+ a: undefined
668
+ });
669
+ return _el$24;
670
+ }
671
+ }), _el$27);
672
+ _$setProp(_el$27, "flexDirection", "row");
673
+ _$setProp(_el$27, "onMouseDown", e => {
674
+ if (e.button === 0) {
675
+ e.stopPropagation();
676
+ void clearTodos();
677
+ }
678
+ });
679
+ _$insert(_el$27, _$createComponent(SvgIcon, {
680
+ get color() {
681
+ return hoverColor(clearHover.hovered(), theme().text.feedback.error.base);
682
+ },
683
+ source: removeSvgIcon,
684
+ fallback: "\u2716",
685
+ height: 1,
686
+ width: 2
687
+ }));
688
+ _$insert(_el$15, _$createComponent(Show, {
689
+ get when() {
690
+ return !isCollapsed();
691
+ },
692
+ get children() {
693
+ var _el$28 = _$createElement("box");
694
+ _$setProp(_el$28, "flexDirection", "column");
695
+ _$setProp(_el$28, "paddingTop", 1);
696
+ _$setProp(_el$28, "paddingLeft", 0.5);
697
+ _$insert(_el$28, _$createComponent(TodoList, {
698
+ get rows() {
699
+ return rows();
700
+ },
701
+ get expandedIds() {
702
+ return expandedIds();
703
+ },
704
+ activeIcon: activeIcon,
705
+ onToggleRow: toggleRowNote
706
+ }));
707
+ return _el$28;
708
+ }
709
+ }), null);
710
+ _$effect(_p$ => {
711
+ var _v$13 = theme().border.base,
712
+ _v$14 = imageSupported() ? 0 : 1,
713
+ _v$15 = {
714
+ fg: theme().text.base
715
+ },
716
+ _v$16 = {
717
+ fg: theme().text.muted
718
+ },
719
+ _v$17 = !imageSupported() ? 1.5 : 0,
720
+ _v$18 = clearHover.onMouseOver,
721
+ _v$19 = clearHover.onMouseOut;
722
+ _v$13 !== _p$.e && (_p$.e = _$setProp(_el$15, "borderColor", _v$13, _p$.e));
723
+ _v$14 !== _p$.t && (_p$.t = _$setProp(_el$18, "marginLeft", _v$14, _p$.t));
724
+ _v$15 !== _p$.a && (_p$.a = _$setProp(_el$21, "style", _v$15, _p$.a));
725
+ _v$16 !== _p$.o && (_p$.o = _$setProp(_el$23, "style", _v$16, _p$.o));
726
+ _v$17 !== _p$.i && (_p$.i = _$setProp(_el$27, "marginRight", _v$17, _p$.i));
727
+ _v$18 !== _p$.n && (_p$.n = _$setProp(_el$27, "onMouseOver", _v$18, _p$.n));
728
+ _v$19 !== _p$.s && (_p$.s = _$setProp(_el$27, "onMouseOut", _v$19, _p$.s));
729
+ return _p$;
730
+ }, {
731
+ e: undefined,
732
+ t: undefined,
733
+ a: undefined,
734
+ o: undefined,
735
+ i: undefined,
736
+ n: undefined,
737
+ s: undefined
738
+ });
739
+ return _el$15;
740
+ })()
741
+ });
742
+ }