mirror of
https://github.com/runbear-io/beardrive.git
synced 2026-08-25 08:08:08 +02:00
* fix(webapp): a background refresh never moves the reader (BEA-155)
Reading a file was interrupted every so often by the viewport jumping back
to the top. Nothing polls the document — the 60s read-count poll does it:
`heatMap` sat in MarkdownView's meta-effect deps and that effect ended by
calling `onRendered()`, which the scroll restorer reads as "content landed"
and answers with `scrollTo(top: 0)`. Capped at 3 attempts per route, hence
"from time to time". `onScroll` then memoized 0, so Back also returned to
the top.
Two changes. The second is the reported bug; the first is why it can't come
back wearing a different hat:
- The scroll goal retires itself the moment the reader scrolls, so no
onRendered caller — present or future, honest or not — can move a reader
who has taken over. Two clauses are load-bearing and each has its own
test: scrollTo fires a scroll event of its own (so "moved" is measured
against the goal, never against zero), and a page shorter than the last
one makes the browser CLAMP the carried-over offset to the bottom, which
is either the old page's offset arriving or a goal that doesn't fit yet
— the exact case the retries exist for.
- MarkdownView's effect splits in two: onMeta keeps its heatMap
dependency, onRendered fires on [html, diagrams]. A metadata refresh is
not a render. A mermaid diagram landing used to call onRendered never,
so Back into a diagram-heavy file never got its late re-apply; now it
does, which is what the retry budget was written for.
The state machine moves to src/lib/scroll.ts because the frontend suite is
`node --test` over pure TS with no jsdom — that is the only way this gets a
regression test at all. Browser.tsx keeps the DOM bits.
The other four onRendered callers are audited and deliberately left alone:
FolderListing's "Recent changes" feed, HistoryView, and SniffView/TextView
on ["text", …] all fire on content that genuinely changes the page height,
which is what the retries are for. MarkdownView was the only one whose
trigger was pure metadata.
No poll interval is touched, and every programmatic scroll keeps
behavior:"instant" — #content carries scroll-behavior:smooth, and an
animated restore would fire intermediate scroll events that the new guard
would read as the reader.
* fix(webapp): apply the scroll goal when it is armed, not only later (BEA-155)
Back landed at the top of the file instead of the offset it remembered —
already true on main, and the same restorer this branch is repairing, so it
lands here rather than as its own issue.
React runs CHILD effects before the parent's. A view calls onRendered from
its own effect, so by the time Browser's route effect arms the goal for the
new route, that call has already happened — against the goal of the route
just left, where the key check discarded it. The goal then sat armed and
nobody applied it: on PUSH the container's carried-over offset was clamped
by the shorter page and looked close enough to right, but on POP the
remembered offset was simply never restored.
Arm and apply in the same pass. Later onRendered calls still cover content
that grows after first paint, which is what the remaining budget is for.
* docs(architecture): lib/scroll.ts, the scroll goal Browser now delegates to (BEA-155)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>