feat(tui): reader paging + verse cursor + book-page layout#11
Merged
Conversation
Captures explore/proposal/spec/tasks/verify-report for the paging +
verse cursor change. Verify verdict: PASS WITH WARNINGS (0 critical /
1 warning / 1 suggestion). 151/151 tests green, tsc clean.
Pending: manual PTY smoke for visual cursor + keybind round-trips.
Suggestion to verify at smoke: gutter ▶ uses fg={ACCENT_HEX} AND
attributes={DIM} together — DIM may suppress accent color on some
terminals.
Two UX issues from manual smoke:
1. Single-verse refs like 'john 3:16' showed only that verse instead
of dropping into the chapter page with the cursor on verse 16.
Root cause: useEffect called getPassage, which sliced the chapter
down to ref.verses range.
Fix: add a new application use case getChapter that returns the
full chapter regardless of ref.verses, and have the reader call
it instead. The verse range now serves as the initial cursor
target, not a slice predicate. PassageFetched computes
cursorIndex by looking up the target verse number in the fetched
passage and derives pageStartIndex from cursorIndex / VERSES_PER_PAGE.
Chapter nav (n/p) resets ref.verses to {1, 1} so the cursor
lands on verse 1 of the new chapter rather than carrying the
old verse number into a possibly-shorter chapter.
2. The palette input spanned the whole bordered frame. Wrapped it
in a width=50 box with marginLeft=2 so it reads as an input
field, not the entire reader surface.
Also fixes the verify suggestion: the focused ▶ cursor span was
applying both fg={ACCENT_HEX} and attributes={DIM}, which washed
out the accent on most terminals. DIM now applies only when the
gutter is empty (non-focused rows).
Tests: 151 → 156 (+5: 3 for getChapter use case, 2 for cursor
positioning in PassageFetched). tsc clean.
Two UX bumps: 1. VERSES_PER_PAGE 8 → 15. Closer to the original ui-sketches.md Reading view density. Comfortably fits an 80×24 terminal once word-wrap accounts for the chrome. 2. Word-wrap with hanging indent for verse text. Previously, long verses ran off the right edge or wrapped break-anywhere with no visual continuity. Now: each verse wraps at word boundaries, the first line carries the gutter + verse number, and continuation lines indent 6 chars to align with the text column — matching the original docs/ui-sketches.md sketch shape (line 122). Wrap width derives from useTerminalDimensions() each render, so narrow terminals reflow correctly. Floor at 20 chars to keep the wrap useful on tiny terminals. Tests updated to be VERSES_PER_PAGE-relative (using the constant symbol in place of hardcoded 8/16) so future tuning won't churn the suite. 156/156 still pass, tsc clean.
…e rhythm
Matches docs/ui-sketches.md Reading view (line 122):
- Constrain bordered frame to max 70 chars (configurable via
PAGE_MAX_WIDTH), centered horizontally. Wide terminals no longer
produce a single 200-char box that runs verses off the right edge
or floods the screen with empty margin.
- Falls back to terminal width - 4 on narrower terminals (floor 40).
- 5-char prefix (' 16 ' or '▶16 ') with 5-space hanging indent —
matches the sketch's verse-row layout exactly.
- Blank row between verses for vertical breathing room (also from
the sketch).
- Top padding + box auto-sizes to content so the box no longer
stretches to the terminal floor and looks like a log dump.
Tests: 156/156 still pass (no reducer changes). tsc clean.
INVERSE swaps fg/bg, which on a dark terminal renders as black-on- light — jarring against the rest of the muted page. Switching to fg=ACCENT_HEX, matching docs/ui-sketches.md line 148 'Focused verse: accent color on the line + ▶ marker in the gutter'. The Typography table at line 437 of the same doc said 'selection (inverse)', but the Reading view rules are more specific to this surface and read better on a dark background. Verse text and gutter marker now share the accent token when focused, making the cursor row read as a single highlighted unit instead of a sudden color block.
SDD cycle closed. PASS WITH WARNINGS verify + 4 user-sanctioned post-verify iterations from manual smoke (whole-chapter fetch, denser pages, book-page layout, accent cursor color). 156/156 tests pass. Manual PTY smoke confirmed by user. Out-of-scope follow-ups: dynamic page size from terminal height, cross-chapter cursor flow, two-page open-book spread, long-word wrap, visual page-boundary feedback.
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Builds on PR #10 with the actual reading UX:
[/]move between 15-verse pages within a chapter (silent clamp at edges).↑/↓move a▶marker between verses; rolls across page boundaries; cursor verse rendered in accent blue (matchesdocs/ui-sketches.mdline 148 — "Focused verse: accent color on the line + ▶ marker in the gutter").n/ptake over chapter advance/retreat from[/]. Chapter transitions reset cursor to verse 1.state.kind !== "awaiting", so/(and others) no longer fire while typing into the input.q/Qquit always works regardless of state.getChapteruse case: new application use case returns the whole chapter regardless ofref.verses. Single-verse refs likejohn 3:16now drop you into the chapter page with the cursor positioned on verse 16 instead of showing only that verse.What does NOT change
parseReference(already shipped in PR feat(tui): interactive reader — palette + fetch + bordered frame #10).getPassagestays for the CLI.getChapteris new and reader-only.useReducer, object-dispatch handler table per Rule 13, no useless comments per Rule 14).Verify
Original SDD verify: PASS WITH WARNINGS (0 critical / 1 warning / 1 suggestion). Four post-verify user-directed UX iterations were applied on the same branch after manual PTY smoke:
fbd7d71— Whole-chapter fetch + cursor positioning (john 3:16→ page 2 cursor on verse 16, not single-verse view); palette input width capped; gutter accent washout fix.5a5ef32— Page size 8 → 15; word-wrap with hanging indent; tests refactored to useVERSES_PER_PAGEsymbol.0ac25aa— Book-page layout: capped 70-char column, horizontal centering, blank-line verse rhythm, content-sized box.INVERSE(no more black-on-light block on dark terminals).bun test: 156/156 pass.bun run tsc --noEmit: exits 0.Test plan
bun test156/156Out of scope (next PR candidates)
VERSES_PER_PAGEfrom terminal heightChapterAdvanced)Full SDD trail in `openspec/changes/archive/tui-reader-paging/`.