@sascha384/tic 4.9.0 → 5.1.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 (41) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dist/auth/gitlab.js +12 -5
  3. package/dist/auth/gitlab.js.map +1 -1
  4. package/dist/commands.d.ts +28 -1
  5. package/dist/commands.js +677 -5
  6. package/dist/commands.js.map +1 -1
  7. package/dist/components/BranchList.js +326 -168
  8. package/dist/components/BranchList.js.map +1 -1
  9. package/dist/components/ColorPill.js +3 -3
  10. package/dist/components/ColorPill.js.map +1 -1
  11. package/dist/components/DetailPanel.js +2 -2
  12. package/dist/components/DetailPanel.js.map +1 -1
  13. package/dist/components/Header.js +6 -2
  14. package/dist/components/Header.js.map +1 -1
  15. package/dist/components/HelpScreen.d.ts +1 -9
  16. package/dist/components/HelpScreen.js +32 -227
  17. package/dist/components/HelpScreen.js.map +1 -1
  18. package/dist/components/IterationPicker.js +3 -2
  19. package/dist/components/IterationPicker.js.map +1 -1
  20. package/dist/components/PullRequestList.js +56 -16
  21. package/dist/components/PullRequestList.js.map +1 -1
  22. package/dist/components/Settings.js +36 -26
  23. package/dist/components/Settings.js.map +1 -1
  24. package/dist/components/StatusScreen.js +32 -20
  25. package/dist/components/StatusScreen.js.map +1 -1
  26. package/dist/components/WorkItemForm.js +20 -14
  27. package/dist/components/WorkItemForm.js.map +1 -1
  28. package/dist/components/WorkItemList.d.ts +0 -1
  29. package/dist/components/WorkItemList.js +97 -101
  30. package/dist/components/WorkItemList.js.map +1 -1
  31. package/dist/implement.js +7 -3
  32. package/dist/implement.js.map +1 -1
  33. package/dist/storage/index.js +14 -11
  34. package/dist/storage/index.js.map +1 -1
  35. package/dist/stores/backendDataStore.d.ts +1 -0
  36. package/dist/stores/backendDataStore.js +23 -1
  37. package/dist/stores/backendDataStore.js.map +1 -1
  38. package/dist/stores/uiStore.d.ts +12 -0
  39. package/dist/stores/uiStore.js.map +1 -1
  40. package/drizzle/meta/_journal.json +1 -1
  41. package/package.json +1 -1
@@ -15,7 +15,7 @@ import { useScrollViewport } from '../hooks/useScrollViewport.js';
15
15
  import { useBackendDataStore, backendDataStore, } from '../stores/backendDataStore.js';
16
16
  import { useShallow } from 'zustand/shallow';
17
17
  import { buildTree, sortTree } from './buildTree.js';
18
- import { getVisibleCommands, } from '../commands.js';
18
+ import { getVisibleCommands, buildFooterHints, matchesCommand, } from '../commands.js';
19
19
  import { OverlayPanel } from './OverlayPanel.js';
20
20
  import { DetailPanel } from './DetailPanel.js';
21
21
  import { undoStore } from '../stores/undoStore.js';
@@ -127,29 +127,6 @@ export function getTargetIds(markedIds, cursorItem) {
127
127
  }
128
128
  return cursorItem ? [cursorItem.id] : [];
129
129
  }
