Skip to content

refactor(terminal): consolidate scroll math into one helper#7

Merged
pro-vi merged 3 commits into
feat/languagefrom
refactor/scroll-consolidation
Jun 10, 2026
Merged

refactor(terminal): consolidate scroll math into one helper#7
pro-vi merged 3 commits into
feat/languagefrom
refactor/scroll-consolidation

Conversation

@pro-vi

@pro-vi pro-vi commented Jun 10, 2026

Copy link
Copy Markdown
Owner

What

Four views each rolled their own scroll logic; only journal used the ScrollableRegion widget. This extracts the shared math into widgets/scroll.ts and routes every view through it — behavior-preserving (no UX change, full suite + language verifier green).

View Before After
journal ScrollableRegion (free-scroll) same widget, now built on the helpers
detail inline clamp + page indicator clampOffset / pageIndicator
browse inline cursor-into-view offsetToShow
settings inline window math windowFor

widgets/scroll.ts — four pure helpers:

  • clampOffset(offset, contentLength, viewport) — free-scroll range clamp
  • offsetToShow(cursor, offset, viewport) — cursor-into-view (stateful)
  • windowFor(cursor, viewport, total) — stateless visible window
  • pageIndicator(offset, contentLength, viewport) — "2/5"-style indicator

ScrollableRegion also gains ensureVisible(cursor) for cursor lists.

Scope note

detail shares the math but isn't fully folded onto ScrollableRegion: its scroll state is intertwined with a rich model AND a pre-existing viewportHeight(20)-vs-renderer-visibleRows split. Reconciling that is a behavior change, not a pure refactor — so the full state-fold is left as a follow-up; sharing the math captures the duplication safely.

Tests

scroll.test.ts covers the helper math; existing browse/settings/journal/detail tests pin behavior. Suite 572/0, typecheck + verifier green.

Base

Stacked on feat/language (which adds the settings scroll this consolidates) — auto-retargets to main when #5 merges.

🤖 Generated with Claude Code

Four views each rolled their own scroll logic (only journal used the
ScrollableRegion widget). Extract widgets/scroll.ts with the shared primitives
and route every view through it — behavior-preserving:

- scroll.ts: clampOffset (free-scroll range), offsetToShow (cursor-into-view,
  stateful), windowFor (stateless visible window), pageIndicator.
- browse-model: ensureCursorVisible → offsetToShow (was the same math inline).
- settings-scene: visible-window calc → windowFor.
- ScrollableRegion (journal): scroll/indicator now call clampOffset/pageIndicator;
  gains ensureVisible() for cursor lists.
- detail-model / detail-renderer: clamp + page indicator → clampOffset/pageIndicator.

detail keeps its own scroll fields rather than folding fully onto ScrollableRegion:
its scroll state is intertwined with a rich model AND a pre-existing
viewportHeight(20)-vs-renderer-visibleRows split, which a full fold would have to
reconcile — a behavior change, not a pure refactor. Sharing the math is safe and
captures the duplication; the full state-fold is a separate follow-up.

Adds scroll.test.ts (the helper math). Suite + verifier green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pro-vi added a commit that referenced this pull request Jun 10, 2026
…g the loader

`config set language 繁` / `简` / `EN` / `zh-hant` was rejected even though the
Settings UI displays exactly those labels — and the file-load path already
accepts them (ultrareview P3#2), so the CLI and file paths diverged. Extract a
shared `canonicalLanguage()` (used by both normalizeConfig and the CLI) so
`config set language` accepts the same inputs and persists the canonical value
(en/zh-Hant/zh-Hans). Genuinely invalid values are still rejected with the
canonical list. Resolves ultrareview P3 #7 (the documented strict-CLI/lenient-file
asymmetry is no longer worth keeping now the loader is lenient).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pro-vi and others added 2 commits June 9, 2026 22:22
…factor lens)

/refactor sibling sweep on the scroll consolidation: journal-scene owned a
ScrollableRegion but hand-rolled ensureCursorVisible() as a verbatim copy of
offsetToShow — the exact math the region now exposes as ensureVisible(). Delegate
to it. Behavior-identical (character-for-character same offset math); full suite
green (572 tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cene

commentaryScrollOffset was written at three sites (exploration-mode ↑/↓
handlers, setFocusedHex reset, constructor init) and read by nothing — no
renderer ever consumed it, so the arrows mutated invisible state. Remove the
field and all writers. Key routing is unchanged: in exploration mode ↑/↓ now
fall through every remaining handler and do nothing, same as the user ever saw.

Surfaced by the /refactor sibling sweep on the scroll consolidation; if
commentary scrolling is wanted later, it should be built on widgets/scroll.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pro-vi

pro-vi commented Jun 10, 2026

Copy link
Copy Markdown
Owner Author

@codex review — please review this PR (scroll-math consolidation + journal/cast cleanups).

@pro-vi

pro-vi commented Jun 10, 2026

Copy link
Copy Markdown
Owner Author

@claude review this PR.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@pro-vi pro-vi merged commit 47f2343 into feat/language Jun 10, 2026
@pro-vi pro-vi deleted the refactor/scroll-consolidation branch June 10, 2026 07:16
pro-vi added a commit that referenced this pull request Jun 20, 2026
…ntry clamp (review #5/#6/#7)

Three P3 findings from the independent review:

- #5: `journal list` validated --limit unconditionally, but --all discards it —
  so `journal list --all --limit abc` exited 1 over a flag it ignores. Validate
  --limit only on the !--all path.
- #6: the journal search input handled backspace / deleteWord / up-down but
  dropped the caret keys the note input honors — arrow left/right, forward
  delete, home, end. Mirror the note-input branches (left/right move the query
  caret; up/down still move the result cursor).
- #7: enter() reseeded the list + patterns viewports but skipped the cursor
  re-clamp resize() runs, so popping back from a replayed reading into a shrunk
  terminal left the selection below the fold. Run ensureCursorVisible() +
  patternsScroll.scrollDown(0) in enter(), matching resize().

Tests: --all ignores a bad --limit (mutation-verified); the search input does
caret-aware insert + home/forward-delete + end/backspace (mutation-verified);
re-entering shrunk keeps the selection on screen (mutation-verified).

Gates: 1341 tests pass, typecheck clean, language-surface PASS, smoke 5/5.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant