@sascha384/tic 1.15.0 → 1.17.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 (53) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dist/app.d.ts +2 -0
  3. package/dist/app.js +16 -1
  4. package/dist/app.js.map +1 -1
  5. package/dist/backends/availability.d.ts +14 -0
  6. package/dist/backends/availability.js +41 -0
  7. package/dist/backends/availability.js.map +1 -0
  8. package/dist/backends/factory.js +9 -5
  9. package/dist/backends/factory.js.map +1 -1
  10. package/dist/backends/jira/config.js +5 -1
  11. package/dist/backends/jira/config.js.map +1 -1
  12. package/dist/backends/local/config.d.ts +2 -0
  13. package/dist/backends/local/config.js +1 -0
  14. package/dist/backends/local/config.js.map +1 -1
  15. package/dist/backends/local/index.d.ts +0 -2
  16. package/dist/backends/local/index.js +31 -31
  17. package/dist/backends/local/index.js.map +1 -1
  18. package/dist/components/DefaultPicker.d.ts +8 -0
  19. package/dist/components/DefaultPicker.js +19 -0
  20. package/dist/components/DefaultPicker.js.map +1 -0
  21. package/dist/components/Header.js +3 -4
  22. package/dist/components/Header.js.map +1 -1
  23. package/dist/components/IterationPicker.js +9 -3
  24. package/dist/components/IterationPicker.js.map +1 -1
  25. package/dist/components/Settings.js +174 -71
  26. package/dist/components/Settings.js.map +1 -1
  27. package/dist/components/StatusScreen.js +3 -8
  28. package/dist/components/StatusScreen.js.map +1 -1
  29. package/dist/components/WorkItemForm.js +10 -2
  30. package/dist/components/WorkItemForm.js.map +1 -1
  31. package/dist/components/WorkItemList.js +152 -194
  32. package/dist/components/WorkItemList.js.map +1 -1
  33. package/dist/index.js +8 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/stores/backendDataStore.d.ts +24 -0
  36. package/dist/stores/backendDataStore.js +115 -0
  37. package/dist/stores/backendDataStore.js.map +1 -0
  38. package/dist/stores/configStore.d.ts +10 -0
  39. package/dist/stores/configStore.js +80 -0
  40. package/dist/stores/configStore.js.map +1 -0
  41. package/dist/stores/uiStore.d.ts +52 -0
  42. package/dist/stores/uiStore.js +22 -0
  43. package/dist/stores/uiStore.js.map +1 -0
  44. package/dist/update-checker.d.ts +6 -0
  45. package/dist/update-checker.js +27 -0
  46. package/dist/update-checker.js.map +1 -0
  47. package/dist/updater.d.ts +6 -0
  48. package/dist/updater.js +37 -0
  49. package/dist/updater.js.map +1 -0
  50. package/package.json +5 -2
  51. package/dist/hooks/useBackendData.d.ts +0 -17
  52. package/dist/hooks/useBackendData.js +0 -98
  53. package/dist/hooks/useBackendData.js.map +0 -1
@@ -1,17 +1,17 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState, useMemo, useEffect } from 'react';
2
+ import { useState, useMemo, useEffect, useCallback } from 'react';
3
3
  import { Box, Text, useInput, useApp } from 'ink';
4
4
  import Spinner from 'ink-spinner';
5
- import TextInput from 'ink-text-input';
6
5
  import { useAppState } from '../app.js';
7
6
  import { isGitRepo } from '../git.js';
8
7
  import { beginImplementation } from '../implement.js';
9
- import { readConfigSync } from '../backends/local/config.js';
8
+ import { useConfigStore } from '../stores/configStore.js';
9
+ import { uiStore, useUIStore, getOverlayTargetIds } from '../stores/uiStore.js';
10
10
  import { TableLayout } from './TableLayout.js';
11
11
  import { CardLayout } from './CardLayout.js';
12
12
  import { useTerminalSize } from '../hooks/useTerminalSize.js';
13
13
  import { useScrollViewport } from '../hooks/useScrollViewport.js';
14
- import { useBackendData } from '../hooks/useBackendData.js';
14
+ import { useBackendDataStore, backendDataStore, } from '../stores/backendDataStore.js';
15
15
  import { SyncQueueStore } from '../sync/queue.js';
16
16
  import { buildTree } from './buildTree.js';
17
17
  import { SearchOverlay } from './SearchOverlay.js';
@@ -22,6 +22,8 @@ import { PriorityPicker } from './PriorityPicker.js';
22
22
  import { TemplatePicker } from './TemplatePicker.js';
23
23
  import { TypePicker } from './TypePicker.js';
24
24
  import { StatusPicker } from './StatusPicker.js';
