Skip to content

[BUG] PDF takeoff: the current page's sidebar thumbnail never loads (permanent spinner); the failed render is swallowed and never retried #301

Description

@arvildev

Observed on 9.7.0, in a multi-page takeoff document.

Repro:

  1. Open a multi-page PDF in the takeoff viewer with the page sidebar visible.
  2. Every page's thumbnail fills in except the currently open page, which shows the spinner over a blank tile indefinitely.
  3. Navigate to another page: the previously current page's thumbnail now loads (and the new current page's thumbnail gets stuck instead).

Root cause (frontend/src/modules/pdf-takeoff/TakeoffViewerModule.tsx):

  • The thumbnail effect (:1006-1042) renders pages nearest-first (frontend/src/features/takeoff/lib/takeoff-thumbnails.ts:51-64), so it always starts with the current page. That is the one page the main viewer is rendering at the same time (main render: getPage at :963, page.render at :983).
  • Any error in a thumbnail render lands in an empty catch {} (:1030) and the loop moves on. The effect only re-runs when its deps change ([pdfDoc, showThumbnails, currentPage, totalPages]), so a failed current-page render is never retried while you stay on that page.
  • The tile shows thumbs[n] if present, otherwise the spinner (:5027-5033). Since thumbs[currentPage] is never set, the spinner never goes away.

Why the current page specifically: it is the only page rendered concurrently by two pipelines; every other page renders solo and succeeds. The exact rejection path inside pdf.js is not pinned down here, and the diagnosis does not depend on it. Whatever makes the doubly-rendered page's thumbnail render fail, the catch at :1030 swallows the failure and nothing retries it while the page stays current. Navigating away re-runs the effect, the page now renders solo, and its thumbnail appears, which matches the observed behavior.

Suggested fix, cheapest first:

  1. Do not re-render the current page for its thumbnail at all: when n === currentPage and the main canvas has rendered, downscale it (drawImage from canvasRef to the offscreen canvas, then toDataURL). This removes the concurrent same-page render entirely and saves a full PDF render for the page already on screen.
  2. Alternatively, retry the current page after the main render settles (the catch at :1030 currently drops it silently).
  3. Alternatively, render thumbnails from a second getDocument() instance so the two pipelines never share page state.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions