feat(billing): Paginate invoice comparison admin UI#116647
Merged
armcknight merged 7 commits intoJun 3, 2026
Merged
Conversation
Contributor
📊 Type Coverage Diff✅ no issues found |
2abb8f5 to
f810a8b
Compare
armcknight
pushed a commit
that referenced
this pull request
Jun 2, 2026
Address Cursor Bugbot feedback on #116647: - Replace the `PaginatorRow` styled('div') with `<Flex justify="between" align="center" padding="md lg" borderBottom="primary">`. The styled rule mapped 1:1 to existing Flex props, and `static/AGENTS.md` prefers the layout primitive over new styled components. - Type `PAGE_SIZE_OPTIONS` as `readonly number[]` instead of using `as const` + a call-site `as readonly number[]` cast. Same readonly guarantee, no widening cast, no new type-safety regression flagged by the type-coverage diff. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
f810a8b to
4a47cd5
Compare
armcknight
pushed a commit
that referenced
this pull request
Jun 2, 2026
Address Cursor Bugbot feedback on #116647: - Replace the `PaginatorRow` styled('div') with `<Flex justify="between" align="center" padding="md lg" borderBottom="primary">`. The styled rule mapped 1:1 to existing Flex props, and `static/AGENTS.md` prefers the layout primitive over new styled components. - Type `PAGE_SIZE_OPTIONS` as `readonly number[]` instead of using `as const` + a call-site `as readonly number[]` cast. Same readonly guarantee, no widening cast, no new type-safety regression flagged by the type-coverage diff. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dashed
approved these changes
Jun 2, 2026
armcknight
pushed a commit
that referenced
this pull request
Jun 2, 2026
Address Cursor Bugbot feedback on #116647: - Replace the `PaginatorRow` styled('div') with `<Flex justify="between" align="center" padding="md lg" borderBottom="primary">`. The styled rule mapped 1:1 to existing Flex props, and `static/AGENTS.md` prefers the layout primitive over new styled components. - Type `PAGE_SIZE_OPTIONS` as `readonly number[]` instead of using `as const` + a call-site `as readonly number[]` cast. Same readonly guarantee, no widening cast, no new type-safety regression flagged by the type-coverage diff. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4a47cd5 to
179b787
Compare
armcknight
pushed a commit
that referenced
this pull request
Jun 2, 2026
Address Cursor Bugbot feedback on #116647: - Replace the `PaginatorRow` styled('div') with `<Flex justify="between" align="center" padding="md lg" borderBottom="primary">`. The styled rule mapped 1:1 to existing Flex props, and `static/AGENTS.md` prefers the layout primitive over new styled components. - Type `PAGE_SIZE_OPTIONS` as `readonly number[]` instead of using `as const` + a call-site `as readonly number[]` cast. Same readonly guarantee, no widening cast, no new type-safety regression flagged by the type-coverage diff. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
179b787 to
35764db
Compare
Contributor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1dfd234. Configure here.
armcknight
pushed a commit
that referenced
this pull request
Jun 3, 2026
Address Cursor Bugbot feedback on #116647: - Replace the `PaginatorRow` styled('div') with `<Flex justify="between" align="center" padding="md lg" borderBottom="primary">`. The styled rule mapped 1:1 to existing Flex props, and `static/AGENTS.md` prefers the layout primitive over new styled components. - Type `PAGE_SIZE_OPTIONS` as `readonly number[]` instead of using `as const` + a call-site `as readonly number[]` cast. Same readonly guarantee, no widening cast, no new type-safety regression flagged by the type-coverage diff. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bf57700 to
caedb65
Compare
Adds page navigation to `/_admin/cells/$region/invoice-comparison/`. Each
of the page's two tables (the both-sides comparison list and the
one-sided unmatched debug list) gets its own paginator backed by URL
search params (`rows_page` / `unmatched_page`) so refresh and
shareable URLs preserve position. Running a new comparison resets both
pages to 1.
Consumes the new `summary.{rows,unmatched}_{page,page_size,total_pages}`
contract from getsentry#20496.
REVENG-131.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…omparison Two fixes + one feature: - Move `start` / `end` from local state into URL search params. Previously a refresh wiped the in-memory `submitted` value, which disabled the query and reset the page to the empty input form — and the same gap made `?rows_page=99` URLs render nothing. The window is now reread from the URL on mount and the query fires whenever start+end are present. - After every response, if the server clamped the requested page (e.g. asked for page 99 against a 2-page result), replace the URL with the clamped value so refresh converges on the real page rather than re-requesting the bad one. - Add a "Results per page" dropdown with 25 / 50 / 100 / 250 options. Selection persists in the URL as `page_size` and is applied to both tables (sent as `rows_page_size` and `unmatched_page_size` on the request). Default is 50. Changing the value resets both paginators to page 1 — the previous offset doesn't map cleanly to the resized list. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address Cursor Bugbot feedback on #116647: - Replace the `PaginatorRow` styled('div') with `<Flex justify="between" align="center" padding="md lg" borderBottom="primary">`. The styled rule mapped 1:1 to existing Flex props, and `static/AGENTS.md` prefers the layout primitive over new styled components. - Type `PAGE_SIZE_OPTIONS` as `readonly number[]` instead of using `as const` + a call-site `as readonly number[]` cast. Same readonly guarantee, no widening cast, no new type-safety regression flagged by the type-coverage diff. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address Cursor Bugbot finding: the `start` / `end` `datetime-local` inputs were initialized lazily once and then never updated, so browser back/forward (or any in-app navigation that swapped the query params) would leave the input fields showing a stale window while the fetched results matched the new URL. Add a `useEffect` that mirrors the URL window back into `startInput` / `endInput` whenever those query params are present. When the URL has no window yet, the effect skips the update so a user's in-progress typing isn't clobbered before they submit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address Cursor Bugbot finding: `parsePageSizeParam` silently maps any unsupported `page_size` query value (e.g. `?page_size=200`) to the default while leaving the URL untouched. The result: the API and UI ran at the resolved size while the address bar advertised something else, so refresh / share-links did not reproduce the intended view — unlike `rows_page` / `unmatched_page`, which were already corrected. Add a small `useEffect` that, whenever the URL has a `page_size` value that doesn't match the resolved one, replaces the URL with the resolved value. Runs eagerly (no dependency on `data`) since the validity check is purely client-side, so the address bar matches the real page size before the first request even returns. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…oads
Address Cursor Bugbot finding: clicking Prev / Next (or changing the
page-size dropdown) updated the React Query key, but the query didn't
keep prior data while the next page fetched. The summary and both
tables are gated on `{data && (…)}`, so the whole comparison view
collapsed to the loading indicator on every page change — surprisingly
janky for a paginator.
Pass `placeholderData: keepPreviousData` so the previously-rendered
page stays mounted until the new response arrives.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…omparison Address Cursor Bugbot finding: with `placeholderData: keepPreviousData`, `data.summary` reflects the previously-rendered page while React Query loads the next one. The clamp effect that snaps the URL to the server-reported page therefore fired against stale summary data — every Prev / Next click would rewrite the URL back to the prior page, breaking pagination outright (and the same path broke clamped deep links once the first page loaded). Read `isPlaceholderData` from `useQuery` and bail out of the clamp effect while it's true, so the URL only converges on the server's authoritative page number once the new response actually lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
caedb65 to
299dc36
Compare
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
REVENG-131. Paginates the
/_admin/cells/$region/invoice-comparison/page so operators can navigate past the first 200 rows.rows_page,unmatched_page) so refresh and share-links keep their position.start–end of total · page X of Yplus prev/next.Backend PR: https://github.com/getsentry/getsentry/pull/20496
Test plan
make seed-invoice-comparisonrun, load/_admin/cells/<region>/invoice-comparison/, click Run, and confirm both tables show page 1 of 2 by default (page_size=200, 250 rows each)?rows_page=2, rows change, unmatched paginator stays on page 1?unmatched_page=2, unmatched rows change, rows table stays on page 2?rows_page=1&unmatched_page=1)start = end = now) — paginator row shows "No rows" rather than a broken Prev/Next pair?rows_page=99— backend clamps to last page, frontend shows that page with Next disabled🤖 Generated with Claude Code