revspec 0.8.1 → 0.8.2
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.
- package/README.md +11 -12
- package/package.json +1 -1
- package/src/tui/app.ts +51 -69
- package/src/tui/comment-input.ts +4 -1
- package/src/tui/confirm.ts +1 -1
- package/src/tui/help.ts +3 -3
- package/src/tui/pager.ts +1 -1
- package/src/tui/spinner.ts +1 -1
- package/src/tui/thread-list.ts +2 -2
- package/src/tui/ui/hint-bar.ts +9 -3
- package/src/tui/ui/keymap.ts +3 -3
package/README.md
CHANGED
|
@@ -54,12 +54,18 @@ Opens a TUI with vim-style navigation. Press `c` on any line to open a thread an
|
|
|
54
54
|
| `j/k` | Move cursor down/up |
|
|
55
55
|
| `gg` / `G` | Go to top / bottom |
|
|
56
56
|
| `Ctrl+D/U` | Half page down/up |
|
|
57
|
+
| `H/M/L` | Jump to screen top / middle / bottom |
|
|
57
58
|
| `zz` | Center cursor line in viewport |
|
|
58
59
|
| `/` | Search (smartcase) |
|
|
59
60
|
| `n/N` | Next/prev search match |
|
|
60
61
|
| `Esc` | Clear search highlights |
|
|
61
62
|
| `]t/[t` | Next/prev thread |
|
|
62
63
|
| `]r/[r` | Next/prev unread AI reply |
|
|
64
|
+
| `]1/[1` | Next/prev h1 heading |
|
|
65
|
+
| `]2/[2` | Next/prev h2 heading |
|
|
66
|
+
| `]3/[3` | Next/prev h3 heading |
|
|
67
|
+
| `Ctrl+O/I` | Jump list back/forward |
|
|
68
|
+
| `''` | Jump to previous position |
|
|
63
69
|
|
|
64
70
|
**Review**
|
|
65
71
|
|
|
@@ -69,7 +75,7 @@ Opens a TUI with vim-style navigation. Press `c` on any line to open a thread an
|
|
|
69
75
|
| `r` | Resolve thread (toggle) |
|
|
70
76
|
| `R` | Resolve all pending |
|
|
71
77
|
| `dd` | Delete thread (with confirm) |
|
|
72
|
-
| `t` | List threads |
|
|
78
|
+
| `t` | List threads (`Ctrl+F` to filter all/active/resolved) |
|
|
73
79
|
| `S` | Submit for rewrite (AI updates spec, TUI reloads) |
|
|
74
80
|
| `A` | Approve spec (finalize and exit) |
|
|
75
81
|
|
|
@@ -78,24 +84,17 @@ Opens a TUI with vim-style navigation. Press `c` on any line to open a thread an
|
|
|
78
84
|
| Key | Action |
|
|
79
85
|
|-----|--------|
|
|
80
86
|
| `:q` | Quit (warns if unresolved threads) |
|
|
81
|
-
| `:q!` | Force quit |
|
|
87
|
+
| `:q!` | Force quit (also `:wq!`, `:qa!`, etc.) |
|
|
82
88
|
| `:{N}` | Jump to line N |
|
|
83
89
|
| `Ctrl+C` | Force quit |
|
|
84
90
|
| `?` | Help |
|
|
85
91
|
|
|
86
|
-
**Popups**
|
|
87
|
-
|
|
88
|
-
| Key | Action |
|
|
89
|
-
|-----|--------|
|
|
90
|
-
| `y/Enter` | Confirm / select |
|
|
91
|
-
| `q/Esc` | Cancel / close |
|
|
92
|
-
|
|
93
92
|
### Thread popup
|
|
94
93
|
|
|
95
|
-
The thread popup has two modes:
|
|
94
|
+
The thread popup has two vim-style modes, indicated by border color and label:
|
|
96
95
|
|
|
97
|
-
- **Insert mode** — type your comment, `Tab` sends, `Esc` switches to normal mode
|
|
98
|
-
- **Normal mode** — `j/k` and `Ctrl+D/U` scroll the conversation, `gg/G` top/bottom, `c` to reply, `r` to resolve, `q/Esc` to close
|
|
96
|
+
- **Insert mode** (green border) — type your comment, `Tab` sends, `Esc` switches to normal mode
|
|
97
|
+
- **Normal mode** (blue border) — `j/k` and `Ctrl+D/U` scroll the conversation, `gg/G` top/bottom, `c` to reply, `r` to resolve, `q/Esc` to close
|
|
99
98
|
|
|
100
99
|
### Markdown rendering
|
|
101
100
|
|
package/package.json
CHANGED
package/src/tui/app.ts
CHANGED
|
@@ -124,6 +124,15 @@ export async function runTui(
|
|
|
124
124
|
// Command mode state
|
|
125
125
|
let commandBuffer: string | null = null;
|
|
126
126
|
|
|
127
|
+
// Transient message timer — prevents stale timeouts from clobbering each other
|
|
128
|
+
let messageTimer: ReturnType<typeof setTimeout> | null = null;
|
|
129
|
+
function showTransient(message: string, icon?: import("./status-bar").MessageIcon, ms = 1500): void {
|
|
130
|
+
if (messageTimer) clearTimeout(messageTimer);
|
|
131
|
+
setBottomBarMessage(bottomBar, message, icon);
|
|
132
|
+
renderer.requestRender();
|
|
133
|
+
messageTimer = setTimeout(() => { messageTimer = null; refreshPager(); }, ms);
|
|
134
|
+
}
|
|
135
|
+
|
|
127
136
|
// Jump list — mirrors vim's :jumps behavior.
|
|
128
137
|
// pushJump() is called BEFORE each big jump to record the departure position.
|
|
129
138
|
// Ctrl+O traverses backward, Ctrl+I forward. Making a new jump while in the
|
|
@@ -219,22 +228,22 @@ export async function runTui(
|
|
|
219
228
|
|
|
220
229
|
// Process command buffer input
|
|
221
230
|
function processCommand(cmd: string, resolve: () => void): "exit" | "stay" {
|
|
222
|
-
|
|
231
|
+
const forceQuit = ["q!", "qa!", "wq!", "wqa!", "qw!", "qwa!"];
|
|
232
|
+
const safeQuit = ["q", "qa", "wq", "wqa", "qw", "qwa"];
|
|
233
|
+
if (forceQuit.includes(cmd)) {
|
|
234
|
+
exitTui(resolve, "session-end");
|
|
235
|
+
return "exit";
|
|
236
|
+
}
|
|
237
|
+
if (safeQuit.includes(cmd)) {
|
|
223
238
|
const { open, pending } = state.activeThreadCount();
|
|
224
239
|
const total = open + pending;
|
|
225
240
|
if (total > 0) {
|
|
226
|
-
|
|
227
|
-
renderer.requestRender();
|
|
228
|
-
setTimeout(() => { refreshPager(); }, 2000);
|
|
241
|
+
showTransient(`${total} unresolved thread(s). Use :q! to force quit`, "warn", 2000);
|
|
229
242
|
return "stay";
|
|
230
243
|
}
|
|
231
244
|
exitTui(resolve, "session-end");
|
|
232
245
|
return "exit";
|
|
233
246
|
}
|
|
234
|
-
if (cmd === "q!") {
|
|
235
|
-
exitTui(resolve, "session-end");
|
|
236
|
-
return "exit";
|
|
237
|
-
}
|
|
238
247
|
// :{N} — jump to line number
|
|
239
248
|
const lineNum = parseInt(cmd, 10);
|
|
240
249
|
if (!isNaN(lineNum) && lineNum > 0) {
|
|
@@ -244,9 +253,7 @@ export async function runTui(
|
|
|
244
253
|
refreshPager();
|
|
245
254
|
return "stay";
|
|
246
255
|
}
|
|
247
|
-
|
|
248
|
-
renderer.requestRender();
|
|
249
|
-
setTimeout(() => { refreshPager(); }, 1500);
|
|
256
|
+
showTransient(`Unknown command: ${cmd}`, "warn");
|
|
250
257
|
return "stay";
|
|
251
258
|
}
|
|
252
259
|
|
|
@@ -448,7 +455,7 @@ export async function runTui(
|
|
|
448
455
|
|
|
449
456
|
refreshPager();
|
|
450
457
|
if (state.threads.length === 0) {
|
|
451
|
-
setBottomBarMessage(bottomBar, "Navigate to a line and press c to
|
|
458
|
+
setBottomBarMessage(bottomBar, "Navigate to a line and press c to comment | ? for help", "info");
|
|
452
459
|
renderer.requestRender();
|
|
453
460
|
}
|
|
454
461
|
renderer.start();
|
|
@@ -626,17 +633,13 @@ export async function runTui(
|
|
|
626
633
|
ensureCursorVisible();
|
|
627
634
|
refreshPager();
|
|
628
635
|
if (wrapped) {
|
|
629
|
-
|
|
630
|
-
renderer.requestRender();
|
|
631
|
-
setTimeout(() => { refreshPager(); }, 1200);
|
|
636
|
+
showTransient("Search wrapped to top", "info", 1200);
|
|
632
637
|
}
|
|
633
638
|
} else {
|
|
634
639
|
refreshPager();
|
|
635
640
|
}
|
|
636
641
|
} else {
|
|
637
|
-
|
|
638
|
-
renderer.requestRender();
|
|
639
|
-
setTimeout(() => { refreshPager(); }, 1500);
|
|
642
|
+
showTransient("No active search \u2014 use / to search");
|
|
640
643
|
}
|
|
641
644
|
break;
|
|
642
645
|
case "search-prev":
|
|
@@ -649,17 +652,13 @@ export async function runTui(
|
|
|
649
652
|
ensureCursorVisible();
|
|
650
653
|
refreshPager();
|
|
651
654
|
if (wrapped) {
|
|
652
|
-
|
|
653
|
-
renderer.requestRender();
|
|
654
|
-
setTimeout(() => { refreshPager(); }, 1200);
|
|
655
|
+
showTransient("Search wrapped to bottom", "info", 1200);
|
|
655
656
|
}
|
|
656
657
|
} else {
|
|
657
658
|
refreshPager();
|
|
658
659
|
}
|
|
659
660
|
} else {
|
|
660
|
-
|
|
661
|
-
renderer.requestRender();
|
|
662
|
-
setTimeout(() => { refreshPager(); }, 1500);
|
|
661
|
+
showTransient("No active search \u2014 use / to search");
|
|
663
662
|
}
|
|
664
663
|
break;
|
|
665
664
|
case "comment":
|
|
@@ -676,37 +675,33 @@ export async function runTui(
|
|
|
676
675
|
state.markRead(thread.id);
|
|
677
676
|
appendEvent(jsonlPath, { type: wasResolved ? "unresolve" : "resolve", threadId: thread.id, author: "reviewer", ts: Date.now() });
|
|
678
677
|
refreshPager();
|
|
679
|
-
|
|
678
|
+
showTransient(
|
|
680
679
|
wasResolved ? `Reopened thread #${thread.id}` : `Resolved thread #${thread.id}`,
|
|
681
680
|
"success");
|
|
682
|
-
renderer.requestRender();
|
|
683
|
-
setTimeout(() => { refreshPager(); }, 1500);
|
|
684
681
|
} else {
|
|
685
|
-
|
|
686
|
-
renderer.requestRender();
|
|
687
|
-
setTimeout(() => { refreshPager(); }, 1500);
|
|
682
|
+
showTransient("No thread on this line");
|
|
688
683
|
}
|
|
689
684
|
break;
|
|
690
685
|
}
|
|
691
686
|
case "resolve-all": {
|
|
692
687
|
const { pending } = state.activeThreadCount();
|
|
688
|
+
if (pending === 0) {
|
|
689
|
+
showTransient("No pending threads");
|
|
690
|
+
break;
|
|
691
|
+
}
|
|
693
692
|
const pendingThreads = state.threads.filter(t => t.status === "pending");
|
|
694
693
|
state.resolveAllPending();
|
|
695
694
|
for (const t of pendingThreads) {
|
|
696
695
|
appendEvent(jsonlPath, { type: "resolve", threadId: t.id, author: "reviewer", ts: Date.now() });
|
|
697
696
|
}
|
|
698
697
|
refreshPager();
|
|
699
|
-
|
|
700
|
-
renderer.requestRender();
|
|
701
|
-
setTimeout(() => { refreshPager(); }, 1500);
|
|
698
|
+
showTransient(`Resolved ${pending} pending thread(s)`, "success");
|
|
702
699
|
break;
|
|
703
700
|
}
|
|
704
701
|
case "delete-draft": {
|
|
705
702
|
const thread = state.threadAtLine(state.cursorLine);
|
|
706
703
|
if (!thread) {
|
|
707
|
-
|
|
708
|
-
renderer.requestRender();
|
|
709
|
-
setTimeout(() => { refreshPager(); }, 1500);
|
|
704
|
+
showTransient("No thread on this line");
|
|
710
705
|
break;
|
|
711
706
|
}
|
|
712
707
|
const deleteOverlay = createConfirm({
|
|
@@ -718,9 +713,7 @@ export async function runTui(
|
|
|
718
713
|
state.deleteThread(thread.id);
|
|
719
714
|
appendEvent(jsonlPath, { type: "delete", threadId: thread.id, author: "reviewer", ts: Date.now() });
|
|
720
715
|
refreshPager();
|
|
721
|
-
|
|
722
|
-
renderer.requestRender();
|
|
723
|
-
setTimeout(() => { refreshPager(); }, 1500);
|
|
716
|
+
showTransient(`Deleted thread #${thread.id}`, "success");
|
|
724
717
|
},
|
|
725
718
|
onCancel: () => {
|
|
726
719
|
dismissOverlay();
|
|
@@ -751,9 +744,7 @@ export async function runTui(
|
|
|
751
744
|
clearInterval(activeSpecPoll!);
|
|
752
745
|
activeSpecPoll = null;
|
|
753
746
|
dismissOverlay();
|
|
754
|
-
|
|
755
|
-
renderer.requestRender();
|
|
756
|
-
setTimeout(() => { refreshPager(); }, 3000);
|
|
747
|
+
showTransient("AI did not update the spec. Press S to resubmit.", "warn", 3000);
|
|
757
748
|
},
|
|
758
749
|
});
|
|
759
750
|
showOverlay(spinnerOverlay);
|
|
@@ -774,9 +765,7 @@ export async function runTui(
|
|
|
774
765
|
searchQuery = null;
|
|
775
766
|
ensureCursorVisible();
|
|
776
767
|
refreshPager();
|
|
777
|
-
|
|
778
|
-
renderer.requestRender();
|
|
779
|
-
setTimeout(() => { refreshPager(); }, 2500);
|
|
768
|
+
showTransient("Spec rewritten \u2014 review cleared", "success", 2500);
|
|
780
769
|
}
|
|
781
770
|
} catch {}
|
|
782
771
|
}, 500);
|
|
@@ -799,30 +788,30 @@ export async function runTui(
|
|
|
799
788
|
});
|
|
800
789
|
break;
|
|
801
790
|
case "next-thread": {
|
|
802
|
-
const
|
|
803
|
-
if (
|
|
791
|
+
const nextT = state.nextThread();
|
|
792
|
+
if (nextT !== null) {
|
|
793
|
+
const wrapped = nextT <= state.cursorLine;
|
|
804
794
|
savePrevPosition();
|
|
805
|
-
state.cursorLine =
|
|
795
|
+
state.cursorLine = nextT;
|
|
806
796
|
ensureCursorVisible();
|
|
807
797
|
refreshPager();
|
|
798
|
+
if (wrapped) showTransient("Wrapped to first thread", "info", 1200);
|
|
808
799
|
} else {
|
|
809
|
-
|
|
810
|
-
renderer.requestRender();
|
|
811
|
-
setTimeout(() => { refreshPager(); }, 1500);
|
|
800
|
+
showTransient("No threads");
|
|
812
801
|
}
|
|
813
802
|
break;
|
|
814
803
|
}
|
|
815
804
|
case "prev-thread": {
|
|
816
|
-
const
|
|
817
|
-
if (
|
|
805
|
+
const prevT = state.prevThread();
|
|
806
|
+
if (prevT !== null) {
|
|
807
|
+
const wrapped = prevT >= state.cursorLine;
|
|
818
808
|
savePrevPosition();
|
|
819
|
-
state.cursorLine =
|
|
809
|
+
state.cursorLine = prevT;
|
|
820
810
|
ensureCursorVisible();
|
|
821
811
|
refreshPager();
|
|
812
|
+
if (wrapped) showTransient("Wrapped to last thread", "info", 1200);
|
|
822
813
|
} else {
|
|
823
|
-
|
|
824
|
-
renderer.requestRender();
|
|
825
|
-
setTimeout(() => { refreshPager(); }, 1500);
|
|
814
|
+
showTransient("No threads");
|
|
826
815
|
}
|
|
827
816
|
break;
|
|
828
817
|
}
|
|
@@ -834,9 +823,7 @@ export async function runTui(
|
|
|
834
823
|
ensureCursorVisible();
|
|
835
824
|
refreshPager();
|
|
836
825
|
} else {
|
|
837
|
-
|
|
838
|
-
renderer.requestRender();
|
|
839
|
-
setTimeout(() => { refreshPager(); }, 1500);
|
|
826
|
+
showTransient("No unread replies");
|
|
840
827
|
}
|
|
841
828
|
break;
|
|
842
829
|
}
|
|
@@ -848,9 +835,7 @@ export async function runTui(
|
|
|
848
835
|
ensureCursorVisible();
|
|
849
836
|
refreshPager();
|
|
850
837
|
} else {
|
|
851
|
-
|
|
852
|
-
renderer.requestRender();
|
|
853
|
-
setTimeout(() => { refreshPager(); }, 1500);
|
|
838
|
+
showTransient("No unread replies");
|
|
854
839
|
}
|
|
855
840
|
break;
|
|
856
841
|
}
|
|
@@ -865,9 +850,7 @@ export async function runTui(
|
|
|
865
850
|
ensureCursorVisible();
|
|
866
851
|
refreshPager();
|
|
867
852
|
} else {
|
|
868
|
-
|
|
869
|
-
renderer.requestRender();
|
|
870
|
-
setTimeout(() => { refreshPager(); }, 1500);
|
|
853
|
+
showTransient(`No h${level} headings`);
|
|
871
854
|
}
|
|
872
855
|
break;
|
|
873
856
|
}
|
|
@@ -882,9 +865,7 @@ export async function runTui(
|
|
|
882
865
|
ensureCursorVisible();
|
|
883
866
|
refreshPager();
|
|
884
867
|
} else {
|
|
885
|
-
|
|
886
|
-
renderer.requestRender();
|
|
887
|
-
setTimeout(() => { refreshPager(); }, 1500);
|
|
868
|
+
showTransient(`No h${level} headings`);
|
|
888
869
|
}
|
|
889
870
|
break;
|
|
890
871
|
}
|
|
@@ -931,6 +912,7 @@ export async function runTui(
|
|
|
931
912
|
showSearchOverlay();
|
|
932
913
|
break;
|
|
933
914
|
case "command-mode":
|
|
915
|
+
if (messageTimer) { clearTimeout(messageTimer); messageTimer = null; }
|
|
934
916
|
commandBuffer = "";
|
|
935
917
|
refreshPager();
|
|
936
918
|
break;
|
package/src/tui/comment-input.ts
CHANGED
|
@@ -65,7 +65,7 @@ function createThreadView(
|
|
|
65
65
|
focusedBackgroundColor: theme.backgroundPanel,
|
|
66
66
|
focusedTextColor: theme.text,
|
|
67
67
|
wrapMode: "word",
|
|
68
|
-
placeholder: "
|
|
68
|
+
placeholder: "Type your comment...",
|
|
69
69
|
placeholderColor: theme.textDim,
|
|
70
70
|
initialValue: "",
|
|
71
71
|
});
|
|
@@ -107,6 +107,7 @@ function createThreadView(
|
|
|
107
107
|
onCancel();
|
|
108
108
|
return;
|
|
109
109
|
|
|
110
|
+
case "i":
|
|
110
111
|
case "c":
|
|
111
112
|
enterInsert();
|
|
112
113
|
return;
|
|
@@ -251,6 +252,7 @@ function createThreadView(
|
|
|
251
252
|
mode = "insert";
|
|
252
253
|
textarea.focus();
|
|
253
254
|
dialog.setHints(insertHints);
|
|
255
|
+
dialog.container.borderColor = theme.green;
|
|
254
256
|
renderer.requestRender();
|
|
255
257
|
}
|
|
256
258
|
|
|
@@ -258,6 +260,7 @@ function createThreadView(
|
|
|
258
260
|
mode = "normal";
|
|
259
261
|
textarea.blur();
|
|
260
262
|
dialog.setHints(normalHints);
|
|
263
|
+
dialog.container.borderColor = theme.blue;
|
|
261
264
|
renderer.requestRender();
|
|
262
265
|
}
|
|
263
266
|
|
package/src/tui/confirm.ts
CHANGED
package/src/tui/help.ts
CHANGED
|
@@ -54,7 +54,7 @@ export function createHelp(opts: {
|
|
|
54
54
|
height: Math.min(32, renderer.height - 4),
|
|
55
55
|
top: "10%",
|
|
56
56
|
left: "18%",
|
|
57
|
-
borderColor: theme.
|
|
57
|
+
borderColor: theme.blue,
|
|
58
58
|
onDismiss: onClose,
|
|
59
59
|
hints: HELP_HINTS,
|
|
60
60
|
});
|
|
@@ -77,8 +77,8 @@ export function createHelp(opts: {
|
|
|
77
77
|
]);
|
|
78
78
|
|
|
79
79
|
addHelpSection(dialog.content, renderer, "Thread Popup", [
|
|
80
|
-
" New thread: INSERT mode — type and Tab to send.",
|
|
81
|
-
" Existing thread: NORMAL mode — read conversation,",
|
|
80
|
+
" New thread: INSERT mode (green border) — type and Tab to send.",
|
|
81
|
+
" Existing thread: NORMAL mode (blue border) — read conversation,",
|
|
82
82
|
" c to reply, r to resolve, q/Esc to close.",
|
|
83
83
|
]);
|
|
84
84
|
|
package/src/tui/pager.ts
CHANGED
|
@@ -127,7 +127,7 @@ export function buildPagerNodes(lineNode: TextRenderable, state: ReviewState, se
|
|
|
127
127
|
// Gutter: cursor + indicator + line number (dimmed)
|
|
128
128
|
lineNode.add(TextNodeRenderable.fromString(
|
|
129
129
|
`${prefix}`,
|
|
130
|
-
{ fg: isCursor ? theme.
|
|
130
|
+
{ fg: isCursor ? theme.yellow : theme.textDim, bg: isCursor ? theme.backgroundElement : undefined }
|
|
131
131
|
));
|
|
132
132
|
lineNode.add(TextNodeRenderable.fromString(
|
|
133
133
|
indicator,
|
package/src/tui/spinner.ts
CHANGED
package/src/tui/thread-list.ts
CHANGED
|
@@ -92,7 +92,7 @@ export function createThreadList(opts: ThreadListOptions): ThreadListOverlay {
|
|
|
92
92
|
height: "50%",
|
|
93
93
|
top: "20%",
|
|
94
94
|
left: "22%",
|
|
95
|
-
borderColor: theme.
|
|
95
|
+
borderColor: theme.blue,
|
|
96
96
|
onDismiss: onCancel,
|
|
97
97
|
hints: THREAD_LIST_HINTS,
|
|
98
98
|
});
|
|
@@ -177,7 +177,7 @@ export function createThreadList(opts: ThreadListOptions): ThreadListOverlay {
|
|
|
177
177
|
return;
|
|
178
178
|
}
|
|
179
179
|
if (filtered.length === 0) return;
|
|
180
|
-
if (key.name === "return"
|
|
180
|
+
if (key.name === "return") {
|
|
181
181
|
key.preventDefault();
|
|
182
182
|
key.stopPropagation();
|
|
183
183
|
const selected = select.getSelectedOption();
|
package/src/tui/ui/hint-bar.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TextRenderable, TextNodeRenderable } from "@opentui/core";
|
|
1
|
+
import { TextRenderable, TextNodeRenderable, TextAttributes } from "@opentui/core";
|
|
2
2
|
import { theme } from "./theme";
|
|
3
3
|
|
|
4
4
|
export interface Hint {
|
|
@@ -11,8 +11,14 @@ export function buildHints(text: TextRenderable, hints: Hint[]): void {
|
|
|
11
11
|
text.add(TextNodeRenderable.fromString(" ", {}));
|
|
12
12
|
for (let i = 0; i < hints.length; i++) {
|
|
13
13
|
const h = hints[i];
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// Mode labels (empty action) get distinct styling so they stand out
|
|
15
|
+
const isMode = h.action === "";
|
|
16
|
+
const keyFg = isMode ? (h.key === "INSERT" ? theme.green : theme.blue) : theme.blue;
|
|
17
|
+
const keyAttrs = isMode ? TextAttributes.BOLD : undefined;
|
|
18
|
+
text.add(TextNodeRenderable.fromString(`[${h.key}]`, { fg: keyFg, attributes: keyAttrs }));
|
|
19
|
+
if (!isMode) {
|
|
20
|
+
text.add(TextNodeRenderable.fromString(` ${h.action}`, { fg: theme.textMuted }));
|
|
21
|
+
}
|
|
16
22
|
if (i < hints.length - 1) {
|
|
17
23
|
text.add(TextNodeRenderable.fromString(" ", {}));
|
|
18
24
|
}
|
package/src/tui/ui/keymap.ts
CHANGED
|
@@ -21,7 +21,7 @@ export const PAGER_HINTS = {
|
|
|
21
21
|
|
|
22
22
|
export const THREAD_NORMAL_HINTS: Hint[] = [
|
|
23
23
|
{ key: "NORMAL", action: "" },
|
|
24
|
-
{ key: "c", action: "reply" },
|
|
24
|
+
{ key: "i/c", action: "reply" },
|
|
25
25
|
{ key: "r", action: "resolve" },
|
|
26
26
|
{ key: "q/Esc", action: "close" },
|
|
27
27
|
];
|
|
@@ -36,7 +36,7 @@ export const THREAD_INSERT_HINTS: Hint[] = [
|
|
|
36
36
|
|
|
37
37
|
export const THREAD_LIST_HINTS: Hint[] = [
|
|
38
38
|
{ key: "j/k", action: "navigate" },
|
|
39
|
-
{ key: "
|
|
39
|
+
{ key: "Enter", action: "jump" },
|
|
40
40
|
{ key: "Ctrl+f", action: "filter" },
|
|
41
41
|
{ key: "q/Esc", action: "close" },
|
|
42
42
|
];
|
|
@@ -52,5 +52,5 @@ export const HELP_HINTS: Hint[] = [
|
|
|
52
52
|
|
|
53
53
|
export const CONFIRM_HINTS: Hint[] = [
|
|
54
54
|
{ key: "y/Enter", action: "yes" },
|
|
55
|
-
{ key: "q/Esc", action: "
|
|
55
|
+
{ key: "q/Esc", action: "cancel" },
|
|
56
56
|
];
|