130
- export function buildHelpText(availableWidth) {
131
- const shortcuts = [
132
- { key: '↑↓', label: 'navigate' },
133
- { key: '←→', label: 'expand' },
134
- { key: 'enter', label: 'edit' },
135
- { key: 'c', label: 'create' },
136
- { key: 'd', label: 'delete' },
137
- { key: 'u', label: 'undo' },
138
- { key: '/', label: 'commands' },
139
- { key: ',', label: 'settings' },
140
- { key: '?', label: 'help' },
141
- ];
142
- const sep = ' ';
143
- let result = '';
144
- for (const s of shortcuts) {
145
- const entry = `${s.key} ${s.label}`;
146
- const candidate = result ? result + sep + entry : entry;
147
- if (candidate.length > availableWidth)
148
- break;
149
- result = candidate;
150
- }
151
- return result;
152
- }
153
130
  export function WorkItemList() {
154
131
  const { accent, success, error: errorColor, warning: warningColor, marked, mutedDim, } = useThemeStore((s) => s.colors);
155
132
  // Backend data store - split by change frequency for minimal re-renders
@@ -395,7 +372,8 @@ export function WorkItemList() {
395
372
  const markedDistribution = useMemo(() => getMarkedDistribution(markedIds, treeItems.map((t) => t.item), viewport.start, viewport.end), [markedIds, treeItems, viewport.start, viewport.end]);
396
373
  // Block 1.5: Description scroll handler — active when full description is shown
397
374
  useInput((_input, key) => {
398
- if (_input === ' ' || key.escape) {
375
+ if (matchesCommand('list-toggle-description', _input, key) ||
376
+ key.escape) {
399
377
  setShowFullDescription(false);
400
378
  setDescriptionScrollOffset(0);
401
379
  return;
@@ -410,67 +388,57 @@ export function WorkItemList() {
410
388
  }, { isActive: showFullDescription && activeOverlay === null });
411
389
  // Block 3: Main input handler — only active when no overlay is open
412
390
  useInput((input, key) => {
413
- if (input === '/') {
391
+ if (matchesCommand('list-command-bar', input, key)) {
414
392
  openOverlay({ type: 'command-bar' });
415
393
  return;
416
394
  }
417
- if (input === '?') {
395
+ if (matchesCommand('help', input, key)) {
418
396
  navigateToHelp();
419
397
  return;
420
398
  }
421
- if (key.upArrow) {
422
- if (key.shift) {
423
- const anchor = rangeAnchor ?? cursor;
424
- if (rangeAnchor === null)
425
- setRangeAnchor(cursor);
426
- const newCursor = Math.max(0, cursor - 1);
427
- setCursor(newCursor);
428
- const start = Math.min(anchor, newCursor);
429
- const end = Math.max(anchor, newCursor);
430
- setMarkedIds(new Set(treeItems.slice(start, end + 1).map((t) => t.item.id)));
431
- }
432
- else {
433
- if (rangeAnchor !== null)
434
- setRangeAnchor(null);
435
- setCursor(Math.max(0, cursor - 1));
436
- }
399
+ if (matchesCommand('list-range-select', input, key)) {
400
+ const anchor = rangeAnchor ?? cursor;
401
+ if (rangeAnchor === null)
402
+ setRangeAnchor(cursor);
403
+ const newCursor = key.upArrow
404
+ ? Math.max(0, cursor - 1)
405
+ : Math.min(treeItems.length - 1, cursor + 1);
406
+ setCursor(newCursor);
407
+ const start = Math.min(anchor, newCursor);
408
+ const end = Math.max(anchor, newCursor);
409
+ setMarkedIds(new Set(treeItems.slice(start, end + 1).map((t) => t.item.id)));
437
410
  clearWarning();
438
411
  }
439
- if (key.downArrow) {
440
- if (key.shift) {
441
- const anchor = rangeAnchor ?? cursor;
442
- if (rangeAnchor === null)
443
- setRangeAnchor(cursor);
444
- const newCursor = Math.min(treeItems.length - 1, cursor + 1);
445
- setCursor(newCursor);
446
- const start = Math.min(anchor, newCursor);
447
- const end = Math.max(anchor, newCursor);
448
- setMarkedIds(new Set(treeItems.slice(start, end + 1).map((t) => t.item.id)));
412
+ if (matchesCommand('list-navigate', input, key)) {
413
+ if (rangeAnchor !== null)
414
+ setRangeAnchor(null);
415
+ if (key.upArrow) {
416
+ setCursor(Math.max(0, cursor - 1));
449
417
  }
450
418
  else {
451
- if (rangeAnchor !== null)
452
- setRangeAnchor(null);
453
419
  setCursor(Math.min(treeItems.length - 1, cursor + 1));
454
420
  }
455
421
  clearWarning();
456
422
  }
457
- if (key.pageUp) {
458
- setCursor(Math.max(0, cursor - viewport.maxVisible));
459
- clearWarning();
460
- }
461
- if (key.pageDown) {
462
- setCursor(Math.min(treeItems.length - 1, cursor + viewport.maxVisible));
463
- clearWarning();
464
- }
465
- if (key.home) {
466
- setCursor(0);
423
+ if (matchesCommand('list-page', input, key)) {
424
+ if (key.pageUp) {
425
+ setCursor(Math.max(0, cursor - viewport.maxVisible));
426
+ }
427
+ else {
428
+ setCursor(Math.min(treeItems.length - 1, cursor + viewport.maxVisible));
429
+ }
467
430
  clearWarning();
468
431
  }
469
- if (key.end) {
470
- setCursor(treeItems.length - 1);
432
+ if (matchesCommand('list-home-end', input, key)) {
433
+ if (key.home) {
434
+ setCursor(0);
435
+ }
436
+ else {
437
+ setCursor(treeItems.length - 1);
438
+ }
471
439
  clearWarning();
472
440
  }
473
- if (key.rightArrow && treeItems.length > 0) {
441
+ if (matchesCommand('list-expand', input, key) && treeItems.length > 0) {
474
442
  const current = treeItems[cursor];
475
443
  if (current &&
476
444
  current.hasChildren &&
@@ -478,7 +446,7 @@ export function WorkItemList() {
478
446
  toggleExpanded(current.item.id);
479
447
  }
480
448
  }
481
- if (key.leftArrow && treeItems.length > 0) {
449
+ if (matchesCommand('list-collapse', input, key) && treeItems.length > 0) {
482
450
  const current = treeItems[cursor];
483
451
  if (current) {
484
452
  if (current.hasChildren && !collapsedIds.has(current.item.id)) {
@@ -491,30 +459,30 @@ export function WorkItemList() {
491
459
  }
492
460
  }
493
461
  }
494
- if (key.return && treeItems.length > 0) {
462
+ if (matchesCommand('edit', input, key) && treeItems.length > 0) {
495
463
  setFormMode('item');
496
464
  selectWorkItem(treeItems[cursor].item.id);
497
465
  navigate('form');
498
466
  }
499
- if (input === 'q')
467
+ if (matchesCommand('quit', input, key))
500
468
  exit();
501
- if (input === 'i' && capabilities.iterations)
469
+ if (matchesCommand('iterations', input, key) && capabilities.iterations)
502
470
  navigate('iteration-picker');
503
- if (input === 'P') {
471
+ if (matchesCommand('list-pr-list', input, key)) {
504
472
  navigate('pr-list');
505
473
  return;
506
474
  }
507
- if (input === 'B' && gitAvailable) {
475
+ if (matchesCommand('list-branch-manage', input, key) && gitAvailable) {
508
476
  navigate('branch-list');
509
477
  return;
510
478
  }
511
- if (input === ',') {
479
+ if (matchesCommand('settings', input, key)) {
512
480
  if (updateInfo?.updateAvailable) {
513
481
  setSettingsInitialFocus('update-now');
514
482
  }
515
483
  navigate('settings');
516
484
  }
517
- if (input === 'c') {
485
+ if (matchesCommand('create', input, key)) {
518
486
  if (capabilities.templates && templates.length > 0) {
519
487
  openOverlay({ type: 'template-picker' });
520
488
  }
@@ -525,13 +493,13 @@ export function WorkItemList() {
525
493
  navigate('form');
526
494
  }
527
495
  }
528
- if (input === 'd' && treeItems.length > 0) {
496
+ if (matchesCommand('delete', input, key) && treeItems.length > 0) {
529
497
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
530
498
  if (targetIds.length > 0) {
531
499
  openOverlay({ type: 'delete-confirm', targetIds });
532
500
  }
533
501
  }
534
- if (input === 'u') {
502
+ if (matchesCommand('list-undo', input, key)) {
535
503
  const entry = undoStore.getState().popUndo();
536
504
  if (!entry || !backend)
537
505
  return;
@@ -581,7 +549,9 @@ export function WorkItemList() {
581
549
  .setToast(err instanceof Error ? err.message : 'Undo failed');
582
550
  });
583
551
  }
584
- if (input === 'o' && treeItems.length > 0 && backend) {
552
+ if (matchesCommand('open', input, key) &&
553
+ treeItems.length > 0 &&
554
+ backend) {
585
555
  void (async () => {
586
556
  const itemId = treeItems[cursor].item.id;
587
557
  await backend.openItem(itemId);
@@ -591,14 +561,19 @@ export function WorkItemList() {
591
561
  .catch(() => { });
592
562
  })().catch(() => { });
593
563
  }
594
- if (input === 'b' && gitAvailable && treeItems.length > 0) {
564
+ if (matchesCommand('branch', input, key) &&
565
+ gitAvailable &&
566
+ treeItems.length > 0) {
595
567
  const item = treeItems[cursor].item;
596
568
  const comments = item.comments;
597
569
  try {
598
570
  const itemUrl = backend?.getItemUrl(item.id) || '';
571
+ // Suspend terminal for interactive child process
572
+ process.stdin.setRawMode?.(false);
599
573
  const result = beginImplementation(item, comments, { branchMode, branchCommand, copyToClipboard }, process.cwd(), { itemUrl });
600
- // Restore raw mode after interactive shell changed terminal settings
574
+ // Restore terminal after interactive shell
601
575
  process.stdin.setRawMode?.(true);
576
+ console.clear();
602
577
  let msg = result.resumed
603
578
  ? `Resumed work on #${item.id}`
604
579
  : `Started work on #${item.id}`;
@@ -609,6 +584,7 @@ export function WorkItemList() {
609
584
  }
610
585
  catch (e) {
611
586
  process.stdin.setRawMode?.(true);
587
+ console.clear();
612
588
  setWarning(e instanceof Error ? e.message : 'Failed to start implementation');
613
589
  }
614
590
  void backendDataStore
@@ -616,39 +592,43 @@ export function WorkItemList() {
616
592
  .reloadItem(item.id)
617
593
  .catch(() => { });
618
594
  }
619
- if (input === 'S') {
595
+ if (matchesCommand('status', input, key)) {
620
596
  navigate('status');
621
597
  }
622
- if (input === 'O') {
598
+ if (matchesCommand('sort', input, key)) {
623
599
  openOverlay({ type: 'sort-picker' });
624
600
  }
625
- if (input === 'F') {
601
+ if (matchesCommand('filter', input, key)) {
626
602
  openOverlay({ type: 'filter-picker' });
627
603
  }
628
- if (input === 'V') {
604
+ if (matchesCommand('load-view', input, key)) {
629
605
  openOverlay({ type: 'view-picker' });
630
606
  }
631
- if (input === 'X' && filterCount > 0) {
607
+ if (matchesCommand('clear-filters', input, key) && filterCount > 0) {
632
608
  filterStore.getState().clearFilters();
633
609
  setToast('Filters cleared');
634
610
  }
635
- if (input === 's' && treeItems.length > 0) {
611
+ if (matchesCommand('list-status', input, key) && treeItems.length > 0) {
636
612
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
637
613
  if (targetIds.length > 0) {
638
614
  openOverlay({ type: 'status-picker', targetIds });
639
615
  }
640
616
  }
641
- if (input === 'v') {
617
+ if (matchesCommand('toggle-detail-panel', input, key)) {
642
618
  void configStore
643
619
  .getState()
644
620
  .update({ showDetailPanel: !showDetailPanel })
645
621
  .catch(() => { });
646
622
  }
647
- if (input === ' ' && showDetailPanel && hasDescription) {
623
+ if (matchesCommand('list-toggle-description', input, key) &&
624
+ showDetailPanel &&
625
+ hasDescription) {
648
626
  setShowFullDescription(true);
649
627
  setDescriptionScrollOffset(0);
650
628
  }
651
- if (input === 'p' && prCapabilities.create && treeItems.length > 0) {
629
+ if (matchesCommand('list-pr-create', input, key) &&
630
+ prCapabilities.create &&
631
+ treeItems.length > 0) {
652
632
  const item = treeItems[cursor]?.item;
653
633
  if (item) {
654
634
  const cwd = process.cwd();
@@ -670,14 +650,16 @@ export function WorkItemList() {
670
650
  });
671
651
  }
672
652
  }
673
- if (key.tab && capabilities.customTypes && types.length > 0) {
653
+ if (matchesCommand('list-tab', input, key) &&
654
+ capabilities.customTypes &&
655
+ types.length > 0) {
674
656
  const currentIdx = types.indexOf(activeType ?? '');
675
657
  const nextType = types[(currentIdx + 1) % types.length];
676
658
  setActiveType(nextType);
677
659
  setCursor(0);
678
660
  clearWarning();
679
661
  }
680
- if (input === 'r' && syncManager) {
662
+ if (matchesCommand('sync', input, key) && syncManager) {
681
663
  void syncManager
682
664
  .sync()
683
665
  .then(() => {
@@ -687,12 +669,12 @@ export function WorkItemList() {
687
669
  // Errors recorded in syncStatus by SyncManager
688
670
  });
689
671
  }
690
- if (input === 'm' && treeItems.length > 0) {
672
+ if (matchesCommand('mark', input, key) && treeItems.length > 0) {
691
673
  setRangeAnchor(null);
692
674
  const itemId = treeItems[cursor].item.id;
693
675
  toggleMarked(itemId);
694
676
  }
695
- if (input === 'M' && treeItems.length > 0) {
677
+ if (matchesCommand('clear-marks', input, key) && treeItems.length > 0) {
696
678
  setRangeAnchor(null);
697
679
  const visibleIds = treeItems.map((t) => t.item.id);
698
680
  const allMarked = visibleIds.every((id) => markedIds.has(id));
@@ -703,10 +685,10 @@ export function WorkItemList() {
703
685
  setMarkedIds(new Set(visibleIds));
704
686
  }
705
687
  }
706
- if (input === 'x' && treeItems.length > 0) {
688
+ if (matchesCommand('bulk-menu', input, key) && treeItems.length > 0) {
707
689
  openOverlay({ type: 'bulk-menu' });
708
690
  }
709
- if (input === 'y' &&
691
+ if (matchesCommand('set-priority', input, key) &&
710
692
  capabilities.fields.priority &&
711
693
  treeItems.length > 0) {
712
694
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
@@ -714,13 +696,15 @@ export function WorkItemList() {
714
696
  openOverlay({ type: 'priority-picker', targetIds });
715
697
  }
716
698
  }
717
- if (input === 'g' && capabilities.fields.parent && treeItems.length > 0) {
699
+ if (matchesCommand('list-parent', input, key) &&
700
+ capabilities.fields.parent &&
701
+ treeItems.length > 0) {
718
702
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
719
703
  if (targetIds.length > 0) {
720
704
  openOverlay({ type: 'parent-input', targetIds });
721
705
  }
722
706
  }
723
- if (input === 'a' &&
707
+ if (matchesCommand('set-assignee', input, key) &&
724
708
  capabilities.fields.assignee &&
725
709
  treeItems.length > 0) {
726
710
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
@@ -728,13 +712,17 @@ export function WorkItemList() {
728
712
  openOverlay({ type: 'assignee-input', targetIds });
729
713
  }
730
714
  }
731
- if (input === 'l' && capabilities.fields.labels && treeItems.length > 0) {
715
+ if (matchesCommand('set-labels', input, key) &&
716
+ capabilities.fields.labels &&
717
+ treeItems.length > 0) {
732
718
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
733
719
  if (targetIds.length > 0) {
734
720
  openOverlay({ type: 'labels-input', targetIds });
735
721
  }
736
722
  }
737
- if (input === 't' && capabilities.customTypes && treeItems.length > 0) {
723
+ if (matchesCommand('set-type', input, key) &&
724
+ capabilities.customTypes &&
725
+ treeItems.length > 0) {
738
726
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
739
727
  if (targetIds.length > 0) {
740
728
  openOverlay({ type: 'type-picker', targetIds });
@@ -752,6 +740,11 @@ export function WorkItemList() {
752
740
  gitAvailable,
753
741
  hasActiveFilters: filterCount > 0,
754
742
  hasSavedViews: savedViews.length > 0,
743
+ hasSelectedBranch: false,
744
+ isCurrentBranch: false,
745
+ hasWorktree: false,
746
+ hasPrCreateCapability: false,
747
+ hasSelectedPr: false,
755
748
  };
756
749
  const paletteCommands = useMemo(() => getVisibleCommands(commandContext), [
757
750
  commandContext.markedCount,
@@ -936,8 +929,10 @@ export function WorkItemList() {
936
929
  const comments = item.comments;
937
930
  try {
938
931
  const itemUrl = backend?.getItemUrl(item.id) || '';
932
+ process.stdin.setRawMode?.(false);
939
933
  const result = beginImplementation(item, comments, { branchMode, branchCommand, copyToClipboard }, process.cwd(), { itemUrl });
940
934
  process.stdin.setRawMode?.(true);
935
+ console.clear();
941
936
  let msg = result.resumed
942
937
  ? `Resumed work on #${item.id}`
943
938
  : `Started work on #${item.id}`;
@@ -948,6 +943,7 @@ export function WorkItemList() {
948
943
  }
949
944
  catch (e) {
950
945
  process.stdin.setRawMode?.(true);
946
+ console.clear();
951
947
  setWarning(e instanceof Error ? e.message : 'Failed to start implementation');
952
948
  }
953
949
  void backendDataStore
@@ -1632,6 +1628,6 @@ export function WorkItemList() {
1632
1628
  else {
1633
1629
  closeOverlay();
1634
1630
  }
1635
- }, onCancel: () => closeOverlay() })) : toast ? (_jsxs(Box, { children: [_jsx(Text, { color: success, children: toast.message }), positionText && _jsxs(Text, { dimColor: mutedDim, children: [" ", positionText] })] })) : (_jsxs(Box, { children: [_jsx(Text, { dimColor: mutedDim, children: buildHelpText(terminalWidth - (positionText ? positionText.length + 2 : 0)) }), positionText && _jsxs(Text, { dimColor: mutedDim, children: [" ", positionText] })] })) }), warning && (_jsx(Box, { children: _jsxs(Text, { color: warningColor, children: ["\u26A0 ", warning] }) })), updateInfo?.updateAvailable && activeOverlay === null && (_jsx(Box, { children: _jsxs(Text, { color: warningColor, children: ["Update available: ", updateInfo.current, " \u2192 ", updateInfo.latest, " Press , to update in Settings"] }) }))] }));
1631
+ }, onCancel: () => closeOverlay() })) : toast ? (_jsxs(Box, { children: [_jsx(Text, { color: success, children: toast.message }), positionText && _jsxs(Text, { dimColor: mutedDim, children: [" ", positionText] })] })) : (_jsxs(Box, { children: [_jsx(Text, { dimColor: mutedDim, children: buildFooterHints('list', commandContext, terminalWidth - (positionText ? positionText.length + 2 : 0)) }), positionText && _jsxs(Text, { dimColor: mutedDim, children: [" ", positionText] })] })) }), warning && (_jsx(Box, { children: _jsxs(Text, { color: warningColor, children: ["\u26A0 ", warning] }) })), updateInfo?.updateAvailable && activeOverlay === null && (_jsx(Box, { children: _jsxs(Text, { color: warningColor, children: ["Update available: ", updateInfo.current, " \u2192 ", updateInfo.latest, " Press , to update in Settings"] }) }))] }));
1636
1632
  }
1637
1633
  //# sourceMappingURL=WorkItemList.js.map