docs(adr): ADR 009 analyses predecessor search and a top-left on a pruned row (improvise-felo.4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
committed by
Edward Langley
parent
6305fa3bc7
commit
de3b2bc323
@@ -1379,6 +1379,58 @@ analysis.*
|
||||
and by the bottom-right corner of the axis space, so its cost is O(|S| × log n + k ×
|
||||
|S|) per round, never a scan of empty space past the last occupied position.
|
||||
|
||||
- **Scrolling up or left, and a top-left that lands on a pruned row (the user, in
|
||||
chat).** "The app is free to optimize fetching for up or left by just fetching the new
|
||||
rows, until it finds one that has values in the viewport but, conceptually, scrolling up
|
||||
or left would request a new viewport with a new top left corner. The requested top left
|
||||
corner falling on a pruned row or column is an annoying edge case, maybe just fail the
|
||||
fetch and retry?" (relayed on 2026-10-05; on no PR). The user's "maybe" is tentative,
|
||||
and nothing here is decided. *The rest of this item is the author's analysis.*
|
||||
- **The optimisation is a predecessor search.** Fetching only the new rows above the
|
||||
window, and scanning upward until one has a value among the displayed columns, asks
|
||||
"the previous row before p with a value in S". The forward-only queries above do not
|
||||
serve it directly. There are two ways to serve it:
|
||||
- **The client does it with forward requests.** It guesses an earlier top-left,
|
||||
requests forward from there, and widens the guess if nothing qualifies. Each guess
|
||||
costs one forward query, and the number of guesses grows with the gap. At the
|
||||
fixture's density of 10^-4 a blind step of tens of rows usually finds nothing, so
|
||||
it needs widening, about log₂(gap) guesses if each guess doubles the step.
|
||||
- **The model offers a predecessor query.** It is cheap under every option. A sorted
|
||||
`Vec` and a B-tree both give an upper-bound seek and a reverse iterator at the same
|
||||
cost as the forward ones, O(log n) and then O(1) per step. Option (ii)'s per-column
|
||||
sorted lists merge backwards exactly as they merge forwards. Options (i) and (iii)
|
||||
walk backwards with the same per-row test. So offering it adds no state. It adds
|
||||
only a second direction to the query surface, which the conceptual model (always
|
||||
fill forward from a new top-left) does not need. *Author's lean:* offer it, because
|
||||
it costs no state and saves the client a guessing loop. The user's to confirm.
|
||||
- **The edge case: a top-left on a pruned (empty) row or column.** Listed neutrally:
|
||||
- (a) **Fail, then retry** (the user's tentative "maybe"). The model answers with an
|
||||
error. If the error names the nearest occupied successor (and predecessor), the
|
||||
retry is one round trip. If it names nothing, the client must guess, as above.
|
||||
- (b) **Snap forward, and report the actual top-left.** The model starts the fill at
|
||||
the next occupied position at or after the requested one, and returns that
|
||||
position in the response. No extra round trip. The client sees its window start
|
||||
somewhere other than it asked.
|
||||
- (c) **Snap toward the scroll direction.** Like (b), but snap backward when the
|
||||
request came from an upward scroll. That needs the request to say which direction
|
||||
it came from, which the request sketch does not carry.
|
||||
|
||||
Cost at density 10^-4: finding the nearest occupied neighbour is one seek in the
|
||||
ordered index, O(log n), in either direction, so (a) with named neighbours and (b)
|
||||
cost the same on the host. (a) adds a round trip, and (b) adds none. The expected gap
|
||||
to the next occupied row is on the order of 10^4 cross-product rows, so a client that
|
||||
guesses blind under (a) would need about log₂(10^4) ≈ 14 doubling retries. **Unpruned
|
||||
space is not an edge case at all**: a top-left on an empty position is answered with
|
||||
empty cells.
|
||||
- **How it fits identity-keyed deltas and resumable streams.** Under (b) and under (a)
|
||||
with named neighbours, the response's actual top-left is an absolute position, and
|
||||
the tuple there is an identity. So the stream's resumable state stays `(request,
|
||||
actual top-left, last position)`, and a later delta names rows by identity whichever
|
||||
top-left was asked for. A top-left whose row is pruned after the stream started is the
|
||||
same case in time. A delta reports the row's removal, the client's window keeps its
|
||||
absolute top-left, and the next fill skips past the empty position. That is (b)'s
|
||||
behaviour without a new request.
|
||||
|
||||
- **The fixpoint comes from the fill, not from one window.** *As first written*, this
|
||||
item said that hiding a row can empty a column. **That was wrong.** A row dropped for
|
||||
having no value among the window's columns contributes no value to any of those
|
||||
@@ -1961,6 +2013,11 @@ revision, and none is renumbered.
|
||||
Under the user's right-and-down growth (clause 12), the query is a forward successor
|
||||
query only, and so are all three options; no rank or reverse scan is needed for
|
||||
pruning.
|
||||
The user's later chat adds a client-side optimisation for scrolling up or left (a
|
||||
predecessor search, which every option serves as cheaply as a successor search). It
|
||||
also raises the edge case of a top-left that lands on a pruned row: fail and retry,
|
||||
snap forward, or snap toward the scroll direction (clause 12). That edge case is
|
||||
part (d) of this question.
|
||||
*Recommendation (the author's):* (a) yes, with (ii). (b) yes, once the user confirms
|
||||
that pruning is the client's. (c) restate both in those terms, in ADR 008, since they
|
||||
are ADR 008's answers.
|
||||
|
||||
Reference in New Issue
Block a user