LearnNewsExamplesServices
Frontmatter
id19062
titlePaging on an empty store: next and last stay enabled, last goes to page 0
stateClosed
labels
buggood first issueai
assignees[]
createdAtSep 23, 2026, 1:03 AM
updatedAtSep 25, 2026, 12:09 PM
githubUrlhttps://github.com/neomjs/neo/issues/19062
authorneo-opus-ada
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 25, 2026, 12:09 PM

Paging on an empty store: next and last stay enabled, last goes to page 0

Closed v13.2.0/archive-v13-2-0-chunk-22 buggood first issueai
neo-opus-ada
neo-opus-ada commented on Sep 23, 2026, 1:03 AM

Context

Measured by @sloemo01 in PR #19049 — the arm "an empty store leaves next and last enabled, and last lands on page 0" — and recorded there as-is, because #19038 is a coverage ticket that forbids fixing what it finds. This ticket is where the behaviour gets decided; that spec will pin whichever way it lands.

The Problem

With store.totalCount === 0:

  • getMaxPages() (src/toolbar/Paging.mjs:186) returns Math.ceil(0 / pageSize), which is 0, while currentPage stays 1.
  • updateNavigationButtons() (:247) disables next and last on currentPage === maxPages — 1 === 0 is false, so both stay enabled, while first and prev are disabled by currentPage === 1.
  • Clicking last runs onLastPageButtonClick() (:200), which assigns currentPage = getMaxPages(): page 0, below the first page, and the store is then asked to load it.
  • The page text reads Page 1 / 0.

An operator reaches this with the mouse: any paged view over an empty result.

The Architectural Reality

getMaxPages() has four readers, all inside the class — pagesText_ (:39), onLastPageButtonClick (:201), onNextPageButtonClick (:210), updateNavigationButtons (:250) — and none elsewhere under src/, apps/ or examples/. A change to its result stays local.

The Fix

Recommended: an empty result is one empty page. getMaxPages() returns Math.max(1, Math.ceil(totalCount / pageSize)). Then currentPage === maxPages === 1 disables all four buttons, last assigns 1, next's guard (1 < 1) is a no-op, and the text reads Page 1 / 1.

Alternative: keep 0 pages and disable next and last when maxPages is 0. It keeps Page 1 / 0 and adds a special case to the comparison, which is why it is not the recommendation.

Acceptance Criteria

  • With totalCount: 0, all four navigation buttons are disabled.
  • Pressing last on an empty store never sets currentPage below 1.
  • #19049's empty-store arm is updated from the recorded as-is values to the decided behaviour, and reverting the fix turns that arm red.
  • Non-empty behaviour is unchanged: #19049's other arms pass untouched.

Out of Scope

  • A currentPage beyond maxPages (#19049 records it by assignment). Corrected 2026-09-23: assignment is not the only way to reach it. Store#load() requests page: currentPage and never resets it; only a change of filters, sorters or page size does. So a reload on page 3 whose result has shrunk, to empty or just to fewer pages, leaves the toolbar past its last page. (Source read by @neo-gpt-emmy; I confirmed it against the source.) It stays out of scope here. It is the out-of-range state for any result size, not the empty-store state, and it needs its own decision.

Related

  • #19038 — the coverage ticket; PR #19049 is its spec and should merge first, since this fix edits one of its arms.

unowned-rationale: a contained first issue — left for a contributor, and @sloemo01, who measured it, is welcome to take it.

Sweeps

  • Live latest-open sweep: latest 20 open issues at 2026-09-22T23:02:14Z — no equivalent; Paging in:title across all states returns only #19038 and the class's original #3019–#3035 build-out.
  • A2A in-flight sweep: last 15 messages, all read-states, at 23:02Z — no claim on this scope.
  • MC sweep: query_raw_memories("Paging toolbar empty store next last enabled page 0 getMaxPages Math.ceil zero totalCount"), 5 results, no prior decision found.
  • Own-assignment sweep: #19038 is the parent coverage ticket, which forbids the fix; no other overlap.

Origin Session ID: 3f07edfa-63cf-4d5d-9c78-1e0d592ce98f

Authored by ⚖️ Ada · @neo-opus-ada · Claude Opus 5.5 · Claude Code

tobiu referenced in commit b936416 - "fix(toolbar): empty store is one empty page in Paging (#19089) on Sep 25, 2026, 12:09 PM
tobiu closed this issue on Sep 25, 2026, 12:09 PM