chore: remove superseded TimeWindowPagination, refresh DM design doc

Review prep for the DM pagination branch:

- Delete commons TimeWindowPagination + its test: the early since-based
  time-window approach, referenced only by its own test and fully
  superseded by UntilLimitPager (until+limit, gap-proof). 212 lines a
  reviewer would otherwise study for nothing.
- Bring the design doc up to the final architecture: NIP-04 per-relay
  filter scoping, per-relay independent paging (no rounds) + in-stream
  markers for the convo, the round model still used by rooms/gift-wrap,
  the WindowLoadTracker backstops and tracksReqSends gating, the
  loadingMore-starts-false fix, and the DMPagination diagnostics map.
  Marks the obsolete time-slice section as superseded.

https://claude.ai/code/session_01B1fmmmX8JjQWH3amMLdvcW
This commit is contained in:
Claude
2026-06-03 17:16:21 +00:00
parent 5b0f2051db
commit e1cdd40bb5
3 changed files with 80 additions and 212 deletions
@@ -1,87 +0,0 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.commons.relayClient.pagination
import com.vitorpamplona.quartz.utils.TimeUtils
/**
* Tracks how far back in time a relay subscription should reach.
*
* Boot opens a small window (recent-first) so a screen becomes usable before the
* whole history is fetched and decrypted. Each [loadMore] widens the floor backward
* so scrolling pulls older history on demand.
*
* Only the lower bound ([since]) moves: the subscription stays open so new events
* keep streaming live regardless of the window. The floor is requested in full on
* every assembly (the value is small and bounded), which keeps the window robust
* even if the in-memory note store evicts previously-loaded events under memory
* pressure.
*
* The step can grow geometrically ([growthFactor] > 1) so an auto-fill loop that keeps
* widening to fill a screen (or to confirm there is no older history) converges in a
* handful of requests instead of crawling back a fixed slice at a time. [maxLookback]
* is a hard floor: once [since] reaches it, [isExhausted] is true and there is nothing
* older to ask for.
*/
class TimeWindowPagination(
private val initialWindow: Long = ONE_WEEK_IN_SECONDS,
private val step: Long = ONE_WEEK_IN_SECONDS,
private val growthFactor: Long = 1L,
private val maxLookback: Long = TEN_YEARS_IN_SECONDS,
) {
/** Epoch seconds; events older than this are not requested from relays. */
@Volatile
var since: Long = TimeUtils.now() - initialWindow
private set
@Volatile
private var currentStep: Long = step
private fun floor() = TimeUtils.now() - maxLookback
/** Widens the window backward by the current step, clamped at [maxLookback], then grows the step. */
fun loadMore() {
since = maxOf(floor(), since - currentStep)
if (growthFactor > 1L) currentStep *= growthFactor
}
/** Jumps straight to [maxLookback] so a single request pulls the entire history. */
fun loadAll() {
since = floor()
}
/** True once the window has reached [maxLookback] — there is no older history to request. */
fun isExhausted(): Boolean = since <= floor()
/** Resets the window back to the initial boot size, anchored at the current time. */
fun reset() {
since = TimeUtils.now() - initialWindow
currentStep = step
}
companion object {
const val ONE_WEEK_IN_SECONDS = TimeUtils.ONE_WEEK.toLong()
// Covers the entire history of Nostr (which began ~2021) with margin, so reaching it
// genuinely means "nothing older exists" rather than an arbitrary cutoff.
const val TEN_YEARS_IN_SECONDS = TimeUtils.ONE_YEAR.toLong() * 10
}
}