Skip to content

BUGFIX: Optimize dashboard images and downloads - #8

Open
bmdavis419 wants to merge 15 commits into
mainfrom
fix/dashboard-thumbnails-downloads
Open

BUGFIX: Optimize dashboard images and downloads#8
bmdavis419 wants to merge 15 commits into
mainfrom
fix/dashboard-thumbnails-downloads

Conversation

@bmdavis419

@bmdavis419 bmdavis419 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Serve dashboard raster previews through a fixed 480 × 360 WebP endpoint, cache derivatives in R2, and retain signed-link authorization.
  • Keep lazy loading, use versioned immutable URLs for public files, avoid full-size fallbacks, and purge derivatives with their originals.
  • Fix malformed R2 response metadata so full ZIP and binary downloads return 200 while valid byte ranges return 206.

Important files

  • apps/web/src/routes/t/[id]/[version]/grid.webp/+server.ts
  • apps/web/src/lib/file-thumbnail.ts
  • apps/web/src/lib/components/files/FileThumb.svelte
  • apps/web/src/lib/server/download-response.ts

Validation

  • pnpm format:check
  • pnpm test
  • pnpm check

Open in Devin Review

Note

Add WebP thumbnail generation and serving for dashboard file grid

  • Adds a new route /t/[id]/[version]/grid.webp that generates, caches, and serves Cloudflare Image-transformed WebP thumbnails with ETag/304 support, race-safe storage, and quota enforcement.
  • FileThumb.svelte now requests sized WebP thumbnails for supported image types and retries with a fresh private grant if the initial fetch fails.
  • Private grants can be scoped to a thumbnail-source purpose; thumbnail-source reads are excluded from download counts via the new shouldRecordFileDownload function.
  • rangeHeaders is refactored to strictly validate R2 range metadata, returning null on invalid input and correctly distinguishing full (200) from partial (206) responses.
  • Storage quota now includes thumbnail_size_bytes and thumbnail keys are removed during version purge.
  • Risk: Cloudflare Images Transformations must be enabled on the content zone for thumbnail generation to work; missing this step will cause thumbnail requests to fail silently.

Macroscope summarized 7ee3941.

Greptile Summary

Summary

This change adds cached dashboard thumbnails with scoped access grants and storage accounting, and improves download response handling.

Two previously reported thumbnail issues are resolved: failed Cloudflare image transformations are rejected before caching, and competing thumbnail writers retain matching object-size and quota metadata. The remaining issue is in partial download responses: length-only R2 range metadata can produce a Content-Range header for a different byte interval than the returned body.

Confidence Score: 4/5

Partial downloads can still be labeled as a different byte interval from the body returned by R2.

The remaining blocking failure is in apps/web/src/lib/server/download-response.ts. Although bmdavis419 stated that length-only metadata now begins at byte zero, the current implementation returns bytes 100-199/1000 for { length: 100 } when the request is bytes=100-199; the body is a prefix and must be labeled bytes 0-99/1000.

Files Needing Attention: apps/web/src/lib/server/download-response.ts

T-Rex T-Rex Logs

What T-Rex did

  • Executed focused success and failure paths for Cloudflare transform detection, including an isTransformedWebpResponse test where image/webp with quality=75 and err=9401 returned false while a valid transformed WebP returned true, and verified the thumbnail route rejects failed transforms before storage so they are not cached.
  • Ran a focused concurrency test for competing thumbnail writers; exactly one compare-and-swap update succeeded, the losing object was deleted, and the surviving object's 17-byte size matched D1's thumbnail_size_bytes: 17.
  • T-Rex produced a proof for a posted P1 finding about rangeHeaders metadata; validation included a focused test source and logs showing length-only and end-relative behavior.
  • T-Rex produced a proof for a second posted P1 finding; see the corresponding review comment for details.
  • Focused service-contract execution showed unavailable thumbnails return 404 without a grant and 200 with Cache-Control private, no-store for an authorized request, ensuring unavailable thumbnails do not enter the public cache-revalidation path; the parent revision showed related public-cache behavior differences.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (1)

  1. General comment

    P1 Length-only R2 metadata incorrectly inherits the request's start offset

    • Bug
      • For an R2 range containing { length: 100 } but neither offset nor suffix, rangeHeaders returns Content-Range: bytes 100-199/1000 for request bytes=100-199. The required prefix result is bytes 0-99/1000.
    • Cause
      • At apps/web/src/lib/server/download-response.ts:70-78, the no-suffix/no-offset branch falls back to requestedRangeOffset(requestedRange, ...), which uses the request start rather than treating length-only metadata as a prefix range.
    • Fix
      • When range has a numeric length but no numeric offset and no numeric suffix, set the response offset to 0; retain the existing explicit-suffix branch for end-relative behavior.

    T-Rex Ran code and verified through T-Rex

Fix all with Greploop

Fix All in Codex

Prompt To Fix All With AI
### Issue 1
apps/web/src/lib/server/download-response.ts:73-78
**Length-only ranges inherit the request offset**

When R2 provides only `{ length: 100 }`, it has returned a prefix body, but this fallback derives the offset from the request. A request for `bytes=100-199` on a 1,000-byte object therefore sends the prefix body with `Content-Range: bytes 100-199/1000` instead of `bytes 0-99/1000`. Range clients and intermediary caches can associate the returned bytes with the wrong portion of the file. Treat length-only metadata as starting at zero; retain the end-relative calculation only for an explicit `suffix`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (10): Last reviewed commit: "fix: keep unavailable thumbnails private" | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

The pull request adds versioned WebP dashboard thumbnails for supported raster images. It adds cached delivery, private access validation, thumbnail-aware download accounting, safer range responses, route handling, runtime configuration, purge cleanup, and deployment instructions.

Dashboard thumbnails

Layer / File(s) Summary
Thumbnail contracts and image rendering
apps/web/src/lib/file-thumbnail.ts, apps/web/src/lib/file-thumbnail.test.ts, apps/web/src/lib/components/files/FileThumb.svelte, apps/web/src/lib/dashboard/api.ts, apps/web/src/lib/server/file-content-link.*, apps/web/src/routes/api/files/[id]/link/+server.ts
Defines WebP settings, cache keys, supported image detection, signed source URLs, grant-protected content links, and thumbnail rendering for resizable images.
Thumbnail endpoint and access flow
apps/web/src/routes/t/..., apps/web/src/lib/server/private-grant.*, apps/web/src/lib/server/host-gate.*, apps/web/wrangler.jsonc
Adds the versioned thumbnail endpoint. It validates access, serves cached blobs, fetches and stores transformed WebP data, and returns thumbnail response headers.
Download range and accounting handling
apps/web/src/lib/server/auth-policy.*, apps/web/src/lib/server/download-response.*, apps/web/src/routes/f/[id]/+server.ts
Validates range metadata, supports full and partial responses, rejects unsafe ranges, and excludes verified thumbnail requests from download recording.
Thumbnail storage, quota, and purge
apps/web/migrations/0010_thumbnail_storage.sql, apps/web/src/lib/server/thumbnail-storage.*, apps/web/src/lib/server/storage-quota.ts, apps/web/src/lib/server/services/files.ts
Stores thumbnail metadata and blobs, applies positive quota growth, compensates failed writes, and deletes thumbnail keys during purge.
Deployment and release setup
.agents/skills/deploy-fresh-instance/SKILL.md, docs/release.md
Deployment checks and release instructions now cover Image Transformations, DNS configuration, and updated full-object and ranged response behavior.

Possibly related PRs

  • davis7dotsh/aDrive#1: Extends the related storage-quota logic to include thumbnail bytes and thumbnail storage paths.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed The title clearly summarizes the primary changes to dashboard images and downloads.
Description check ✅ Passed The description directly explains thumbnail generation, download handling, authorization, storage, and validation changes.

Comment @coderabbitai help to get the list of available commands.

macroscopeapp[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/web/src/lib/server/private-grant.test.ts (1)

72-96: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add the inverse purpose-binding assertion.

The test proves that a purpose-bound grant fails without its purpose. It does not prove that an unscoped grant cannot be accepted when the verifier supplies purpose: 'thumbnail-source'.

Add an unscoped grant assertion:

Proposed test addition
 		await expect(
 			verifyPrivateGrant({ ...base, purpose: 'thumbnail-source' })
 		).resolves.toBe(true);
 		await expect(verifyPrivateGrant(base)).resolves.toBe(false);
+
+		const unscopedGrant = await mint();
+		await expect(
+			verifyPrivateGrant({
+				...base,
+				expiresAtSeconds: unscopedGrant.expiresAtSeconds,
+				signature: unscopedGrant.signature,
+				purpose: 'thumbnail-source'
+			})
+		).resolves.toBe(false);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/lib/server/private-grant.test.ts` around lines 72 - 96, Extend
the `binds internal grants to their purpose` test to mint an unscoped grant
without a purpose, then verify it with `purpose: 'thumbnail-source'` and assert
verification resolves to false. Keep the existing purpose-bound grant assertions
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/web/src/lib/server/private-grant.test.ts`:
- Around line 72-96: Extend the `binds internal grants to their purpose` test to
mint an unscoped grant without a purpose, then verify it with `purpose:
'thumbnail-source'` and assert verification resolves to false. Keep the existing
purpose-bound grant assertions unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6413c45f-3ded-4313-9ff9-6b5933275f38

📥 Commits

Reviewing files that changed from the base of the PR and between e622182 and 707c6e1.

📒 Files selected for processing (8)
  • apps/web/src/lib/file-thumbnail.test.ts
  • apps/web/src/lib/file-thumbnail.ts
  • apps/web/src/lib/server/auth-policy.test.ts
  • apps/web/src/lib/server/auth-policy.ts
  • apps/web/src/lib/server/private-grant.test.ts
  • apps/web/src/lib/server/private-grant.ts
  • apps/web/src/routes/f/[id]/+server.ts
  • apps/web/src/routes/t/[id]/[version]/grid.webp/+server.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/web/src/lib/file-thumbnail.test.ts
  • apps/web/src/lib/server/auth-policy.test.ts
  • apps/web/src/routes/t/[id]/[version]/grid.webp/+server.ts
  • apps/web/src/lib/file-thumbnail.ts

@bmdavis419

Copy link
Copy Markdown
Contributor Author

Addressed the final CodeRabbit test nit in 805bea9: the grant tests now prove both directions of purpose binding, including that an ordinary unscoped grant fails thumbnail-source verification.

devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@bmdavis419

Copy link
Copy Markdown
Contributor Author

Addressed the two CodeRabbit documentation nits in 5d8d41b as well: both setup guides now identify the zone that owns CONTENT_ORIGIN as the Image Transformations zone, and the Custom Domains prerequisite says to remove conflicting CNAMEs because deployment creates the application DNS records.

greptile-apps[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

macroscopeapp[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

greptile-apps[bot]

This comment was marked as resolved.

macroscopeapp[bot]

This comment was marked as resolved.

greptile-apps[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 2 new potential issues.

View 10 additional findings in Devin Review.

Open in Devin Review

Comment thread apps/web/src/lib/server/services/files.ts Outdated
Comment thread apps/web/src/routes/t/[id]/[version]/grid.webp/+server.ts Outdated
Comment thread apps/web/src/lib/server/download-response.ts Outdated
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.

1 participant