25
+ import { AutocompleteInput } from './AutocompleteInput.js';
26
+ import { MultiAutocompleteInput } from './MultiAutocompleteInput.js';
25
27
  export function getTargetIds(markedIds, cursorItem) {
26
28
  if (markedIds.size > 0) {
27
29
  return [...markedIds];
@@ -29,46 +31,36 @@ export function getTargetIds(markedIds, cursorItem) {
29
31
  return cursorItem ? [cursorItem.id] : [];
30
32
  }
31
33
  export function WorkItemList() {
32
- const { backend, syncManager, navigate, navigateToHelp, selectWorkItem, activeType, setActiveType, setActiveTemplate, setFormMode, } = useAppState();
34
+ const { backend, syncManager, navigate, navigateToHelp, selectWorkItem, activeType, setActiveType, setActiveTemplate, setFormMode, updateInfo, } = useAppState();
35
+ const defaultType = useConfigStore((s) => s.config.defaultType ?? null);
36
+ const branchMode = useConfigStore((s) => s.config.branchMode ?? 'worktree');
33
37
  const { exit } = useApp();
34
38
  const [cursor, setCursor] = useState(0);
35
- const [confirmDelete, setConfirmDelete] = useState(false);
36
- const [warning, setWarning] = useState('');
37
- const [settingParent, setSettingParent] = useState(false);
38
39
  const [parentInput, setParentInput] = useState('');
39
- const [isSearching, setIsSearching] = useState(false);
40
40
  const [allSearchItems, setAllSearchItems] = useState([]);
41
41
  // Marked items state for bulk operations
42
42
  const [markedIds, setMarkedIds] = useState(() => new Set());
43
- const [deleteTargetIds, setDeleteTargetIds] = useState([]);
44
- const [parentTargetIds, setParentTargetIds] = useState([]);
45
- const [showBulkMenu, setShowBulkMenu] = useState(false);
46
- const [showStatusPicker, setShowStatusPicker] = useState(false);
47
- const [showTypePicker, setShowTypePicker] = useState(false);
48
- const [showPriorityPicker, setShowPriorityPicker] = useState(false);
49
- const [settingAssignee, setSettingAssignee] = useState(false);
50
43
  const [assigneeInput, setAssigneeInput] = useState('');
51
- const [settingLabels, setSettingLabels] = useState(false);
52
44
  const [labelsInput, setLabelsInput] = useState('');
53
- const [bulkTargetIds, setBulkTargetIds] = useState([]);
54
- const [showCommandPalette, setShowCommandPalette] = useState(false);
55
- const [showTemplatePicker, setShowTemplatePicker] = useState(false);
56
45
  const [templates, setTemplates] = useState([]);
46
+ // UI overlay state from store
47
+ const activeOverlay = useUIStore((s) => s.activeOverlay);
48
+ const warning = useUIStore((s) => s.warning);
49
+ const { openOverlay, closeOverlay, setWarning, clearWarning } = uiStore.getState();
57
50
  // Marked count for header display
58
51
  const markedCount = markedIds.size;
59
- const [syncStatus, setSyncStatus] = useState(syncManager?.getStatus() ?? null);
60
- const { capabilities, types, statuses, currentIteration: iteration, items: allItems, loading, refresh: refreshData, } = useBackendData(backend);
61
- useEffect(() => {
62
- if (!syncManager)
63
- return;
64
- const cb = (status) => {
65
- setSyncStatus(status);
66
- if (status.state === 'idle') {
67
- refreshData();
68
- }
69
- };
70
- syncManager.onStatusChange(cb);
71
- }, [syncManager, refreshData]);
52
+ const syncStatus = useBackendDataStore((s) => s.syncStatus);
53
+ const capabilities = useBackendDataStore((s) => s.capabilities);
54
+ const types = useBackendDataStore((s) => s.types);
55
+ const statuses = useBackendDataStore((s) => s.statuses);
56
+ const assignees = useBackendDataStore((s) => s.assignees);
57
+ const labelSuggestions = useBackendDataStore((s) => s.labels);
58
+ const iteration = useBackendDataStore((s) => s.currentIteration);
59
+ const allItems = useBackendDataStore((s) => s.items);
60
+ const loading = useBackendDataStore((s) => s.loading);
61
+ const refreshData = useCallback(() => {
62
+ void backendDataStore.getState().refresh();
63
+ }, []);
72
64
  useEffect(() => {
73
65
  if (capabilities.templates) {
74
66
  void backend.listTemplates().then(setTemplates);
@@ -93,13 +85,14 @@ export function WorkItemList() {
93
85
  const gitAvailable = useMemo(() => isGitRepo(process.cwd()), []);
94
86
  useEffect(() => {
95
87
  if (activeType === null && types.length > 0) {
96
- setActiveType(types[0]);
88
+ setActiveType(defaultType && types.includes(defaultType) ? defaultType : types[0]);
97
89
  }
98
- }, [activeType, types, setActiveType]);
90
+ }, [activeType, types, setActiveType, defaultType]);
99
91
  const items = useMemo(() => allItems.filter((item) => item.type === activeType), [allItems, activeType]);
100
92
  const fullTree = useMemo(() => capabilities.relationships
101
93
  ? buildTree(items, allItems, activeType ?? '')
102
94
  : buildTree(items, items, activeType ?? ''), [items, allItems, activeType, capabilities.relationships]);
95
+ const parentSuggestions = useMemo(() => allItems.map((item) => `${item.id} - ${item.title}`), [allItems]);
103
96
  // Collapse state: set of item IDs that are collapsed (collapsed by default)
104
97
  // Track explicitly expanded items (inverse of collapsed).
105
98
  // All parents are collapsed by default; expanding removes from this set.
@@ -134,7 +127,7 @@ export function WorkItemList() {
134
127
  setCursor((c) => Math.min(c, Math.max(0, treeItems.length - 1)));
135
128
  }, [treeItems.length]);
136
129
  useEffect(() => {
137
- if (!isSearching)
130
+ if (activeOverlay?.type !== 'search')
138
131
  return;
139
132
  let cancelled = false;
140
133
  void backend.listWorkItems().then((items) => {
@@ -144,7 +137,7 @@ export function WorkItemList() {
144
137
  return () => {
145
138
  cancelled = true;
146
139
  };
147
- }, [isSearching, backend]);
140
+ }, [activeOverlay?.type, backend]);
148
141
  const isCardMode = terminalWidth < 80;
149
142
  const viewport = useScrollViewport({
150
143
  totalItems: treeItems.length,
@@ -152,67 +145,51 @@ export function WorkItemList() {
152
145
  chromeLines: 6, // title+margin (2) + table header (1) + help bar margin+text (2) + warning (1)
153
146
  linesPerItem: isCardMode ? 3 : 1,
154
147
  });
155
- useInput((input, key) => {
156
- if (settingParent) {
157
- if (key.escape) {
158
- setSettingParent(false);
159
- }
160
- return;
161
- }
162
- if (showBulkMenu)
163
- return;
164
- if (settingAssignee) {
165
- if (key.escape) {
166
- setSettingAssignee(false);
167
- setBulkTargetIds([]);
168
- }
169
- return;
170
- }
171
- if (settingLabels) {
172
- if (key.escape) {
173
- setSettingLabels(false);
174
- setBulkTargetIds([]);
175
- }
176
- return;
177
- }
178
- if (isSearching)
179
- return;
180
- if (showCommandPalette)
181
- return;
182
- if (showTemplatePicker)
148
+ // Block 1: Overlay escape handlers for inline inputs
149
+ useInput((_input, key) => {
150
+ if (key.escape) {
151
+ closeOverlay();
152
+ }
153
+ }, {
154
+ isActive: activeOverlay?.type === 'parent-input' ||
155
+ activeOverlay?.type === 'assignee-input' ||
156
+ activeOverlay?.type === 'labels-input',
157
+ });
158
+ // Block 2: Delete confirmation handler
159
+ useInput((input) => {
160
+ if (activeOverlay?.type !== 'delete-confirm')
183
161
  return;
184
- if (confirmDelete) {
185
- if (input === 'y' || input === 'Y') {
186
- void (async () => {
187
- for (const id of deleteTargetIds) {
188
- await backend.cachedDeleteWorkItem(id);
189
- await queueWrite('delete', id);
162
+ if (input === 'y' || input === 'Y') {
163
+ const targetIds = activeOverlay.targetIds;
164
+ void (async () => {
165
+ for (const id of targetIds) {
166
+ await backend.cachedDeleteWorkItem(id);
167
+ await queueWrite('delete', id);
168
+ }
169
+ closeOverlay();
170
+ setMarkedIds((prev) => {
171
+ const next = new Set(prev);
172
+ for (const id of targetIds) {
173
+ next.delete(id);
190
174
  }
191
- setConfirmDelete(false);
192
- setDeleteTargetIds([]);
193
- setMarkedIds((prev) => {
194
- const next = new Set(prev);
195
- for (const id of deleteTargetIds) {
196
- next.delete(id);
197
- }
198
- return next;
199
- });
200
- setCursor((c) => Math.max(0, c - 1));
201
- refreshData();
202
- })();
203
- }
204
- else {
205
- setConfirmDelete(false);
206
- setDeleteTargetIds([]);
207
- }
208
- return;
175
+ return next;
176
+ });
177
+ setCursor((c) => Math.max(0, c - 1));
178
+ refreshData();
179
+ })();
180
+ }
181
+ else {
182
+ closeOverlay();
209
183
  }
184
+ }, { isActive: activeOverlay?.type === 'delete-confirm' });
185
+ // Block 3: Main input handler — only active when no overlay is open
186
+ useInput((input, key) => {
210
187
  if (input === '/') {
211
- setIsSearching(true);
188
+ openOverlay({ type: 'search' });
212
189
  return;
213
190
  }
214
191
  if (input === ':') {
215
- setShowCommandPalette(true);
192
+ openOverlay({ type: 'command-palette' });
216
193
  return;
217
194
  }
218
195
  if (input === '?') {
@@ -221,31 +198,33 @@ export function WorkItemList() {
221
198
  }
222
199
  if (key.upArrow) {
223
200
  setCursor((c) => Math.max(0, c - 1));
224
- setWarning('');
201
+ clearWarning();
225
202
  }
226
203
  if (key.downArrow) {
227
204
  setCursor((c) => Math.min(treeItems.length - 1, c + 1));
228
- setWarning('');
205
+ clearWarning();
229
206
  }
230
207
  if (key.pageUp) {
231
208
  setCursor((c) => Math.max(0, c - viewport.maxVisible));
232
- setWarning('');
209
+ clearWarning();
233
210
  }
234
211
  if (key.pageDown) {
235
212
  setCursor((c) => Math.min(treeItems.length - 1, c + viewport.maxVisible));
236
- setWarning('');
213
+ clearWarning();
237
214
  }
238
215
  if (key.home) {
239
216
  setCursor(0);
240
- setWarning('');
217
+ clearWarning();
241
218
  }
242
219
  if (key.end) {
243
220
  setCursor(treeItems.length - 1);
244
- setWarning('');
221
+ clearWarning();
245
222
  }
246
223
  if (key.rightArrow && treeItems.length > 0) {
247
224
  const current = treeItems[cursor];
248
- if (current && current.hasChildren && collapsedIds.has(current.item.id)) {
225
+ if (current &&
226
+ current.hasChildren &&
227
+ collapsedIds.has(current.item.id)) {
249
228
  setExpandedIds((prev) => new Set(prev).add(current.item.id));
250
229
  }
251
230
  }
@@ -279,7 +258,7 @@ export function WorkItemList() {
279
258
  navigate('settings');
280
259
  if (input === 'c') {
281
260
  if (capabilities.templates && templates.length > 0) {
282
- setShowTemplatePicker(true);
261
+ openOverlay({ type: 'template-picker' });
283
262
  }
284
263
  else {
285
264
  setFormMode('item');
@@ -291,8 +270,7 @@ export function WorkItemList() {
291
270
  if (input === 'd' && treeItems.length > 0) {
292
271
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
293
272
  if (targetIds.length > 0) {
294
- setDeleteTargetIds(targetIds);
295
- setConfirmDelete(true);
273
+ openOverlay({ type: 'delete-confirm', targetIds });
296
274
  }
297
275
  }
298
276
  if (input === 'o' && treeItems.length > 0) {
@@ -304,9 +282,8 @@ export function WorkItemList() {
304
282
  if (input === 'b' && gitAvailable && treeItems.length > 0) {
305
283
  const item = treeItems[cursor].item;
306
284
  const comments = item.comments;
307
- const config = readConfigSync(process.cwd());
308
285
  try {
309
- const result = beginImplementation(item, comments, { branchMode: config.branchMode ?? 'worktree' }, process.cwd());
286
+ const result = beginImplementation(item, comments, { branchMode }, process.cwd());
310
287
  setWarning(result.resumed
311
288
  ? `Resumed work on #${item.id}`
312
289
  : `Started work on #${item.id}`);
@@ -319,14 +296,10 @@ export function WorkItemList() {
319
296
  if (input === 's') {
320
297
  navigate('status');
321
298
  }
322
- if (input === 'p' &&
323
- capabilities.fields.parent &&
324
- treeItems.length > 0 &&
325
- !settingParent) {
299
+ if (input === 'p' && capabilities.fields.parent && treeItems.length > 0) {
326
300
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
327
301
  if (targetIds.length > 0) {
328
- setParentTargetIds(targetIds);
329
- setSettingParent(true);
302
+ openOverlay({ type: 'parent-input', targetIds });
330
303
  // For single item, prefill current parent
331
304
  if (targetIds.length === 1) {
332
305
  const item = treeItems.find((t) => t.item.id === targetIds[0]);
@@ -342,7 +315,7 @@ export function WorkItemList() {
342
315
  const nextType = types[(currentIdx + 1) % types.length];
343
316
  setActiveType(nextType);
344
317
  setCursor(0);
345
- setWarning('');
318
+ clearWarning();
346
319
  }
347
320
  if (input === 'r' && syncManager) {
348
321
  void syncManager.sync().then(() => {
@@ -366,46 +339,46 @@ export function WorkItemList() {
366
339
  setMarkedIds(new Set());
367
340
  }
368
341
  if (input === 'B' && treeItems.length > 0) {
369
- setShowBulkMenu(true);
342
+ openOverlay({ type: 'bulk-menu' });
370
343
  }
371
- if (input === 'P' && capabilities.fields.priority && treeItems.length > 0) {
344
+ if (input === 'P' &&
345
+ capabilities.fields.priority &&
346
+ treeItems.length > 0) {
372
347
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
373
348
  if (targetIds.length > 0) {
374
- setBulkTargetIds(targetIds);
375
- setShowPriorityPicker(true);
349
+ openOverlay({ type: 'priority-picker', targetIds });
376
350
  }
377
351
  }
378
- if (input === 'a' && capabilities.fields.assignee && treeItems.length > 0) {
352
+ if (input === 'a' &&
353
+ capabilities.fields.assignee &&
354
+ treeItems.length > 0) {
379
355
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
380
356
  if (targetIds.length > 0) {
381
- setBulkTargetIds(targetIds);
382
- setSettingAssignee(true);
357
+ openOverlay({ type: 'assignee-input', targetIds });
383
358
  setAssigneeInput('');
384
359
  }
385
360
  }
386
361
  if (input === 'l' && capabilities.fields.labels && treeItems.length > 0) {
387
362
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
388
363
  if (targetIds.length > 0) {
389
- setBulkTargetIds(targetIds);
390
- setSettingLabels(true);
364
+ openOverlay({ type: 'labels-input', targetIds });
391
365
  setLabelsInput('');
392
366
  }
393
367
  }
394
368
  if (input === 't' && capabilities.customTypes && treeItems.length > 0) {
395
369
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
396
370
  if (targetIds.length > 0) {
397
- setBulkTargetIds(targetIds);
398
- setShowTypePicker(true);
371
+ openOverlay({ type: 'type-picker', targetIds });
399
372
  }
400
373
  }
401
- });
374
+ }, { isActive: activeOverlay === null });
402
375
  const handleSearchSelect = (item) => {
403
- setIsSearching(false);
376
+ closeOverlay();
404
377
  selectWorkItem(item.id);
405
378
  navigate('form');
406
379
  };
407
380
  const handleSearchCancel = () => {
408
- setIsSearching(false);
381
+ closeOverlay();
409
382
  };
410
383
  const commandContext = {
411
384
  screen: 'list',
@@ -427,7 +400,7 @@ export function WorkItemList() {
427
400
  gitAvailable,
428
401
  ]);
429
402
  const handleCommandSelect = (command) => {
430
- setShowCommandPalette(false);
403
+ closeOverlay();
431
404
  switch (command.id) {
432
405
  case 'create':
433
406
  selectWorkItem(null);
@@ -443,8 +416,7 @@ export function WorkItemList() {
443
416
  if (treeItems.length > 0) {
444
417
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
445
418
  if (targetIds.length > 0) {
446
- setDeleteTargetIds(targetIds);
447
- setConfirmDelete(true);
419
+ openOverlay({ type: 'delete-confirm', targetIds });
448
420
  }
449
421
  }
450
422
  break;
@@ -460,9 +432,8 @@ export function WorkItemList() {
460
432
  if (treeItems[cursor]) {
461
433
  const item = treeItems[cursor].item;
462
434
  const comments = item.comments;
463
- const config = readConfigSync(process.cwd());
464
435
  try {
465
- const result = beginImplementation(item, comments, { branchMode: config.branchMode ?? 'worktree' }, process.cwd());
436
+ const result = beginImplementation(item, comments, { branchMode }, process.cwd());
466
437
  setWarning(result.resumed
467
438
  ? `Resumed work on #${item.id}`
468
439
  : `Started work on #${item.id}`);
@@ -510,8 +481,7 @@ export function WorkItemList() {
510
481
  {
511
482
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
512
483
  if (targetIds.length > 0) {
513
- setBulkTargetIds(targetIds);
514
- setShowPriorityPicker(true);
484
+ openOverlay({ type: 'priority-picker', targetIds });
515
485
  }
516
486
  }
517
487
  break;
@@ -519,8 +489,7 @@ export function WorkItemList() {
519
489
  {
520
490
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
521
491
  if (targetIds.length > 0) {
522
- setBulkTargetIds(targetIds);
523
- setSettingAssignee(true);
492
+ openOverlay({ type: 'assignee-input', targetIds });
524
493
  setAssigneeInput('');
525
494
  }
526
495
  }
@@ -529,8 +498,7 @@ export function WorkItemList() {
529
498
  {
530
499
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
531
500
  if (targetIds.length > 0) {
532
- setBulkTargetIds(targetIds);
533
- setSettingLabels(true);
501
+ openOverlay({ type: 'labels-input', targetIds });
534
502
  setLabelsInput('');
535
503
  }
536
504
  }
@@ -539,13 +507,12 @@ export function WorkItemList() {
539
507
  {
540
508
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
541
509
  if (targetIds.length > 0) {
542
- setBulkTargetIds(targetIds);
543
- setShowTypePicker(true);
510
+ openOverlay({ type: 'type-picker', targetIds });
544
511
  }
545
512
  }
546
513
  break;
547
514
  case 'bulk-menu':
548
- setShowBulkMenu(true);
515
+ openOverlay({ type: 'bulk-menu' });
549
516
  break;
550
517
  case 'quit':
551
518
  exit();
@@ -556,7 +523,7 @@ export function WorkItemList() {
556
523
  const type = command.id.replace('switch-', '');
557
524
  setActiveType(type);
558
525
  setCursor(0);
559
- setWarning('');
526
+ clearWarning();
560
527
  }
561
528
  break;
562
529
  }
@@ -565,141 +532,132 @@ export function WorkItemList() {
565
532
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
566
533
  if (targetIds.length === 0)
567
534
  return;
568
- setBulkTargetIds(targetIds);
569
535
  switch (action) {
570
536
  case 'status':
571
- setShowStatusPicker(true);
537
+ openOverlay({ type: 'status-picker', targetIds });
572
538
  break;
573
539
  case 'iteration':
574
540
  navigate('iteration-picker');
575
541
  break;
576
542
  case 'parent':
577
- setParentTargetIds(targetIds);
578
- setSettingParent(true);
543
+ openOverlay({ type: 'parent-input', targetIds });
579
544
  setParentInput('');
580
545
  break;
581
546
  case 'type':
582
- setShowTypePicker(true);
547
+ openOverlay({ type: 'type-picker', targetIds });
583
548
  break;
584
549
  case 'priority':
585
- setShowPriorityPicker(true);
550
+ openOverlay({ type: 'priority-picker', targetIds });
586
551
  break;
587
552
  case 'assignee':
588
- setSettingAssignee(true);
553
+ openOverlay({ type: 'assignee-input', targetIds });
589
554
  setAssigneeInput('');
590
555
  break;
591
556
  case 'labels':
592
- setSettingLabels(true);
557
+ openOverlay({ type: 'labels-input', targetIds });
593
558
  setLabelsInput('');
594
559
  break;
595
560
  case 'delete':
596
- setDeleteTargetIds(targetIds);
597
- setConfirmDelete(true);
561
+ openOverlay({ type: 'delete-confirm', targetIds });
598
562
  break;
599
563
  }
600
564
  };
601
565
  const typeLabel = activeType
602
566
  ? activeType.charAt(0).toUpperCase() + activeType.slice(1) + 's'
603
567
  : '';
604
- const helpText = '↑↓ navigate pgup/dn page home/end jump enter edit c create ? help';
568
+ const helpText = '↑↓ navigate ←→ expand/collapse enter edit c create , settings ? help';
605
569
  const visibleTreeItems = treeItems.slice(viewport.start, viewport.end);
606
- return (_jsxs(Box, { flexDirection: "column", children: [isSearching && (_jsx(SearchOverlay, { items: allSearchItems, currentIteration: iteration, onSelect: handleSearchSelect, onCancel: handleSearchCancel })), !isSearching && (_jsxs(_Fragment, { children: [showBulkMenu && (_jsx(BulkMenu, { itemCount: markedIds.size > 0 ? markedIds.size : 1, capabilities: capabilities, onSelect: (action) => {
607
- setShowBulkMenu(false);
570
+ return (_jsxs(Box, { flexDirection: "column", children: [activeOverlay?.type === 'search' && (_jsx(SearchOverlay, { items: allSearchItems, currentIteration: iteration, onSelect: handleSearchSelect, onCancel: handleSearchCancel })), activeOverlay?.type !== 'search' && (_jsxs(_Fragment, { children: [activeOverlay?.type === 'bulk-menu' && (_jsx(BulkMenu, { itemCount: markedIds.size > 0 ? markedIds.size : 1, capabilities: capabilities, onSelect: (action) => {
571
+ closeOverlay();
608
572
  handleBulkAction(action);
609
- }, onCancel: () => setShowBulkMenu(false) })), showCommandPalette && (_jsx(CommandPalette, { commands: paletteCommands, onSelect: handleCommandSelect, onCancel: () => setShowCommandPalette(false) })), showStatusPicker && (_jsx(StatusPicker, { statuses: statuses, onSelect: (status) => {
573
+ }, onCancel: () => closeOverlay() })), activeOverlay?.type === 'command-palette' && (_jsx(CommandPalette, { commands: paletteCommands, onSelect: handleCommandSelect, onCancel: () => closeOverlay() })), activeOverlay?.type === 'status-picker' && (_jsx(StatusPicker, { statuses: statuses, onSelect: (status) => {
574
+ const targetIds = getOverlayTargetIds();
575
+ closeOverlay();
610
576
  void (async () => {
611
- setShowStatusPicker(false);
612
- for (const id of bulkTargetIds) {
577
+ for (const id of targetIds) {
613
578
  await backend.cachedUpdateWorkItem(id, { status });
614
579
  await queueWrite('update', id);
615
580
  }
616
- setBulkTargetIds([]);
617
581
  refreshData();
618
582
  })();
619
- }, onCancel: () => {
620
- setShowStatusPicker(false);
621
- setBulkTargetIds([]);
622
- } })), showTypePicker && (_jsx(TypePicker, { types: types, onSelect: (type) => {
583
+ }, onCancel: () => closeOverlay() })), activeOverlay?.type === 'type-picker' && (_jsx(TypePicker, { types: types, onSelect: (type) => {
584
+ const targetIds = getOverlayTargetIds();
585
+ closeOverlay();
623
586
  void (async () => {
624
- setShowTypePicker(false);
625
- for (const id of bulkTargetIds) {
587
+ for (const id of targetIds) {
626
588
  await backend.cachedUpdateWorkItem(id, { type });
627
589
  await queueWrite('update', id);
628
590
  }
629
- setBulkTargetIds([]);
630
591
  refreshData();
631
592
  })();
632
- }, onCancel: () => {
633
- setShowTypePicker(false);
634
- setBulkTargetIds([]);
635
- } })), showPriorityPicker && (_jsx(PriorityPicker, { onSelect: (priority) => {
593
+ }, onCancel: () => closeOverlay() })), activeOverlay?.type === 'priority-picker' && (_jsx(PriorityPicker, { onSelect: (priority) => {
594
+ const targetIds = getOverlayTargetIds();
595
+ closeOverlay();
636
596
  void (async () => {
637
- setShowPriorityPicker(false);
638
- for (const id of bulkTargetIds) {
597
+ for (const id of targetIds) {
639
598
  await backend.cachedUpdateWorkItem(id, { priority });
640
599
  await queueWrite('update', id);
641
600
  }
642
- setBulkTargetIds([]);
643
601
  refreshData();
644
602
  })();
645
- }, onCancel: () => {
646
- setShowPriorityPicker(false);
647
- setBulkTargetIds([]);
648
- } })), showTemplatePicker && (_jsx(TemplatePicker, { templates: templates, onSelect: (template) => {
649
- setShowTemplatePicker(false);
603
+ }, onCancel: () => closeOverlay() })), activeOverlay?.type === 'template-picker' && (_jsx(TemplatePicker, { templates: templates, onSelect: (template) => {
604
+ closeOverlay();
650
605
  setFormMode('item');
651
606
  setActiveTemplate(template);
652
607
  selectWorkItem(null);
653
608
  navigate('form');
654
- }, onCancel: () => {
655
- setShowTemplatePicker(false);
656
- } })), _jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { bold: true, color: "cyan", children: [typeLabel, " \u2014 ", iteration] }), _jsx(Text, { dimColor: true, children: ` (${items.length} items)` })] }), loading && (_jsx(Box, { marginLeft: 1, children: _jsx(Text, { color: "yellow", children: _jsx(Spinner, { type: "dots" }) }) })), syncStatus && syncStatus.state === 'syncing' ? (_jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: "yellow", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { dimColor: true, children: " Syncing..." })] })) : syncStatus && syncStatus.state === 'error' ? (_jsx(Text, { dimColor: true, children: ` ⚠ Sync failed (${syncStatus.errors.length} errors)` })) : syncStatus && syncStatus.pendingCount > 0 ? (_jsx(Text, { dimColor: true, children: ` ↑ ${syncStatus.pendingCount} pending` })) : syncStatus ? (_jsx(Text, { dimColor: true, children: " \u2713 Synced" })) : null, markedCount > 0 && (_jsx(Text, { color: "magenta", children: ` ● ${markedCount} marked` }))] }), terminalWidth >= 80 ? (_jsx(TableLayout, { treeItems: visibleTreeItems, cursor: viewport.visibleCursor, capabilities: capabilities, collapsedIds: collapsedIds, markedIds: markedIds })) : (_jsx(CardLayout, { treeItems: visibleTreeItems, cursor: viewport.visibleCursor, capabilities: capabilities, collapsedIds: collapsedIds, markedIds: markedIds })), treeItems.length === 0 && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["No ", activeType, "s in this iteration."] }) })), _jsx(Box, { marginTop: 1, children: capabilities.fields.parent && settingParent ? (_jsxs(Box, { children: [_jsxs(Text, { color: "cyan", children: ["Set parent for ", parentTargetIds.length, " item", parentTargetIds.length > 1 ? 's' : '', " (empty to clear):", ' '] }), _jsx(TextInput, { value: parentInput, onChange: setParentInput, focus: true, onSubmit: (value) => {
609
+ }, onCancel: () => closeOverlay() })), _jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { bold: true, color: "cyan", children: [typeLabel, " \u2014 ", iteration] }), _jsx(Text, { dimColor: true, children: ` (${items.length} items)` })] }), loading && (_jsx(Box, { marginLeft: 1, children: _jsx(Text, { color: "yellow", children: _jsx(Spinner, { type: "dots" }) }) })), syncStatus && syncStatus.state === 'syncing' ? (_jsxs(Box, { marginLeft: 1, children: [_jsx(Text, { color: "yellow", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { dimColor: true, children: " Syncing..." })] })) : syncStatus && syncStatus.state === 'error' ? (_jsx(Text, { dimColor: true, children: ` ⚠ Sync failed (${syncStatus.errors.length} errors)` })) : syncStatus && syncStatus.pendingCount > 0 ? (_jsx(Text, { dimColor: true, children: ` ↑ ${syncStatus.pendingCount} pending` })) : syncStatus ? (_jsx(Text, { dimColor: true, children: " \u2713 Synced" })) : null, markedCount > 0 && (_jsx(Text, { color: "magenta", children: ` ● ${markedCount} marked` }))] }), terminalWidth >= 80 ? (_jsx(TableLayout, { treeItems: visibleTreeItems, cursor: viewport.visibleCursor, capabilities: capabilities, collapsedIds: collapsedIds, markedIds: markedIds })) : (_jsx(CardLayout, { treeItems: visibleTreeItems, cursor: viewport.visibleCursor, capabilities: capabilities, collapsedIds: collapsedIds, markedIds: markedIds })), treeItems.length === 0 && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["No ", activeType, "s in this iteration."] }) })), _jsx(Box, { marginTop: 1, children: activeOverlay?.type === 'parent-input' ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "cyan", children: ["Set parent for ", activeOverlay.targetIds.length, " item", activeOverlay.targetIds.length > 1 ? 's' : '', " (empty to clear):"] }), _jsx(AutocompleteInput, { value: parentInput, onChange: setParentInput, focus: true, suggestions: parentSuggestions, onSubmit: () => {
610
+ const targetIds = getOverlayTargetIds();
657
611
  void (async () => {
658
- const newParent = value.trim() === '' ? null : value.trim();
612
+ const raw = parentInput.trim();
613
+ const newParent = raw === ''
614
+ ? null
615
+ : raw.includes(' - ')
616
+ ? raw.split(' - ')[0].trim()
617
+ : raw;
659
618
  try {
660
- for (const id of parentTargetIds) {
619
+ for (const id of targetIds) {
661
620
  await backend.cachedUpdateWorkItem(id, {
662
621
  parent: newParent,
663
622
  });
664
623
  await queueWrite('update', id);
665
624
  }
666
- setWarning('');
625
+ clearWarning();
667
626
  }
668
627
  catch (e) {
669
628
  setWarning(e instanceof Error ? e.message : 'Invalid parent');
670
629
  }
671
- setSettingParent(false);
630
+ closeOverlay();
672
631
  setParentInput('');
673
- setParentTargetIds([]);
674
632
  refreshData();
675
633
  })();
676
- } })] })) : settingAssignee ? (_jsxs(Box, { children: [_jsxs(Text, { color: "cyan", children: ["Set assignee for ", bulkTargetIds.length, " item", bulkTargetIds.length > 1 ? 's' : '', ":", ' '] }), _jsx(TextInput, { value: assigneeInput, onChange: setAssigneeInput, focus: true, onSubmit: (value) => {
634
+ } })] })) : activeOverlay?.type === 'assignee-input' ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "cyan", children: ["Set assignee for ", activeOverlay.targetIds.length, " item", activeOverlay.targetIds.length > 1 ? 's' : '', ":"] }), _jsx(AutocompleteInput, { value: assigneeInput, onChange: setAssigneeInput, focus: true, suggestions: assignees, onSubmit: () => {
635
+ const targetIds = getOverlayTargetIds();
636
+ closeOverlay();
677
637
  void (async () => {
678
- const assignee = value.trim();
679
- for (const id of bulkTargetIds) {
638
+ const assignee = assigneeInput.trim();
639
+ for (const id of targetIds) {
680
640
  await backend.cachedUpdateWorkItem(id, { assignee });
681
641
  await queueWrite('update', id);
682
642
  }
683
- setSettingAssignee(false);
684
643
  setAssigneeInput('');
685
- setBulkTargetIds([]);
686
644
  refreshData();
687
645
  })();
688
- } })] })) : settingLabels ? (_jsxs(Box, { children: [_jsxs(Text, { color: "cyan", children: ["Set labels for ", bulkTargetIds.length, " item", bulkTargetIds.length > 1 ? 's' : '', " (comma-separated):", ' '] }), _jsx(TextInput, { value: labelsInput, onChange: setLabelsInput, focus: true, onSubmit: (value) => {
646
+ } })] })) : activeOverlay?.type === 'labels-input' ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "cyan", children: ["Set labels for ", activeOverlay.targetIds.length, " item", activeOverlay.targetIds.length > 1 ? 's' : '', ' ', "(comma-separated):"] }), _jsx(MultiAutocompleteInput, { value: labelsInput, onChange: setLabelsInput, focus: true, suggestions: labelSuggestions, onSubmit: () => {
647
+ const targetIds = getOverlayTargetIds();
648
+ closeOverlay();
689
649
  void (async () => {
690
- const labels = value
650
+ const labels = labelsInput
691
651
  .split(',')
692
652
  .map((l) => l.trim())
693
653
  .filter(Boolean);
694
- for (const id of bulkTargetIds) {
654
+ for (const id of targetIds) {
695
655
  await backend.cachedUpdateWorkItem(id, { labels });
696
656
  await queueWrite('update', id);
697
657
  }
698
- setSettingLabels(false);
699
658
  setLabelsInput('');
700
- setBulkTargetIds([]);
701
659
  refreshData();
702
660
  })();
703
- } })] })) : confirmDelete ? (_jsxs(Text, { color: "red", children: ["Delete ", deleteTargetIds.length, " item", deleteTargetIds.length > 1 ? 's' : '', "? (y/n)"] })) : (_jsx(Text, { dimColor: true, children: helpText })) }), warning && (_jsx(Box, { children: _jsxs(Text, { color: "yellow", children: ["\u26A0 ", warning] }) }))] }))] }));
661
+ } })] })) : activeOverlay?.type === 'delete-confirm' ? (_jsxs(Text, { color: "red", children: ["Delete ", activeOverlay.targetIds.length, " item", activeOverlay.targetIds.length > 1 ? 's' : '', "? (y/n)"] })) : (_jsx(Text, { dimColor: true, children: helpText })) }), warning && (_jsx(Box, { children: _jsxs(Text, { color: "yellow", children: ["\u26A0 ", warning] }) })), updateInfo?.updateAvailable && activeOverlay === null && (_jsx(Box, { children: _jsxs(Text, { color: "yellow", children: ["Update available: ", updateInfo.current, " \u2192 ", updateInfo.latest, ' ', "Press , to update in Settings"] }) }))] }))] }));
704
662
  }
705
663
  //# sourceMappingURL=WorkItemList.js.map