Skip to content

fix(stats): stop fast arrow presses from wedging the range tabs - #1379

Open
addyCooks wants to merge 1 commit into
Nano-Collective:mainfrom
addyCooks:fix/stats-range-stale-closure
Open

addyCooks wants to merge 1 commit into
Nano-Collective:mainfrom
addyCooks:fix/stats-range-stale-closure

Conversation

@addyCooks

Copy link
Copy Markdown
Contributor

Description

Pressing / quickly on /stats could silently drop a press and freeze the range tabs on one value until another key broke the tie.

The left/right handlers stepped from the range captured in the input handler's closure. Ink re-registers useInput handlers in a passive effect that runs after the frame is painted, so a press arriving in that window was dispatched with the previous render's value, recomputed the tab it had already moved to, and React bailed out — leaving the tabs stuck. Both directions now step from the value React holds, which also collapses the two near-identical branches into one.

Instrumented trace on the unfixed code:

[RENDER] range = 7d
  [EFFECT ran] range = 7d
--- press 1
[INPUT fired] closure range = 7d     → setRange('3m')
[RENDER] range = 3m                   ← frame painted here
--- press 2
[INPUT fired] closure range = 7d     ← stale handler still registered
  [EFFECT ran] range = 3m             ← re-registration only now
[RENDER] range = 3m                   ← wedged

This is also why Unit Tests & Coverage Analysis has been failing on unrelated PRs (e.g. #1374): the spec pressed once per tick and polled the frame in between, which is exactly the window that hides the bug, so it only failed when the passive effect lost the race. It reproduces on main, so it can wedge on any branch.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Changeset

  • Added a changeset (pnpm changeset) describing this change for the changelog

Testing

Automated Tests

  • New features include passing tests in .spec.ts/tsx files — regression test, verified failing before the fix and passing after
  • All existing tests pass (pnpm test:all completes successfully) — with the caveat below
  • Tests cover both success and error scenarios — both step directions, wraparound in both, and the Escape close path

Manual Testing

  • Tested with Ollama
  • Tested with OpenRouter
  • Tested with OpenAI-compatible API
  • Tested MCP integration (if applicable)

Checklist

  • If this was for an open issue, I was assigned to it
  • Code follows project style guidelines
  • Self-review completed
  • Documentation updated (if needed)
  • No breaking changes (or clearly documented)
  • Appropriate logging added using structured logging

The left/right handlers stepped from the `range` captured in the input
handler's closure. Ink re-registers `useInput` handlers in a passive effect
that runs after the frame is painted, so a press arriving in that window was
dispatched with the previous render's value, recomputed the tab it had
already moved to, and React bailed out - leaving the tabs stuck until
another key broke the tie. Both directions now step from the value React
holds, which also collapses the two near-identical branches into one.

The spec pressed once per tick and polled the frame in between, which is
exactly the window that hides this, so it only failed when the passive
effect lost the race - intermittently on CI, consistently on Windows. It now
sends two presses in one tick to pin the bug deterministically, and adds a
left-arrow press so the shared step is covered in both directions.
@github-actions

Copy link
Copy Markdown
Contributor

nc-review: comments — 1 nit

@addyCooks — a few things worth a look, none blocking.

The fix replaces the setRange(range +1) closure-capture pattern with the functional-updater form (setRange(prev => ...)), which is the established convention elsewhere in the codebase for handling stale closures inside Ink useInput handlers. The regression test exercises two presses in one tick and asserts the wrap-around result, which does differentiate the buggy wedge from the fixed behaviour. The changeset, scope and test coverage all check out.

⚪ nit · tests · source/commands/stats.spec.tsx:120

The inline comment says the second rapid press 'lands on 3m again', but with two stale-closure presses from range = 3m both compute idx = 1 → +1 = 2 → all-time, so the second setRange(all-time) is a no-op and the tabs wedge on all-time, not 3m. The PR body description is right ('the tab it had already moved to'); only this comment drifts. Not a correctness issue — the test still differentiates the bug from the fix — but worth tightening so the comment matches what the test actually exercises.


🔴 blocking · 🟠 a reviewer would ask for a change · ⚪ optional

Automated code review — correctness, security, design, tests, plus duplicates and scope. A human still decides; this is not a substitute for review and is not exhaustive. The required status checks separately cover lint, formatting, types, unused dependencies, the test suite and the build. This bot never merges. Maintainers can rerun with /re-review.

@github-actions github-actions Bot added the agent:comments nc-review left non-blocking findings label Sep 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent:comments nc-review left non-blocking findings area:tui Terminal UI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant