@sascha384/tic 5.0.0 → 5.2.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 (44) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dist/app.js +4 -1
  3. package/dist/app.js.map +1 -1
  4. package/dist/commands.d.ts +23 -1
  5. package/dist/commands.js +636 -16
  6. package/dist/commands.js.map +1 -1
  7. package/dist/components/BranchList.js +21 -20
  8. package/dist/components/BranchList.js.map +1 -1
  9. package/dist/components/Header.js +6 -2
  10. package/dist/components/Header.js.map +1 -1
  11. package/dist/components/HelpScreen.d.ts +1 -9
  12. package/dist/components/HelpScreen.js +32 -228
  13. package/dist/components/HelpScreen.js.map +1 -1
  14. package/dist/components/IterationPicker.js +3 -2
  15. package/dist/components/IterationPicker.js.map +1 -1
  16. package/dist/components/MarkdownEditor.d.ts +1 -0
  17. package/dist/components/MarkdownEditor.js +173 -0
  18. package/dist/components/MarkdownEditor.js.map +1 -0
  19. package/dist/components/PullRequestList.js +14 -13
  20. package/dist/components/PullRequestList.js.map +1 -1
  21. package/dist/components/Settings.js +31 -26
  22. package/dist/components/Settings.js.map +1 -1
  23. package/dist/components/StatusScreen.js +19 -15
  24. package/dist/components/StatusScreen.js.map +1 -1
  25. package/dist/components/WorkItemForm.js +26 -35
  26. package/dist/components/WorkItemForm.js.map +1 -1
  27. package/dist/components/WorkItemList.d.ts +0 -1
  28. package/dist/components/WorkItemList.js +84 -100
  29. package/dist/components/WorkItemList.js.map +1 -1
  30. package/dist/components/markdownHighlight.d.ts +20 -0
  31. package/dist/components/markdownHighlight.js +195 -0
  32. package/dist/components/markdownHighlight.js.map +1 -0
  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 +1 -0
  37. package/dist/stores/backendDataStore.js.map +1 -1
  38. package/dist/stores/editorStore.d.ts +53 -0
  39. package/dist/stores/editorStore.js +513 -0
  40. package/dist/stores/editorStore.js.map +1 -0
  41. package/dist/stores/navigationStore.d.ts +1 -1
  42. package/dist/stores/navigationStore.js +1 -1
  43. package/dist/stores/navigationStore.js.map +1 -1
  44. 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,7 +561,9 @@ 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 {
@@ -620,39 +592,43 @@ export function WorkItemList() {
620
592
  .reloadItem(item.id)
621
593
  .catch(() => { });
622
594
  }
623
- if (input === 'S') {
595
+ if (matchesCommand('status', input, key)) {
624
596
  navigate('status');
625
597
  }
626
- if (input === 'O') {
598
+ if (matchesCommand('sort', input, key)) {
627
599
  openOverlay({ type: 'sort-picker' });
628
600
  }
629
- if (input === 'F') {
601
+ if (matchesCommand('filter', input, key)) {
630
602
  openOverlay({ type: 'filter-picker' });
631
603
  }
632
- if (input === 'V') {
604
+ if (matchesCommand('load-view', input, key)) {
633
605
  openOverlay({ type: 'view-picker' });
634
606
  }
635
- if (input === 'X' && filterCount > 0) {
607
+ if (matchesCommand('clear-filters', input, key) && filterCount > 0) {
636
608
  filterStore.getState().clearFilters();
637
609
  setToast('Filters cleared');
638
610
  }
639
- if (input === 's' && treeItems.length > 0) {
611
+ if (matchesCommand('list-status', input, key) && treeItems.length > 0) {
640
612
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
641
613
  if (targetIds.length > 0) {
642
614
  openOverlay({ type: 'status-picker', targetIds });
643
615
  }
644
616
  }
645
- if (input === 'v') {
617
+ if (matchesCommand('toggle-detail-panel', input, key)) {
646
618
  void configStore
647
619
  .getState()
648
620
  .update({ showDetailPanel: !showDetailPanel })
649
621
  .catch(() => { });
650
622
  }
651
- if (input === ' ' && showDetailPanel && hasDescription) {
623
+ if (matchesCommand('list-toggle-description', input, key) &&
624
+ showDetailPanel &&
625
+ hasDescription) {
652
626
  setShowFullDescription(true);
653
627
  setDescriptionScrollOffset(0);
654
628
  }
655
- if (input === 'p' && prCapabilities.create && treeItems.length > 0) {
629
+ if (matchesCommand('list-pr-create', input, key) &&
630
+ prCapabilities.create &&
631
+ treeItems.length > 0) {
656
632
  const item = treeItems[cursor]?.item;
657
633
  if (item) {
658
634
  const cwd = process.cwd();
@@ -674,14 +650,16 @@ export function WorkItemList() {
674
650
  });
675
651
  }
676
652
  }
677
- if (key.tab && capabilities.customTypes && types.length > 0) {
653
+ if (matchesCommand('list-tab', input, key) &&
654
+ capabilities.customTypes &&
655
+ types.length > 0) {
678
656
  const currentIdx = types.indexOf(activeType ?? '');
679
657
  const nextType = types[(currentIdx + 1) % types.length];
680
658
  setActiveType(nextType);
681
659
  setCursor(0);
682
660
  clearWarning();
683
661
  }
684
- if (input === 'r' && syncManager) {
662
+ if (matchesCommand('sync', input, key) && syncManager) {
685
663
  void syncManager
686
664
  .sync()
687
665
  .then(() => {
@@ -691,12 +669,12 @@ export function WorkItemList() {
691
669
  // Errors recorded in syncStatus by SyncManager
692
670
  });
693
671
  }
694
- if (input === 'm' && treeItems.length > 0) {
672
+ if (matchesCommand('mark', input, key) && treeItems.length > 0) {
695
673
  setRangeAnchor(null);
696
674
  const itemId = treeItems[cursor].item.id;
697
675
  toggleMarked(itemId);
698
676
  }
699
- if (input === 'M' && treeItems.length > 0) {
677
+ if (matchesCommand('clear-marks', input, key) && treeItems.length > 0) {
700
678
  setRangeAnchor(null);
701
679
  const visibleIds = treeItems.map((t) => t.item.id);
702
680
  const allMarked = visibleIds.every((id) => markedIds.has(id));
@@ -707,10 +685,10 @@ export function WorkItemList() {
707
685
  setMarkedIds(new Set(visibleIds));
708
686
  }
709
687
  }
710
- if (input === 'x' && treeItems.length > 0) {
688
+ if (matchesCommand('bulk-menu', input, key) && treeItems.length > 0) {
711
689
  openOverlay({ type: 'bulk-menu' });
712
690
  }
713
- if (input === 'y' &&
691
+ if (matchesCommand('set-priority', input, key) &&
714
692
  capabilities.fields.priority &&
715
693
  treeItems.length > 0) {
716
694
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
@@ -718,13 +696,15 @@ export function WorkItemList() {
718
696
  openOverlay({ type: 'priority-picker', targetIds });
719
697
  }
720
698
  }
721
- if (input === 'g' && capabilities.fields.parent && treeItems.length > 0) {
699
+ if (matchesCommand('list-parent', input, key) &&
700
+ capabilities.fields.parent &&
701
+ treeItems.length > 0) {
722
702
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
723
703
  if (targetIds.length > 0) {
724
704
  openOverlay({ type: 'parent-input', targetIds });
725
705
  }
726
706
  }
727
- if (input === 'a' &&
707
+ if (matchesCommand('set-assignee', input, key) &&
728
708
  capabilities.fields.assignee &&
729
709
  treeItems.length > 0) {
730
710
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
@@ -732,13 +712,17 @@ export function WorkItemList() {
732
712
  openOverlay({ type: 'assignee-input', targetIds });
733
713
  }
734
714
  }
735
- if (input === 'l' && capabilities.fields.labels && treeItems.length > 0) {
715
+ if (matchesCommand('set-labels', input, key) &&
716
+ capabilities.fields.labels &&
717
+ treeItems.length > 0) {
736
718
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
737
719
  if (targetIds.length > 0) {
738
720
  openOverlay({ type: 'labels-input', targetIds });
739
721
  }
740
722
  }
741
- if (input === 't' && capabilities.customTypes && treeItems.length > 0) {
723
+ if (matchesCommand('set-type', input, key) &&
724
+ capabilities.customTypes &&
725
+ treeItems.length > 0) {
742
726
  const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
743
727
  if (targetIds.length > 0) {
744
728
  openOverlay({ type: 'type-picker', targetIds });
@@ -1644,6 +1628,6 @@ export function WorkItemList() {
1644
1628
  else {
1645
1629
  closeOverlay();
1646
1630
  }
1647
- }, 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"] }) }))] }));
1648
1632
  }
1649
1633
  //# sourceMappingURL=WorkItemList.js.map