diff --git a/internal/tui/calendar_views.go b/internal/tui/calendar_views.go index 26bffe68..62ec5ae8 100644 --- a/internal/tui/calendar_views.go +++ b/internal/tui/calendar_views.go @@ -652,7 +652,7 @@ func renderDayView(events, habits, countdowns []Recording, anchor, now time.Time // The day names itself above its hours — the subnav carries the calendar and the // view mode, so which day this is has nowhere else to be said — and the keys that - // move it sit on the same line, where the cover puts "x to peek". + // move it sit on the same line, where the cover puts "z to peek". b.WriteString(hintedSectionHeader(anchor.Local().Format("Monday, January 2"), hint, width)) b.WriteString("\n") diff --git a/internal/tui/content.go b/internal/tui/content.go index 84d2c38d..4a592de8 100644 --- a/internal/tui/content.go +++ b/internal/tui/content.go @@ -569,7 +569,7 @@ func (c *contentList) view() string { if label := c.sectionLabelAt(i); label != "" { if c.cover != coverNone && sectionOf(p) == sectionPreviouslySeen { - fmt.Fprintln(&b, hintedSectionHeader(label, "x to cover", c.width)) + fmt.Fprintln(&b, hintedSectionHeader(label, "z to cover", c.width)) } else { fmt.Fprintln(&b, sectionHeader(label, c.width)) } @@ -695,7 +695,7 @@ func (c *contentList) view() string { // The threads themselves are not rendered at all — that is the whole point of a // cover, and it is why the art can have every row the postings did not use. func (c *contentList) coverView(hidden, rowsUsed int) string { - hint := fmt.Sprintf("%d hidden · x to peek", hidden) + hint := fmt.Sprintf("%d hidden · z to peek", hidden) header := hintedSectionHeader(sectionPreviouslySeen.label(), hint, c.width) rows := c.height - rowsUsed - 1 @@ -716,7 +716,7 @@ func sectionHeader(label string, width int) string { } // hintedSectionHeader is a section label with a hint on its right, where the HEY web -// app puts a section's buttons: "Previously Seen ──── 34 hidden · x to peek", or +// app puts a section's buttons: "Previously Seen ──── 34 hidden · z to peek", or // "Habits ──── b to manage". func hintedSectionHeader(label, hint string, width int) string { rule := lipgloss.NewStyle().Foreground(colorChrome) diff --git a/internal/tui/covers_test.go b/internal/tui/covers_test.go index 79d438b6..8f087805 100644 --- a/internal/tui/covers_test.go +++ b/internal/tui/covers_test.go @@ -556,7 +556,7 @@ func TestCoverHidesPreviouslySeen(t *testing.T) { if !strings.Contains(view, sectionPreviouslySeen.label()) { t.Error("covered list dropped the Previously Seen divider") } - if !strings.Contains(view, "3 hidden · x to peek") { + if !strings.Contains(view, "3 hidden · z to peek") { t.Error("covered list gave no hint about what is under the cover") } for _, posting := range list.postings[1:] { @@ -599,7 +599,7 @@ func TestPeekingLiftsTheCover(t *testing.T) { if !strings.Contains(view, list.postings[2].Name) { t.Error("peeking did not reveal the seen threads") } - if !strings.Contains(view, "x to cover") { + if !strings.Contains(view, "z to cover") { t.Error("a peeked list does not say how to put the cover back") } @@ -627,7 +627,7 @@ func TestCoverDropsTheArtBeforeTheDivider(t *testing.T) { list := coveredList(coverTopo, coverMinRows+2, false, false, true) view := list.view() - if !strings.Contains(view, "1 hidden · x to peek") { + if !strings.Contains(view, "1 hidden · z to peek") { t.Error("a short covered list lost its divider") } if rows := strings.Count(view, "\n") + 1; rows > coverMinRows+2 { @@ -643,7 +643,7 @@ func TestCoverWithNothingUnread(t *testing.T) { t.Errorf("itemCount = %d, want 0", got) } view := list.view() - if !strings.Contains(view, "2 hidden · x to peek") { + if !strings.Contains(view, "2 hidden · z to peek") { t.Error("an all-read Imbox does not say what is under the cover") } if rows := strings.Count(view, "\n") + 1; rows != 20 { diff --git a/internal/tui/mail.go b/internal/tui/mail.go index ac9482a8..1e1962ec 100644 --- a/internal/tui/mail.go +++ b/internal/tui/mail.go @@ -955,7 +955,7 @@ func (v *mailView) HelpBindings() []helpBinding { } bindings := []helpBinding{ {"enter", "open"}, - {"space", "select"}, + {"space/x", "select"}, {"ctrl+b", "bulk reply"}, {"r", "reply"}, {"f", "forward"}, @@ -992,7 +992,7 @@ func (v *mailView) HelpBindings() []helpBinding { {"/", "search"}, {"ctrl+s", "screener"}, {"c", "compose"}, - {"space", "select"}, + {"space/x", "select"}, {"ctrl+b", "bulk reply"}, {"r", "reply"}, {"f", "forward"}, @@ -1015,9 +1015,9 @@ func (v *mailView) HelpBindings() []helpBinding { helpBinding{"ctrl+r", "reload"}, ) if v.postingList.cover != coverNone { - peek := helpBinding{"x", "peek under cover"} + peek := helpBinding{"z", "peek under cover"} if v.postingList.coverPeeked { - peek = helpBinding{"x", "cover"} + peek = helpBinding{"z", "cover"} } bindings = append(bindings, peek) } @@ -1316,7 +1316,7 @@ func (v *mailView) HandleContentKey(msg tea.KeyPressMsg) tea.Cmd { case "j": v.seenList.moveDown() return v.loadMoreSeenPostings() - case " ", "space": + case " ", "space", "x": v.seenList.toggleSelected() return nil case "ctrl+b": @@ -1356,7 +1356,7 @@ func (v *mailView) HandleContentKey(msg tea.KeyPressMsg) tea.Cmd { return v.startSearch() case "c": return v.startCompose() - case " ", "space": + case " ", "space", "x": v.postingList.toggleSelected() return nil case "ctrl+b": @@ -1370,7 +1370,7 @@ func (v *mailView) HandleContentKey(msg tea.KeyPressMsg) tea.Cmd { return v.startFolderPicker() case "n", "N": return v.startCollectionPicker() - case "x": + case "z": v.postingList.toggleCoverPeek() return nil case "ctrl+v": diff --git a/internal/tui/mail_test.go b/internal/tui/mail_test.go index 8d1ea5c4..9fc6a5da 100644 --- a/internal/tui/mail_test.go +++ b/internal/tui/mail_test.go @@ -2786,16 +2786,43 @@ func TestMailViewHaystackPickerAliases(t *testing.T) { } } -func TestMailViewCoverPeekUsesX(t *testing.T) { +func TestMailViewCoverPeekUsesZ(t *testing.T) { v := mailWithPostings() v.postingList.setCover(coverTopo) - v.HandleContentKey(keyPress("x")) + v.HandleContentKey(keyPress("z")) if !v.postingList.coverPeeked { - t.Fatal("x did not lift the cover") + t.Fatal("z did not lift the cover") + } + v.HandleContentKey(keyPress("z")) + if v.postingList.coverPeeked { + t.Fatal("z did not replace the cover") } +} + +// x selects like space does, matching the HEY desktop app — including on a covered +// Imbox, where x used to lift the cover (that is z's key now). +func TestMailViewSelectsWithXLikeSpace(t *testing.T) { + v := mailWithPostings() + v.postingList.setCover(coverTopo) + v.HandleContentKey(keyPress("x")) + if ids := v.postingList.selectedIDs(); len(ids) != 1 || ids[0] != 100 { + t.Fatalf("selected after x = %v, want [100]", ids) + } if v.postingList.coverPeeked { - t.Fatal("x did not replace the cover") + t.Error("x lifted the cover instead of selecting") + } + v.HandleContentKey(keyPress("x")) + if ids := v.postingList.selectedIDs(); len(ids) != 0 { + t.Errorf("selected after second x = %v, want none", ids) + } + + seen := mailWithPostings() + seen.seenActive = true + seen.seenList.setPostings(testPostings()) + seen.HandleContentKey(keyPress("x")) + if ids := seen.seenList.selectedIDs(); len(ids) != 1 || ids[0] != 100 { + t.Errorf("seen screen selected after x = %v, want [100]", ids) } } @@ -3616,7 +3643,7 @@ func TestMailViewHelpBindingsNamePreviouslySeen(t *testing.T) { v.seenActive = true bindings := v.HelpBindings() - for _, key := range []string{"enter", "space", "ctrl+b", "a", "l", "u", "t", "v", "b", "n"} { + for _, key := range []string{"enter", "space/x", "ctrl+b", "a", "l", "u", "t", "v", "b", "n"} { if !hasHelpBinding(bindings, key) { t.Errorf("seen screen help misses %q: %+v", key, bindings) }