Repository navigation
UN-4505 [DEV] Add the v1 DocumentViewer with toolbar, zoom, keys and scroll handle - #2408
muhammad-ali-e wants to merge 21 commits into
Conversation
… scroll handle - DocumentViewer composes the loader, status screens, toolbar and page list: <DocumentViewer source highlights activeHighlightId onHighlightClick initialZoom ref />, with scrollToPage and scrollToHighlight on the ref. A scroll asked for while loading runs once the pages are laid out, and the active highlight is scrolled into view when it changes. - ViewerToolbar: previous/next, a page box (n / N, Enter to jump), zoom -/+, the zoom level, fit width and fit page, each a labelled button reachable with Tab. - Zoom: fit width by default, fitted from the viewer's size with a ResizeObserver; -/+ step through 25%-400% from wherever a fit left it. A zoom change keeps the same spot of the same page in view. - Keys while the pages have focus: PageUp/PageDown, Home/End, + and -; modified keys are left to the browser. - PdfPageList scrolls by the pages' laid-out offsets, which are exact before any page renders, and reports the page showing most of itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R7trgcAzu6jxDnit3eeozm
for more information, see https://pre-commit.ci
|
The failing hook is There's no fix to port yet. The fix is a one-line change on #2399: Generated by Claude Code |
# Conflicts: # frontend/src/v1/components/patterns/document-viewer/PdfPageList.test.tsx
- Fit page fits every page instead of the current one. With mixed page sizes, following the current page rescaled the whole document each time scrolling crossed into a page of another size. - The page box keeps a half-typed number and its focus when the current page changes; it follows the current page only while not being edited. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R7trgcAzu6jxDnit3eeozm
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R7trgcAzu6jxDnit3eeozm
… to their source The current page came from intersection ratios reported at quarter steps, so for a page more than four times the viewer's height it was arbitrary, and after a jump it lagged a frame: the toolbar showed the wrong page, PageDown could skip one, and the zoom anchor was recorded against the old page. Both now come from the laid-out page offsets, on scroll and straight after a jump. A scroll waiting for a document is dropped when another source is shown, and a highlight scroll whose highlight is not in the list yet waits and is tried again when the highlights change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R7trgcAzu6jxDnit3eeozm
… commit Round two of the review: - the last page is current once the end is reached, so Next and PageDown no longer stick when a short last page or two whole pages show - the page in view is measured in the content's own coordinates, with one query per scroll, and again when the viewer changes height - a waiting scroll is stamped with its source and dropped only at flush time, so one made in a parent's layout effect on mount survives - the active highlight is re-armed for each new source, and clearing it drops only a request it made Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R7trgcAzu6jxDnit3eeozm
When the last few pages fit together, a jump to one of them is clamped at the end of the scroll and the end rule reported the last page again, so Previous and PageUp could not leave it. The page jumped to now stays current until the reader scrolls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R7trgcAzu6jxDnit3eeozm
[Low impact] The PR appears safe to merge; no blocking issues remain.
|
…tly, clamp a starting zoom - The current page is the one showing the largest share of itself, a page taller than the view counting the view as whole, so part of a tall page no longer beats a short page shown whole. - scrollToHighlight centres on the page's own top, padding included; page jumps keep the gap above the page. - A numeric initialZoom goes through clampZoom like a fitted one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R7trgcAzu6jxDnit3eeozm
praveen-formido
left a comment
There was a problem hiding this comment.
DocumentViewer, toolbar, zoom and keys. Two items I would fix first: viewerKeyAction does not check for an editable target, so any input rendered in an overlay loses -, =, Home, End and the page keys; and fit-width assumes a 16px scrollbar, which gives a permanent horizontal scrollbar on 17px classic scrollbars. The rest are smaller.
Also: the PR body says fit page uses the current page, but the code (and its comment at DocumentViewer.tsx:158) deliberately fits every page. The code reads as the intended behaviour; the body should say so.
# Conflicts: # frontend/src/v1/components/patterns/document-viewer/PdfPageList.tsx
…e reset, deferred fit layout - viewerKeyAction leaves keys typed into an input, textarea, select or contenteditable to that field. - Fit modes measure the platform scrollbar once (lib/scrollbar) instead of assuming 16 px, so a 17 px classic scrollbar no longer forces a horizontal scrollbar. - The page index resets while rendering when the document changes, so the toolbar never shows the last document's page against the new count. - In a fit mode the page list waits for the viewer's size, so pages are not laid out at 100% first and again at the fitted zoom. - sync() reads the page elements once per list, not on every scroll. - Document identity uses objectKey from lib/pdf/pdfjs, shared with pdfSourceKey. - useElementSize measures once where ResizeObserver is missing, matching the guard in PdfPageList. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R7trgcAzu6jxDnit3eeozm
Dismissed because a newer commit was pushed; Greptile will re-review the current head.
…ht is no longer active The retry effect runs before the effect that cancels a cleared selection, so a render that both delivers the highlight and clears it scrolled to a selection the reader had just cleared. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R7trgcAzu6jxDnit3eeozm
Dismissed because a newer commit was pushed; Greptile will re-review the current head.
A scroll queued for document A survived a switch to B and back to A while neither had opened, because the retry effect only clears a stale request once pages are shown; by then the key matched A again. It is now dropped on the source change itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R7trgcAzu6jxDnit3eeozm
Dismissed because a newer commit was pushed; Greptile will re-review the current head.
|



What
DocumentViewer(components/patterns/document-viewer/) is the component page tasks will import. It combines the loader, the status screens, the toolbar and the page list.ViewerToolbar: page navigation and zoom controls.lib/pdf/zoom.ts: zoom steps and fit-to-width / fit-to-page maths.lib/pdf/keyboard.ts: maps keys to viewer actions.hooks/useElementSize.ts: tracks an element's size.PdfPageListgains a ref handle, current-page reporting, zoom anchoring and a keyboard target.Part of the v1 DocumentViewer (P1-T21, UN-4390).
Why
Six page tasks need one viewer with paging, zoom and programmatic scrolling. v0's viewers used
setTimeoutbefore jumping to a page. Here every scroll target is exact, because every page has its real size before it renders.How
<DocumentViewer source highlights activeHighlightId onHighlightClick initialZoom ref />. The ref exposesscrollToPage(index)andscrollToHighlight(id).activeHighlightIdchanges, that highlight is centred in view. This replaces v0's jump-to-highlight on change.offsetTop/offsetHeight/offsetWidth, so it needs no timers and no waiting for a render.role="toolbar", and every control is a labelled button reachable with Tab, in reading order.n / N. Enter jumps to the typed page; an out-of-range value reverts.ZoomIn/ZoomOut/ChevronLeft/ChevronRightmatch the Prompt Studio viewer frames in the P1-T22 inventory. Figma has no glyph for fit width or fit page, so I pickedMoveHorizontalandScan. Easy to swap.+/=zoom in, and-zooms out.Can this PR break any existing features. If yes, please list possible items. If no, please explain why. (PS: Admins do not merge the PR without this section filled)
src/v1/. Nothing in v0 or the v1 entry imports it yet, andlucide-reactis already a dependency.Database Migrations
Env Config
Relevant Docs
Related Issues or PRs
Dependencies Versions
Notes on Testing
bun run typecheck:v1,lint:v1andtest:v1pass: 125 unit tests and 56 boundary fixtures.biome ci src/reports no errors.scrollToPageis exact and clamped.scrollToHighlightcentres both ways, and returns false for an unknown id.+/-keys work, and Ctrl ++is ignored.Screenshots
Checklist
I have read and understood the Contribution Guidelines.
Generated by Claude Code