Skip to content

fix(ui): preserve rendered page tiles after partial render failures (#623 item 4) - #5223

Open
ishveet wants to merge 4 commits into
storytold:mainfrom
ishveet:fix/623-preserve-rendered-tiles-current
Open

ishveet wants to merge 4 commits into
storytold:mainfrom
ishveet:fix/623-preserve-rendered-tiles-current

Conversation

@ishveet

@ishveet ishveet commented Oct 28, 2026 •

Copy link
Copy Markdown

What this changes

Refs #623, item 4 (viewer behavior only). This partial fix leaves the issue's other items open.

A failed pixel request currently replaces a whole page with an error even if another tile or backdrop rendered successfully. This change preserves useful visible pixels and shows an error notice beside them. When no cached pixels intersect the visible page region, the existing full-page error remains.

  • Suppress identical failed requests while allowing other tiles, thumbnails, organize-grid images and print previews to finish.
  • Keep main-canvas diagnostics separate from aggregate page_errors(), so a failed preview cannot put a false warning on a successfully rendered main page.
  • Hide obsolete zoom/pan diagnostics after current rendering succeeds, while retaining recent failure history to suppress repeated failures.
  • Bound failure retention per page/consumer by max(64, current pixel demand), then trim it as demand changes or ends. An untried healthy tile can progress even after 64 other visible requests fail.
  • Preserve page/document invalidation, stale-result rejection, recovery and existing texture/cache admission checks.

No watchdog budgets, PDF interpretation, raster-cache architecture, GPU rendering or localization changes. No speedup is claimed.

How you verified it

Windows 11 x86-64, Rust/Cargo 1.98.1. Cargo checks used the low-disk profile, locked/offline dependencies and two jobs. Visual tests used Vulkan and PDFCRAFT_SYSTEM_FONTS=0.

The build used the project's existing CI font input: craft-fonts at b076417, with CRAFT_FONTS_REQUIRED=1. Its manifest licences and SHA-256 hashes were verified. No font files or font-policy changes enter this PR.

  • cargo fmt --check and git diff --check.
  • cargo clippy -p pdfcraft-ui-egui --all-targets -- -D warnings.
  • Focused UI library tile_ regressions: 17 passed.
  • Full UI library: 595 passed, 0 failed, 1 ignored.
  • Full UI --test it: 1,190 passed, 0 failed, 7 ignored.
  • Existing UI frame-budget tests: 3 passed; UI doc-test command passed with zero doc tests. These are not a before/after performance comparison.
  • Renderer library: 401 passed (the prior 400 plus the new upstream CropBox test); renderer --test it: 43 passed, 1 ignored.
  • cargo xtask layers, cargo xtask assets, cargo xtask parity.
  • cargo check -p pdfcraft-ui-egui --target wasm32-unknown-unknown (five existing renderer warnings and nine existing UI warnings in untouched code).
  • Native app/CLI rebuild and Vulkan viewer/control smoke: synthetic page creation/save/render, tiled view, UI inspection, fit view, screenshots and clean shutdown.

Archived red/green runs demonstrate the original pixel-loss bug and the independently reviewed follow-up failures with their corresponding corrections absent. The final focused selection also protects existing invalidation and rendering behavior. Its coverage includes both tile arrival orders, a successful backdrop, all failures, preview independence, main/preview diagnostics, zoom/pan, visible coverage at all four rotations, invalidation, stale results and recovery. The synthetic stress test exercises 512 distinct requests. Actual headless canvas geometry produces 81 visible tiles plus a backdrop and verifies continued scheduling after 64 failures; no giant framebuffer is allocated.

Font-baseline distinction: earlier runs without craft-fonts recorded full UI library 567 passed/17 failed/1 ignored, viewer 65/1, view 75/4 and control 58/1, with the known U+22EF failure sets. Those archived runs remain failures. With the documented CI input, unchanged main at 7555be3c first passed 570 UI library tests (0 failed, 1 ignored). Main advanced during validation, first to 21f6c29d and then dc943eeb. The scoped commit integrated cleanly; range-diff and exact added/deleted-line comparison confirm the reviewed patch is unchanged. The final counts above are from dc943eeb. No assertions or missing-glyph policy were relaxed.

Not run: full-workspace sweep, other-OS runtime, live browser runtime, fuzzing, real heavy-PDF end-to-end reproduction, a physical oversized-window/downlevel-adapter experiment, or comparable before/after benchmarks. The default graphics backend was not rerun; no resolution of its previously observed crash is claimed. The saturation case establishes headless layout/scheduling behavior, not native GPU throughput.

Never-crash

  • No new production panicking shortcuts, input-derived indexing/slicing, or unsafe.
  • Failure history contracts with current demand; existing texture and raster-memory guards remain.
  • Synthetic regressions cover the error behavior. This is not a renderer crash fix.

Provenance

  • No Adobe-derived/proprietary assets, font binaries, new dependencies or copied GPL/AGPL code.
  • Tests use synthetic pages and injected results; local visual evidence uses PdfCraft's own UI and allowed fonts. No screenshots or PDFs are added to the PR.
  • Existing asset checks pass; no new asset requires attribution.

Security

  • No new file access, network endpoints, MCP capabilities or control-channel authorization changes.
  • Existing root restrictions and opt-in, loopback/token-authenticated control remain unchanged.

Parity and reach

  • Deterministic UI tests exercise presentation; existing automation consumers retain aggregate page_errors() diagnostics.
  • ROADMAP.md and docs/roadmap.md updated. No new parity capability is claimed and no parity entry is changed.

Anything a reviewer should look at first

Review the request-history lifecycle and main-view diagnostic selection in canvas/cache.rs, and visible pixel coverage/error painting in canvas.rs.

PR #4501 remains open and handles successful renders with content warnings. It shares canvas.rs, canvas/cache.rs and canvas/tests.rs; expect source conflicts if both patches land. Preserve both its warnings/warnings_dismissed behavior and this PR's errors/error_demand handling when reconciling them. #4191 (watchdog deadlines) and #2113 (truncation notices) remain separate work. No other contributor's branch was changed.

Summary by CodeRabbit

  • Bug Fixes
    • Kept successfully rendered canvas tiles visible when another tile fails, with an error notice that reflects whether page content is still available.
    • Improved recovery by allowing unaffected tiles and sharper replacements to keep rendering after individual failures.
    • Prevented failures in thumbnails, print previews, and grid views from incorrectly appearing as main-canvas errors.
    • Limited repeated attempts for identical failed requests while allowing requests at other scales and for other views to proceed.
    • Adjusted failure history as rendering demand changes, and prevented repeated tile failures from starving visible content.

@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 1dbd29b and 1abf54e.

Files selected for processing: 5
  • ROADMAP.md
  • crates/ui-egui/src/canvas.rs
  • crates/ui-egui/src/canvas/cache.rs
  • crates/ui-egui/src/canvas/tests.rs
  • docs/roadmap.md
Files skipped from review as they are similar to previous changes: 1
  • ROADMAP.md

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Walkthrough

The canvas now tracks raster failures by request and rendering domain. It continues eligible work after failures and keeps visible cached pixels on screen while showing an error indication for partial page content.

Changes: Canvas render failure handling

Layer / File(s) Summary
Track failures and schedule eligible work
crates/ui-egui/src/canvas.rs, crates/ui-egui/src/canvas/cache.rs, crates/ui-egui/src/canvas/organize.rs, crates/ui-egui/src/canvas/tests.rs, ROADMAP.md, docs/roadmap.md
Failures are recorded and suppressed per request and domain. History is bounded by current demand. Successful results can clear matching failures. Eligible main, thumbnail, grid, and print work can proceed independently. Tests and roadmap notes cover these changes.
Render cached content with page errors
crates/ui-egui/src/canvas.rs, crates/ui-egui/src/canvas/tests.rs
The canvas checks for visible cached pixels before displaying a full-page error. It paints retained tiles and shows an error indication when failed content remains visible. Tests cover tile-result order, cached backdrops, and pages without visible pixels.

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix

Suggested reviewers: fredapps, aiongg

Merge Risk: Minimal · up to 1abf5

The change keeps successfully rendered page pixels visible with an error notice when another tile or backdrop fails. No merge-blocking issue remains from the review; the author's deterministic regression tests cover the scheduling and cache logic.

Security architecture risk: Low · up to c5d15

The change preserves partial page content while separating failures between the main view and previews. Existing rendering safeguards remain in the examined paths, and no introduced security defect was established. The assessment is limited to those paths rather than a complete security assurance.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — For the examined paths, document-induced render failures affect the receiving DocView's page caches, scheduling and diagnostics across its rendering consumers. Continued work expands beyond the formerly failed request, but no additional file, network, credential or cross-document authority was established.

Trust Boundaries and Controls

  • observed — Continued scheduling after failure does not bypass the existing result-admission boundary. Request-value membership in the latest queue rejects obsolete results, and successful pixel buffers still undergo size and admission checks before texture creation. These are retained controls, not a new request-identity mechanism.

Resilience and Maintainability Implications

  • inferred — Request-specific suppression improves failure containment by avoiding repeated identical failures without stopping independent rendering consumers. Inspected regression cases support this behavior under partial failure, saturation, invalidation and stale-result delivery; they do not establish exhaustive resource-exhaustion resistance.
Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title clearly identifies the UI fix: preserving rendered page tiles after partial render failures. It is specific and concise enough for project history.
Description check Passed The description covers the requested sections, explains the behavior and scope, documents verification results and limitations, and addresses never-crash, provenance, security, parity, and reviewer fo…
Docstring Coverage Passed Docstring coverage is 96.88% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentati…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description covers the requested sections, explains the behavior and scope, documents verification results and limitations, and addresses never-crash, provenance, security, parity, and reviewer focus.

Full details: Docstring Coverage

Explanation

Docstring coverage is 96.88% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 32 functions across 4 files. (2 skipped: 2 unsupported.)

Finishing Touches
Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@ishveet
ishveet marked this pull request as ready for review October 28, 2026 23:13
@croquete1

Copy link
Copy Markdown

Revisão

Boa melhoria para o #623 item 4 (só viewer, como o PR delimita). Revisei o que dá por leitura:

  • Never-panic melhorado: vários info.pages[i] viraram info.pages.get(i) com early-return (else { return }/continue), removendo um caminho de índice fora de limites.
  • Histórico de falhas limitado: RECENT_REQUEST_FAILURES = 64, com failure_limit escalando à demanda corrente e retain podando o que não é mais pedido — e, importante, o próprio autor pegou e documentou no ROADMAP que uma versão anterior podia starvar um tile visível não-tentado após 64 falhas, agora corrigido. Bom sinal de rigor.
  • Semântica: main_page_error só reporta falha da demanda atual; um raster válido completo supera falhas antigas; zooms novos escondem diagnósticos obsoletos. Pixels bons + aviso ao lado em vez de página inteira de erro.

Não consigo verificar a corretude do agendamento/cache inteira por leitura — mas a cobertura é extensa (ordenação, zoom/pan, retenção limitada, agendamento de preview, cobertura offscreen, recuperação, invalidação, resultados stale), e é esse conjunto de regressões determinísticas que serve de backstop para lógica de UI assim.

Relação: este PR é de cache de tiles de página, independente dos #5237/#5233/#5318 (pintura de padrão de ladrilho) — camadas diferentes, não concorrem. Observação: merge UNKNOWN — vale rebase/refresh para o CI recomputar. Fora isso, LGTM.

@FredApps FredApps left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReviewerA — source-read COMMENT (not an approval)

I read the diff: rendered page tiles are preserved after partial render failures (#623 item 4). The viewer's errors map changes from one message per page that was "never re-requested" to a bounded retry history per page (HashMap<usize, Vec<(RenderRequest, String)>>), only the current demand contributes diagnostics (error_demand, the last committed pixel demand including cached and failed requests before queue filtering), and a failed request leaves the successfully rendered visible tiles and backdrops on screen with an error notice, collecting demand after a failure because another in-flight tile can still succeed; a failed zoom-out may show the older tiles below the tiling threshold. Not blanking a page because one tile failed is the right behaviour for a viewer. The ROADMAP carries three log lines of this PR's own follow-ups. The risk is retry storms: a request that fails must not be re-sent every frame (the old comment said "never re-requested"; the new one says "bounded retry history"), so state the bound (a count and a backoff), and test that a page whose tile always fails stops asking after N tries. I read about 28 lines. I have not run it, and no exact-head CI result exists for this head, so this is a source-read comment and not an approval. Nothing was run.

I ran nothing and have no exact-head CI result for this PR, so this is not an approval. Where a diff was long I read its code hunks rather than every generated or documentation line.

Status: waiting_author.

@ishveet

ishveet commented Nov 2, 2026

Copy link
Copy Markdown
Author

Thanks for the review, @FredApps.

The retry-storm concern is addressed through exact-request failure suppression, rather than an N-attempt retry loop with timed backoff.

  • request_failed() tracks failed requests by their complete RenderRequest identity.
  • suppress_failed_requests() prevents identical tracked pixel requests from being resubmitted on subsequent frames.
  • RECENT_REQUEST_FAILURES = 64 is the minimum retained failure-history capacity per page/consumer domain, not a retry-attempt count. failure_limit() expands that capacity to cover current demand.
  • Obsolete failures can eventually be evicted, and invalidation clears failure history. This is not permanent suppression or a timed-backoff guarantee.

The regressions tile_review_failure_history_is_bounded_without_retrying_active_requests and tile_error_all_failures_keep_the_full_page_error_without_retrying cover active failure suppression and repeated frame behavior.

The updated PR is now mergeable against upstream main. Local Windows validation passed 182 distinct tests, including the combined image-drag/partial-render-failure regression, with strict Clippy and formatting checks passing.

Upstream CI still requires maintainer approval, so I am not claiming exact-head CI success.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants