aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-07-23 23:30:42 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-07-23 23:30:42 +0200
commit369c995292b0ae830e077c5845f0b09fbe3508a4 (patch)
tree059b5a397bd45a9f939f8490186f0a759ddbbf22
parent9ec2c438a39542ec485e221d485c43b04d692aca (diff)
downloadlectio-369c995292b0ae830e077c5845f0b09fbe3508a4.tar.gz
lectio-369c995292b0ae830e077c5845f0b09fbe3508a4.zip
tui: honor config all; r-key forces refresh (bypass cache); clamp scroll in Update (no over-scroll)
-rw-r--r--internal/tui/tui.go59
1 files changed, 37 insertions, 22 deletions
diff --git a/internal/tui/tui.go b/internal/tui/tui.go
index a4c14b9..47314b6 100644
--- a/internal/tui/tui.go
+++ b/internal/tui/tui.go
@@ -120,16 +120,18 @@ func shiftDate(date string, days int) string {
}
// fetchCmd issues the readings.Load fetch for the model's current date as a
-// tea.Cmd, resolving to readingsMsg or errMsg. It is a full-day reader, so
-// All is always true; Offline follows cfg.Offline.
-func (m Model) fetchCmd() tea.Cmd {
+// tea.Cmd, resolving to readingsMsg or errMsg. All follows cfg.All (config's
+// gospel-only vs every-part choice); Offline follows cfg.Offline; refresh
+// bypasses the cache (the "r" key), matching the CLI's --refresh.
+func (m Model) fetchCmd(refresh bool) tea.Cmd {
cfg := m.cfg
date := m.date
return func() tea.Msg {
secs, err := readings.Load(cfg, readings.Options{
Date: date,
+ Refresh: refresh,
Offline: cfg.Offline,
- All: true,
+ All: cfg.All,
})
if err != nil {
return errMsg{err}
@@ -140,7 +142,7 @@ func (m Model) fetchCmd() tea.Cmd {
// Init issues the first load.
func (m Model) Init() tea.Cmd {
- return m.fetchCmd()
+ return m.fetchCmd(false)
}
// Update handles key input and fetch results.
@@ -175,38 +177,33 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.date = shiftDate(m.date, -1)
m.loading = true
m.err = nil
- return m, m.fetchCmd()
+ return m, m.fetchCmd(false)
case "right":
m.date = shiftDate(m.date, +1)
m.loading = true
m.err = nil
- return m, m.fetchCmd()
+ return m, m.fetchCmd(false)
case "r":
m.loading = true
m.err = nil
- return m, m.fetchCmd()
+ return m, m.fetchCmd(true)
case "j", "down":
- m.scroll++
+ m.scroll = m.scrollTo(m.scroll + 1)
return m, nil
case "k", "up":
- if m.scroll > 0 {
- m.scroll--
- }
+ m.scroll = m.scrollTo(m.scroll - 1)
return m, nil
case " ":
- m.scroll += pageSize(m.height)
+ m.scroll = m.scrollTo(m.scroll + pageSize(m.height))
return m, nil
case "b":
- m.scroll -= pageSize(m.height)
- if m.scroll < 0 {
- m.scroll = 0
- }
+ m.scroll = m.scrollTo(m.scroll - pageSize(m.height))
return m, nil
case "g":
m.scroll = 0
return m, nil
case "G":
- m.scroll = 1 << 30 // clamped to the last page in View
+ m.scroll = m.scrollTo(1 << 30)
return m, nil
}
}
@@ -239,6 +236,27 @@ func clampScroll(scroll, total, visible int) int {
return scroll
}
+// innerWidth is the reading pane's wrap width: the terminal width minus a
+// small margin, with an 80-column fallback before the first WindowSizeMsg.
+func (m Model) innerWidth() int {
+ w := m.width
+ if w <= 0 {
+ w = 80
+ }
+ iw := w - 2
+ if iw < 20 {
+ iw = 20
+ }
+ return iw
+}
+
+// scrollTo clamps a target scroll offset to the reading's real length, so the
+// view can't scroll past the end -- keeping m.scroll bounded in Update, not
+// merely clamped for display in View.
+func (m Model) scrollTo(s int) int {
+ return clampScroll(s, len(m.bodyLines(m.innerWidth())), pageSize(m.height))
+}
+
// View renders the header (date + active version label), the scrolling
// reading, and the footer keybar.
func (m Model) View() string {
@@ -246,10 +264,7 @@ func (m Model) View() string {
if w <= 0 {
w = 80
}
- innerW := w - 2
- if innerW < 20 {
- innerW = 20
- }
+ innerW := m.innerWidth()
header := headerStyle.Width(w).Render(m.headerText())
footer := footerStyle.Width(w).Render(i18n.Get(m.cfg.UILanguage).FooterKeys)