Skip to content

refactor(viewer): consolidate view state into a store hook - #44

Merged
denolfe merged 3 commits into
mainfrom
refactor/consolidate-view-state
Jul 25, 2026
Merged

refactor(viewer): consolidate view state into a store hook#44
denolfe merged 3 commits into
mainfrom
refactor/consolidate-view-state

Conversation

@denolfe

@denolfe denolfe commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Consolidates App's nine scattered view-state fields into a single useViewState store hook, and makes createCommands stable by reading state through a latest-state ref. This is a pure structural refactor with no behavior change.

Each view-state field was declared in five places: a useState in App, a read field and a setter in CommandDeps, and an entry in two useMemo dependency arrays. Adding a field was a five-site edit, and a missed dependency array was a silent staleness bug. Commands also rebuilt on every keystroke and scroll because they closed over each field.

Key Changes

  • View-state store hook
    • useViewState({ seedVisible }) holds the nine fields in one useState<ViewState> with a stable useCallback writer per field, grouped into an actions object.
    • covering and status stay as local useState. They are App-local UI flags, not part of the shared view-state read model.
  • Stable commands via a latest-state ref
    • useLatest exposes the current ViewState as a ref updated on every render. createCommands reads stateRef.current.* and writes via actions.*.
    • The commands memo no longer lists per-field state. It rebuilds only on doc/nav swap and viewport resize, not per keystroke or scroll.
  • Narrowed CommandDeps
    • The read/set bag is replaced by stateRef, actions, and a scalar historyDepth (nav-derived, so kept out of the store).
  • Docs and tests
    • commands.test.ts builds deps via makeDeps({ state }) and asserts on actions.* mocks. The architecture doc and CLAUDE.md describe the new seam.

Design Decisions

A custom store hook was chosen over useReducer. It keeps named writer methods and the smallest call-site diff, and avoids introducing a second dispatch/action vocabulary alongside the existing keyboard dispatcher.

Command state reads flow through a latest-state ref rather than closures. Within a single command, all reads see one committed snapshot, matching the previous fresh-closure behavior. This frees createCommands from per-field dependencies, so it stops rebuilding on every state change.

@github-actions

Copy link
Copy Markdown

Startup benchmark (--render test/exhaustive.md, linux-x64)

build mean ratio verdict
baseline (main@8cbde71) 740.0ms ± 14.5ms
PR 726.7ms ± 18.1ms 0.98× ✅ ok

Thresholds: warn ≥ 1.1×, fail ≥ 1.25×. Baseline built from main.

@denolfe
denolfe merged commit e39c971 into main Jul 25, 2026
8 checks passed
@denolfe
denolfe deleted the refactor/consolidate-view-state branch July 25, 2026 03:13
denolfe added a commit that referenced this pull request Jul 26, 2026
- refactor: split breadcrumb and ancestor-stack vocabulary (#45) (4fc9a88)
- refactor(viewer): consolidate view state into a store hook (#44) (e39c971)
- refactor(viewer): extract nav-intent consumer into a pure dispatcher (#43) (8cbde71)
- refactor: consolidate heading/overlay geometry into a Fold module (#42) (bc77a33)
- refactor(viewer): extract scroll pending/pin machine into a pure reducer (#41) (a2e8062)
- feat(viewer): add a keyboard shortcut help drawer (#40) (cfb3815)
- feat(viewer): breadcrumb trail for relative-link navigation (#39) (65ffb0e)
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