tmux-ide 1.2.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/bin/cli.js +4 -216
  2. package/bin/cli.ts +536 -0
  3. package/package.json +33 -14
  4. package/src/__tests__/support.ts +186 -0
  5. package/src/{attach.js → attach.ts} +7 -4
  6. package/src/cli.test.ts +314 -0
  7. package/src/command-center/discovery.test.ts +265 -0
  8. package/src/command-center/discovery.ts +296 -0
  9. package/src/command-center/index.ts +31 -0
  10. package/src/command-center/schemas.ts +57 -0
  11. package/src/command-center/server.test.ts +818 -0
  12. package/src/command-center/server.ts +1426 -0
  13. package/src/config-cli.test.ts +86 -0
  14. package/src/config.test.ts +205 -0
  15. package/src/config.ts +313 -0
  16. package/src/detect.test.ts +142 -0
  17. package/src/{detect.js → detect.ts} +31 -15
  18. package/src/dispatch.ts +102 -0
  19. package/src/doctor.test.ts +119 -0
  20. package/src/doctor.ts +167 -0
  21. package/src/init.test.ts +101 -0
  22. package/src/init.ts +144 -0
  23. package/src/inspect.test.ts +67 -0
  24. package/src/{inspect.js → inspect.ts} +95 -10
  25. package/src/integration.test.ts.skip +276 -0
  26. package/src/js-yaml.d.ts +10 -0
  27. package/src/launch.test.ts +222 -0
  28. package/src/launch.ts +494 -0
  29. package/src/lib/auth/auth-service.test.ts +178 -0
  30. package/src/lib/auth/auth-service.ts +227 -0
  31. package/src/lib/auth/middleware.test.ts +119 -0
  32. package/src/lib/auth/middleware.ts +56 -0
  33. package/src/lib/auth/types.ts +13 -0
  34. package/src/lib/authorship.test.ts +242 -0
  35. package/src/lib/authorship.ts +280 -0
  36. package/src/lib/cast/recorder.test.ts +127 -0
  37. package/src/lib/cast/recorder.ts +148 -0
  38. package/src/lib/daemon-watchdog.ts +102 -0
  39. package/src/lib/daemon.ts +417 -0
  40. package/src/lib/dot-path.test.ts +66 -0
  41. package/src/lib/dot-path.ts +17 -0
  42. package/src/lib/errors.ts +44 -0
  43. package/src/lib/event-log.test.ts +189 -0
  44. package/src/lib/event-log.ts +237 -0
  45. package/src/lib/github-pr.ts +182 -0
  46. package/src/lib/hq/client.test.ts +154 -0
  47. package/src/lib/hq/client.ts +142 -0
  48. package/src/lib/hq/mdns.test.ts +54 -0
  49. package/src/lib/hq/mdns.ts +88 -0
  50. package/src/lib/hq/registry.test.ts +237 -0
  51. package/src/lib/hq/registry.ts +145 -0
  52. package/src/lib/hq/types.ts +44 -0
  53. package/src/lib/ipc/socket-protocol.test.ts +126 -0
  54. package/src/lib/ipc/socket-protocol.ts +324 -0
  55. package/src/lib/launch-plan.test.ts +119 -0
  56. package/src/lib/launch-plan.ts +90 -0
  57. package/src/lib/log.test.ts +39 -0
  58. package/src/lib/log.ts +71 -0
  59. package/src/lib/metrics.test.ts +213 -0
  60. package/src/lib/metrics.ts +345 -0
  61. package/src/lib/orchestrator.test.ts +2358 -0
  62. package/src/lib/orchestrator.ts +1792 -0
  63. package/src/lib/{output.js → output.ts} +18 -10
  64. package/src/lib/plan-store.test.ts +148 -0
  65. package/src/lib/plan-store.ts +131 -0
  66. package/src/lib/research.test.ts +208 -0
  67. package/src/lib/research.ts +475 -0
  68. package/src/lib/round-trip.test.ts +341 -0
  69. package/src/lib/session-monitor.test.ts +152 -0
  70. package/src/lib/session-monitor.ts +282 -0
  71. package/src/lib/session-options.test.ts +146 -0
  72. package/src/lib/{session-options.js → session-options.ts} +11 -6
  73. package/src/lib/shell.test.ts +20 -0
  74. package/src/lib/shell.ts +8 -0
  75. package/src/lib/sizes.test.ts +73 -0
  76. package/src/lib/{sizes.js → sizes.ts} +4 -4
  77. package/src/lib/skill-registry.test.ts +134 -0
  78. package/src/lib/skill-registry.ts +86 -0
  79. package/src/lib/slugify.test.ts +35 -0
  80. package/src/lib/slugify.ts +10 -0
  81. package/src/lib/task-store.ts +352 -0
  82. package/src/lib/tmux.test.ts +500 -0
  83. package/src/lib/tmux.ts +372 -0
  84. package/src/lib/token-tracker.test.ts +102 -0
  85. package/src/lib/token-tracker.ts +70 -0
  86. package/src/lib/tunnels/cloudflare.test.ts +73 -0
  87. package/src/lib/tunnels/cloudflare.ts +254 -0
  88. package/src/lib/tunnels/manager.test.ts +63 -0
  89. package/src/lib/tunnels/manager.ts +132 -0
  90. package/src/lib/tunnels/ngrok.test.ts +94 -0
  91. package/src/lib/tunnels/ngrok.ts +261 -0
  92. package/src/lib/tunnels/tailscale.test.ts +103 -0
  93. package/src/lib/tunnels/tailscale.ts +1048 -0
  94. package/src/lib/tunnels/types.ts +44 -0
  95. package/src/lib/validation.test.ts +289 -0
  96. package/src/lib/validation.ts +123 -0
  97. package/src/lib/webhook.ts +51 -0
  98. package/src/lib/workflow-store.ts +209 -0
  99. package/src/lib/ws-v3/protocol.test.ts +127 -0
  100. package/src/lib/ws-v3/protocol.ts +160 -0
  101. package/src/lib/yaml-io.test.ts +125 -0
  102. package/src/lib/{yaml-io.js → yaml-io.ts} +5 -4
  103. package/src/ls.test.ts +76 -0
  104. package/src/{ls.js → ls.ts} +4 -4
  105. package/src/metrics-cli.ts +177 -0
  106. package/src/notify.ts +85 -0
  107. package/src/orchestrator-status.ts +228 -0
  108. package/src/plan.ts +117 -0
  109. package/src/postinstall.test.ts +79 -0
  110. package/src/remote.test.ts +115 -0
  111. package/src/remote.ts +148 -0
  112. package/src/restart.ts +24 -0
  113. package/src/schemas/domain.ts +422 -0
  114. package/src/schemas/ide-config.ts +148 -0
  115. package/src/schemas/index.ts +53 -0
  116. package/src/send.test.ts +70 -0
  117. package/src/send.ts +181 -0
  118. package/src/skill.test.ts +114 -0
  119. package/src/skill.ts +138 -0
  120. package/src/status.ts +66 -0
  121. package/src/stop.test.ts +176 -0
  122. package/src/stop.ts +47 -0
  123. package/src/task.test.ts +1466 -0
  124. package/src/task.ts +1143 -0
  125. package/src/tunnel.test.ts +80 -0
  126. package/src/tunnel.ts +133 -0
  127. package/src/types.ts +15 -0
  128. package/src/ui/index.ts +32 -0
  129. package/src/ui/terminal/index.ts +9 -0
  130. package/src/ui/types.ts +91 -0
  131. package/src/ui/web/base.css +80 -0
  132. package/src/ui/web/components/Box.tsx +59 -0
  133. package/src/ui/web/components/Input.tsx +32 -0
  134. package/src/ui/web/components/ScrollBox.tsx +60 -0
  135. package/src/ui/web/components/Text.tsx +28 -0
  136. package/src/ui/web/hooks.ts +106 -0
  137. package/src/ui/web/index.ts +27 -0
  138. package/src/ui/web/render.ts +77 -0
  139. package/src/ui/web/utils/color.test.ts +93 -0
  140. package/src/ui/web/utils/color.ts +27 -0
  141. package/src/validate.test.ts +385 -0
  142. package/src/validate.ts +217 -0
  143. package/src/widgets/changes/index.tsx +576 -0
  144. package/src/widgets/config/index.tsx +440 -0
  145. package/src/widgets/costs/index.tsx +216 -0
  146. package/src/widgets/explorer/breadcrumbs.tsx +77 -0
  147. package/src/widgets/explorer/footer.tsx +20 -0
  148. package/src/widgets/explorer/header.tsx +23 -0
  149. package/src/widgets/explorer/index.tsx +408 -0
  150. package/src/widgets/explorer/tree-model.test.ts +214 -0
  151. package/src/widgets/explorer/tree-model.ts +103 -0
  152. package/src/widgets/explorer/tree.tsx +156 -0
  153. package/src/widgets/lib/config-model.ts +116 -0
  154. package/src/widgets/lib/files.test.ts +103 -0
  155. package/src/widgets/lib/files.ts +88 -0
  156. package/src/widgets/lib/git.test.ts +151 -0
  157. package/src/widgets/lib/git.ts +88 -0
  158. package/src/widgets/lib/pane-comms.test.ts +178 -0
  159. package/src/widgets/lib/pane-comms.ts +192 -0
  160. package/src/widgets/lib/theme.ts +151 -0
  161. package/src/widgets/lib/watcher.ts +50 -0
  162. package/src/widgets/mission-control/activity-feed.tsx +59 -0
  163. package/src/widgets/mission-control/agent-panel.tsx +132 -0
  164. package/src/widgets/mission-control/command-bar.tsx +63 -0
  165. package/src/widgets/mission-control/index.tsx +751 -0
  166. package/src/widgets/mission-control/task-panel.tsx +238 -0
  167. package/src/widgets/preview/index.tsx +415 -0
  168. package/src/widgets/resolve.ts +40 -0
  169. package/src/widgets/setup/agent-naming.tsx +112 -0
  170. package/src/widgets/setup/config-tree.tsx +238 -0
  171. package/src/widgets/setup/detect-panel.tsx +72 -0
  172. package/src/widgets/setup/field-editor.tsx +265 -0
  173. package/src/widgets/setup/footer.tsx +107 -0
  174. package/src/widgets/setup/index.tsx +340 -0
  175. package/src/widgets/setup/layout-picker.tsx +96 -0
  176. package/src/widgets/setup/orchestrator-panel.tsx +200 -0
  177. package/src/widgets/setup/review-panel.tsx +140 -0
  178. package/src/widgets/setup/setup-model.test.ts +303 -0
  179. package/src/widgets/setup/setup-model.ts +188 -0
  180. package/src/widgets/tasks/index.tsx +129 -0
  181. package/src/widgets/tasks/task-detail.tsx +309 -0
  182. package/src/widgets/tasks/task-form.tsx +193 -0
  183. package/src/widgets/tasks/task-list.tsx +205 -0
  184. package/src/widgets/tasks/task-model.test.ts +473 -0
  185. package/src/widgets/tasks/task-model.ts +157 -0
  186. package/templates/AGENTS.md +20 -0
  187. package/templates/missions.yml +41 -0
  188. package/templates/skills/backend.md +30 -0
  189. package/templates/skills/frontend.md +30 -0
  190. package/templates/skills/general-worker.md +30 -0
  191. package/templates/skills/researcher.md +69 -0
  192. package/templates/skills/reviewer.md +30 -0
  193. package/src/config.js +0 -341
  194. package/src/doctor.js +0 -91
  195. package/src/init.js +0 -73
  196. package/src/launch.js +0 -245
  197. package/src/lib/dot-path.js +0 -16
  198. package/src/lib/errors.js +0 -35
  199. package/src/lib/launch-plan.js +0 -49
  200. package/src/lib/session-monitor.js +0 -179
  201. package/src/lib/tmux.js +0 -237
  202. package/src/restart.js +0 -16
  203. package/src/status.js +0 -35
  204. package/src/stop.js +0 -25
  205. package/src/validate.js +0 -154
@@ -0,0 +1,193 @@
1
+ import { createSignal, onMount } from "solid-js";
2
+ import { RGBA, TextAttributes, type InputRenderable } from "@opentui/core";
3
+ import { useKeyboard } from "@opentui/solid";
4
+ import { createTask, updateTaskField, loadTasks, type Task } from "./task-model.ts";
5
+ import type { WidgetTheme, RGBA as RGBAType } from "../lib/theme.ts";
6
+
7
+ function toRGBA(c: RGBAType): RGBA {
8
+ return RGBA.fromInts(c.r, c.g, c.b, c.a);
9
+ }
10
+
11
+ interface TaskFormProps {
12
+ mode: "create" | "edit";
13
+ task?: Task;
14
+ dir: string;
15
+ onSave: (task: Task) => void;
16
+ onCancel: () => void;
17
+ theme: WidgetTheme;
18
+ }
19
+
20
+ export function TaskForm(props: TaskFormProps) {
21
+ const [title, setTitle] = createSignal(props.task?.title ?? "");
22
+ const [description, setDescription] = createSignal(props.task?.description ?? "");
23
+ const [priority, setPriority] = createSignal(props.task?.priority ?? 2);
24
+ const [activeField, setActiveField] = createSignal<"title" | "description">("title");
25
+
26
+ let titleRef: InputRenderable | undefined;
27
+ let descRef: InputRenderable | undefined;
28
+
29
+ const theme = props.theme;
30
+
31
+ onMount(() => {
32
+ setTimeout(() => titleRef?.focus(), 50);
33
+ });
34
+
35
+ useKeyboard((evt) => {
36
+ if (evt.name === "escape") {
37
+ props.onCancel();
38
+ evt.preventDefault();
39
+ } else if (evt.name === "tab") {
40
+ if (activeField() === "title") {
41
+ setActiveField("description");
42
+ setTimeout(() => descRef?.focus(), 10);
43
+ } else {
44
+ setActiveField("title");
45
+ setTimeout(() => titleRef?.focus(), 10);
46
+ }
47
+ evt.preventDefault();
48
+ } else if (evt.ctrl && evt.name === "s") {
49
+ save();
50
+ evt.preventDefault();
51
+ }
52
+ });
53
+
54
+ function save() {
55
+ if (!title()) return;
56
+
57
+ if (props.mode === "create") {
58
+ const tasks = loadTasks(props.dir);
59
+ const newTask = createTask(props.dir, tasks);
60
+ updateTaskField(props.dir, newTask.id, {
61
+ title: title(),
62
+ description: description(),
63
+ priority: priority(),
64
+ });
65
+ props.onSave({
66
+ ...newTask,
67
+ title: title(),
68
+ description: description(),
69
+ priority: priority(),
70
+ });
71
+ } else if (props.task) {
72
+ updateTaskField(props.dir, props.task.id, {
73
+ title: title(),
74
+ description: description(),
75
+ priority: priority(),
76
+ });
77
+ props.onSave({
78
+ ...props.task,
79
+ title: title(),
80
+ description: description(),
81
+ priority: priority(),
82
+ });
83
+ }
84
+ }
85
+
86
+ return (
87
+ <box paddingLeft={1} paddingRight={1}>
88
+ {/* Header */}
89
+ <box flexShrink={0} flexDirection="row" justifyContent="space-between" paddingBottom={1}>
90
+ <text fg={toRGBA(theme.fg)} attributes={TextAttributes.BOLD}>
91
+ {props.mode === "create" ? "New Task" : "Edit Task"}
92
+ </text>
93
+ <text fg={toRGBA(theme.fgMuted)} onMouseUp={() => props.onCancel()}>
94
+ Esc:cancel
95
+ </text>
96
+ </box>
97
+
98
+ {/* Title field */}
99
+ <box flexShrink={0} paddingBottom={1}>
100
+ <text fg={toRGBA(activeField() === "title" ? theme.accent : theme.fgMuted)}>Title</text>
101
+ <input
102
+ value={title()}
103
+ placeholder="What needs to be done?"
104
+ onInput={(v: string) => setTitle(v)}
105
+ focusedBackgroundColor={toRGBA(theme.selected)}
106
+ cursorColor={toRGBA(theme.accent)}
107
+ focusedTextColor={toRGBA(theme.fg)}
108
+ onMouseDown={() => {
109
+ setActiveField("title");
110
+ setTimeout(() => titleRef?.focus(), 10);
111
+ }}
112
+ ref={(r: InputRenderable) => {
113
+ titleRef = r;
114
+ }}
115
+ />
116
+ </box>
117
+
118
+ {/* Description field */}
119
+ <box flexShrink={0} paddingBottom={1}>
120
+ <text fg={toRGBA(activeField() === "description" ? theme.accent : theme.fgMuted)}>
121
+ Description
122
+ </text>
123
+ <input
124
+ value={description()}
125
+ placeholder="Add details..."
126
+ onInput={(v: string) => setDescription(v)}
127
+ focusedBackgroundColor={toRGBA(theme.selected)}
128
+ cursorColor={toRGBA(theme.accent)}
129
+ focusedTextColor={toRGBA(theme.fg)}
130
+ onMouseDown={() => {
131
+ setActiveField("description");
132
+ setTimeout(() => descRef?.focus(), 10);
133
+ }}
134
+ ref={(r: InputRenderable) => {
135
+ descRef = r;
136
+ }}
137
+ />
138
+ </box>
139
+
140
+ {/* Priority selector */}
141
+ <box flexShrink={0} paddingBottom={1}>
142
+ <text fg={toRGBA(theme.fgMuted)}>Priority</text>
143
+ <box flexDirection="row" gap={2} paddingTop={0}>
144
+ <text
145
+ fg={toRGBA(priority() === 1 ? theme.gitDeleted : theme.fgMuted)}
146
+ onMouseUp={() => setPriority(1)}
147
+ >
148
+ {priority() === 1 ? "[***]" : " *** "}
149
+ </text>
150
+ <text
151
+ fg={toRGBA(priority() === 2 ? theme.gitModified : theme.fgMuted)}
152
+ onMouseUp={() => setPriority(2)}
153
+ >
154
+ {priority() === 2 ? "[** ]" : " ** "}
155
+ </text>
156
+ <text
157
+ fg={toRGBA(priority() === 3 ? theme.accent : theme.fgMuted)}
158
+ onMouseUp={() => setPriority(3)}
159
+ >
160
+ {priority() === 3 ? "[* ]" : " * "}
161
+ </text>
162
+ <text
163
+ fg={toRGBA(priority() === 4 ? theme.fg : theme.fgMuted)}
164
+ onMouseUp={() => setPriority(4)}
165
+ >
166
+ {priority() === 4 ? "[ ]" : " "}
167
+ </text>
168
+ </box>
169
+ </box>
170
+
171
+ {/* Spacer */}
172
+ <box flexGrow={1} />
173
+
174
+ {/* Footer */}
175
+ <box flexShrink={0}>
176
+ <box flexShrink={0} height={1}>
177
+ <text fg={toRGBA(theme.border)} wrapMode="none">
178
+ {"─".repeat(40)}
179
+ </text>
180
+ </box>
181
+ <box flexDirection="row" gap={2}>
182
+ <text fg={toRGBA(theme.fgMuted)}>Tab:next field</text>
183
+ <text fg={toRGBA(theme.fgMuted)} onMouseUp={() => save()}>
184
+ Ctrl+S:save
185
+ </text>
186
+ <text fg={toRGBA(theme.fgMuted)} onMouseUp={() => props.onCancel()}>
187
+ Esc:cancel
188
+ </text>
189
+ </box>
190
+ </box>
191
+ </box>
192
+ );
193
+ }
@@ -0,0 +1,205 @@
1
+ import { createSignal, createMemo, For, Show } from "solid-js";
2
+ import { RGBA, TextAttributes } from "@opentui/core";
3
+ import { useKeyboard } from "@opentui/solid";
4
+ import {
5
+ groupTasks,
6
+ flattenTaskList,
7
+ isBlocked,
8
+ type Task,
9
+ type TaskStatus,
10
+ type FlatItem,
11
+ } from "./task-model.ts";
12
+ import type { WidgetTheme, RGBA as RGBAType } from "../lib/theme.ts";
13
+
14
+ function toRGBA(c: RGBAType): RGBA {
15
+ return RGBA.fromInts(c.r, c.g, c.b, c.a);
16
+ }
17
+
18
+ function statusColor(status: TaskStatus, theme: WidgetTheme): RGBAType {
19
+ switch (status) {
20
+ case "in-progress":
21
+ return theme.gitModified;
22
+ case "todo":
23
+ return theme.fgMuted;
24
+ case "review":
25
+ return theme.diffHunk;
26
+ case "done":
27
+ return theme.gitAdded;
28
+ }
29
+ }
30
+
31
+ function statusLabel(status: TaskStatus): string {
32
+ switch (status) {
33
+ case "in-progress":
34
+ return "DOING";
35
+ case "todo":
36
+ return "TODO";
37
+ case "review":
38
+ return "REVIEW";
39
+ case "done":
40
+ return "DONE";
41
+ }
42
+ }
43
+
44
+ function priorityDots(priority: number): string {
45
+ switch (priority) {
46
+ case 1:
47
+ return "***";
48
+ case 2:
49
+ return "** ";
50
+ case 3:
51
+ return "* ";
52
+ default:
53
+ return " ";
54
+ }
55
+ }
56
+
57
+ interface TaskListProps {
58
+ tasks: Task[];
59
+ onSelect: (task: Task) => void;
60
+ onNew: () => void;
61
+ onQuit: () => void;
62
+ theme: WidgetTheme;
63
+ }
64
+
65
+ export function TaskList(props: TaskListProps) {
66
+ const [selected, setSelected] = createSignal(0);
67
+ const [inputMode, setInputMode] = createSignal<"keyboard" | "mouse">("keyboard");
68
+
69
+ const groups = createMemo(() => groupTasks(props.tasks));
70
+ const flatList = createMemo(() => flattenTaskList(groups()));
71
+
72
+ function selectedTask(): Task | null {
73
+ const item = flatList()[selected()];
74
+ return item?.kind === "task" ? item.task : null;
75
+ }
76
+
77
+ function moveSelection(direction: number) {
78
+ const list = flatList();
79
+ let next = selected() + direction;
80
+ while (next >= 0 && next < list.length && list[next]?.kind === "header") {
81
+ next += direction;
82
+ }
83
+ if (next >= 0 && next < list.length) setSelected(next);
84
+ }
85
+
86
+ useKeyboard((evt) => {
87
+ setInputMode("keyboard");
88
+ if (evt.name === "up" || evt.name === "k") {
89
+ moveSelection(-1);
90
+ evt.preventDefault();
91
+ } else if (evt.name === "down" || evt.name === "j") {
92
+ moveSelection(1);
93
+ evt.preventDefault();
94
+ } else if (evt.name === "return" || evt.name === "l" || evt.name === "right") {
95
+ const task = selectedTask();
96
+ if (task) props.onSelect(task);
97
+ evt.preventDefault();
98
+ } else if (evt.name === "n") {
99
+ props.onNew();
100
+ evt.preventDefault();
101
+ } else if (evt.name === "g" && !evt.shift) {
102
+ setSelected(0);
103
+ evt.preventDefault();
104
+ } else if (evt.shift && evt.name === "g") {
105
+ setSelected(flatList().length - 1);
106
+ evt.preventDefault();
107
+ } else if (evt.name === "q") {
108
+ props.onQuit();
109
+ }
110
+ });
111
+
112
+ return (
113
+ <box paddingLeft={1} paddingRight={1}>
114
+ {/* Header bar */}
115
+ <box flexShrink={0} flexDirection="row" justifyContent="space-between" paddingBottom={1}>
116
+ <box flexDirection="row" gap={1}>
117
+ <text fg={toRGBA(props.theme.fg)} attributes={TextAttributes.BOLD}>
118
+ Tasks
119
+ </text>
120
+ <text fg={toRGBA(props.theme.fgMuted)}>{props.tasks.length}</text>
121
+ </box>
122
+ <text fg={toRGBA(props.theme.fgMuted)}>Enter:open n:new q:quit</text>
123
+ </box>
124
+
125
+ {/* Task list */}
126
+ <Show
127
+ when={flatList().length > 0}
128
+ fallback={
129
+ <box paddingLeft={1} paddingTop={1} flexGrow={1}>
130
+ <text fg={toRGBA(props.theme.fgMuted)}>No tasks yet. Press n to create one.</text>
131
+ </box>
132
+ }
133
+ >
134
+ <scrollbox flexGrow={1}>
135
+ <For each={flatList()}>
136
+ {(item, index) => {
137
+ if (item.kind === "header") {
138
+ return (
139
+ <box paddingTop={1} paddingLeft={1} onMouseMove={() => setInputMode("mouse")}>
140
+ <text fg={toRGBA(props.theme.fgMuted)}>
141
+ {statusLabel(item.status)} ({item.count})
142
+ </text>
143
+ </box>
144
+ );
145
+ }
146
+
147
+ const task = item.task;
148
+ const isSelected = createMemo(() => index() === selected());
149
+ const blocked = isBlocked(task, props.tasks);
150
+ const sColor = blocked ? props.theme.fgMuted : statusColor(task.status, props.theme);
151
+ const titleColor = () =>
152
+ isSelected()
153
+ ? props.theme.selectedText
154
+ : blocked
155
+ ? props.theme.fgMuted
156
+ : props.theme.fg;
157
+ return (
158
+ <box
159
+ backgroundColor={
160
+ isSelected() ? toRGBA(props.theme.selected) : RGBA.fromInts(0, 0, 0, 0)
161
+ }
162
+ flexDirection="row"
163
+ paddingLeft={1}
164
+ paddingRight={1}
165
+ onMouseMove={() => {
166
+ setInputMode("mouse");
167
+ setSelected(index());
168
+ }}
169
+ onMouseDown={() => setSelected(index())}
170
+ onMouseUp={() => {
171
+ const t = selectedTask();
172
+ if (t) props.onSelect(t);
173
+ }}
174
+ >
175
+ <Show when={blocked}>
176
+ <text fg={toRGBA(props.theme.fgMuted)} flexShrink={0} wrapMode="none">
177
+ {"# "}
178
+ </text>
179
+ </Show>
180
+ <text fg={toRGBA(sColor)} flexShrink={0} wrapMode="none">
181
+ {priorityDots(task.priority)}{" "}
182
+ </text>
183
+ <text fg={toRGBA(titleColor())} wrapMode="none" flexGrow={1}>
184
+ {task.title}
185
+ </text>
186
+ <Show when={task.status === "done"}>
187
+ <text fg={toRGBA(sColor)} flexShrink={0}>
188
+ {" ok"}
189
+ </text>
190
+ </Show>
191
+ <Show when={task.assignee && task.status !== "done"}>
192
+ <text fg={toRGBA(props.theme.fgMuted)} flexShrink={0} wrapMode="none">
193
+ {" @"}
194
+ {task.assignee}
195
+ </text>
196
+ </Show>
197
+ </box>
198
+ );
199
+ }}
200
+ </For>
201
+ </scrollbox>
202
+ </Show>
203
+ </box>
204
+ );
205
+ }