Problem
Admin listing pages for time-series data (posts, editions, events) need pagination, but cursor-based or page-number pagination is awkward when the natural browsing unit is "recent days" and the admin thinks in terms of recency, not page numbers.
Approach
Add an offset_days query parameter alongside the existing days (window size) parameter. The window is computed as:
end_date = today_ny() - timedelta(days=offset_days)
start_date = end_date - timedelta(days=max(0, days - 1))
# query: WHERE edition_date >= start_date AND edition_date <= end_dateoffset_days=0, days=10 → last 10 days (default).offset_days=10, days=10 → 10–20 days ago.
Frontend uses two simple functions:
const PAGE_DAYS = 10;
let offset_days = 0;
function page_newer() {
offset_days = Math.max(0, offset_days - PAGE_DAYS);
load_all();
}
function page_older() {
offset_days += PAGE_DAYS;
load_all();
}Navigation shows "← Newer 10 days" / "Older 10 days →" at both top and bottom of the listing. Header subtitle reflects the window: "Last 10 days" or "10–20 days ago".
Why not cursor/page-number
- The data is naturally bucketed by date — the admin thinks "show me last week" not "page 3"
- No total count needed — the "older" button always works (empty results are fine)
- Multiple endpoints (items, editions) share the same window without coordinating cursors
- Stats endpoint stays un-paginated (it's summary data over the full window)
Gotcha
When using Promise.all to load multiple paginated endpoints, all must use the same offset_days value or the items/editions will be out of sync. Pass the offset as a shared variable, not per-endpoint state.