Skip to content

Allow viewing shared documents on share and tracked link pages - #45

Open
zmeyer44 wants to merge 5 commits into
mainfrom
share-link-document-preview
Open

Allow viewing shared documents on share and tracked link pages#45
zmeyer44 wants to merge 5 commits into
mainfrom
share-link-document-preview

Conversation

@zmeyer44

@zmeyer44 zmeyer44 commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Problem

When a recipient opens a tracked link created with View Only, they see only the file name and size — the document card isn't clickable and there is no way to view the document. This was true for both view-only and download links on /t/[token], and for regular share links on /shared/[token].

Root cause was twofold:

  • The file card was a static div with no click handler.
  • View-only links had no server path to the file bytes at all: the only content endpoint (trackedLinks.getDownloadUrl) hard-rejects access !== "download".

Changes

Server

  • New public getPreviewUrl mutation on the trackedLinks router — same validation as getDownloadUrl (active, expiry/validity window, max views, password, email, workspace match, folder containment) but allowed for both view and download access, and it does not increment the download counter.
  • Same getPreviewUrl on the shares router — respects the maxDownloads cap but previewing doesn't consume a download.

Client

  • New SharedFilePreview component (features/files/shared-preview) that fetches the signed URL and renders the existing PreviewArea (PDF, DOCX, images, video, audio, markdown, CSV, HTML, text).
  • /t/[token] and /shared/[token]: the file card is now a clickable button, and file rows inside shared folders are clickable too. Clicking opens the rendered document inline, with a Back button and — only when the link allows downloads — a Download button. The card widens while previewing.
  • PreviewArea / UnsupportedPreview: onDownload is now optional, so view-only links show "Preview is not available for this file type" without offering a download button.

Notes

  • View Only remains a UX-level restriction: rendering a preview necessarily hands the browser a signed URL, so a determined user can still save the bytes. This matches how other document-sharing tools treat view-only.
  • Heads-up: download-all-shared-folder (Add download all button to shared folder page #44 area) also touches apps/web/app/shared/[token]/page.tsx, so a small merge conflict is expected in the file-row/download-handler area whichever lands second.

Testing

  • pnpm typecheck / pnpm lint (tsc) pass in apps/web.
  • No existing e2e specs reference these pages.

🤖 Generated with Claude Code

Update: Excel preview support

Second commit adds viewing for Excel files, which previously hit the "unsupported" state:

  • New XlsxViewer powered by SheetJS (installed from the SheetJS CDN tarball — the npm-registry xlsx package is stale at 0.18.5 with known vulnerabilities). Handles both legacy .xls and modern .xlsx.
  • Converts the active sheet to CSV and reuses the existing CsvPreview table (search, sort, pagination), with a tab bar for multi-sheet workbooks.
  • Library is dynamically imported client-side only, matching the docx/pdf viewer pattern, so it doesn't affect the main bundle.
  • Works in the in-app file viewer and on the share/tracked link pages from this PR.

Recipients of tracked links (including View Only) and share links could
only see the file name and size — there was no way to open the document.
View-only links had no server path to the file at all since
getDownloadUrl rejects access !== "download".

- Add public getPreviewUrl procedures to the trackedLinks and shares
  routers: same validation as getDownloadUrl, but allowed for view-only
  access and not counted against download limits
- Add SharedFilePreview component reusing the in-app PreviewArea
- Make file cards and folder file rows clickable on /t/[token] and
  /shared/[token], opening an inline preview with Back and (for
  download links) Download actions
- Make onDownload optional in PreviewArea/UnsupportedPreview so
  view-only links don't offer a download button

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
locker-web Ready Ready Preview Aug 22, 2026 12:18am

Request Review

@greptile-apps

greptile-apps Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds inline document previews to public share and tracked-link pages and extends the existing viewer with Excel support.

  • Adds public signed-preview URL procedures for shared and tracked files.
  • Adds clickable file and folder-file previews with conditional download actions.
  • Adds SheetJS-based XLS/XLSX rendering through the existing CSV table viewer.
  • Adjusts PDF sizing behavior and analytics duration formatting.

Confidence Score: 2/5

The PR does not appear safe to merge until tracked-link previews enforce maximum-view restrictions and failed text preview requests surface as errors.

The preview endpoint still returns complete signed file URLs without consuming the tracked link's view allowance, and the shared text-preview path still renders ordinary HTTP error responses as document content.

Files Needing Attention: apps/web/server/trpc/routers/tracked-links.ts; apps/web/features/files/shared-preview/index.tsx

Important Files Changed

Filename Overview
apps/web/server/trpc/routers/tracked-links.ts Adds a public preview URL procedure for tracked files while retaining an unresolved maximum-view enforcement issue.
apps/web/server/trpc/routers/shares.ts Adds a scoped public preview URL procedure for individual and folder-shared files.
apps/web/features/files/shared-preview/index.tsx Adds the shared preview orchestrator, but its text fetch still treats non-success HTTP bodies as document content.
apps/web/components/xlsx-viewer.tsx Adds client-side workbook parsing, sheet selection, and CSV-table rendering.
apps/web/app/t/[token]/page.tsx Adds inline previews to tracked-link file cards and folder rows with download controls based on access mode.
apps/web/app/shared/[token]/page.tsx Adds inline previews and navigation for directly shared files and files within shared folders.

Sequence Diagram

sequenceDiagram
  participant R as Recipient
  participant P as Share/Tracked Page
  participant T as tRPC Preview Procedure
  participant S as Storage
  participant V as PreviewArea
  R->>P: Select shared file
  P->>T: Request preview URL
  T->>T: Validate token and file scope
  T->>S: Create signed URL
  S-->>T: Signed file URL
  T-->>P: Preview URL
  P->>V: Render URL or fetched text
  V->>S: Fetch file bytes
  S-->>V: Document content
Loading

Reviews (5): Last reviewed commit: "better duration format" | Re-trigger Greptile

if (link.validUntil && new Date(link.validUntil) < new Date()) {
throw new Error("Link is no longer active");
}
if (link.maxViews && link.viewCount >= link.maxViews) {

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.

P1 security Preview bypasses maximum views

When a valid tracked link remains below maxViews, getPreviewUrl returns complete one-hour signed file URLs without incrementing viewCount, so a recipient can repeatedly retrieve the document without consuming the configured allowance.

How this was verified: The public mutation checks viewCount, returns a signed URL, and contains no counter update, while only the separate access procedure increments the counter.

Knowledge Base Used: Uploads, Share Links, Upload Links, and Tracked Links

) {
throw new Error("Incorrect password");
}
if (link.maxDownloads && link.downloadCount >= link.maxDownloads) {

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.

P1 security Preview bypasses download limits

When a share has maxDownloads, getPreviewUrl returns complete one-hour signed file URLs without incrementing downloadCount, so a recipient can retrieve the file repeatedly without consuming the configured limit.

How this was verified: The preview mutation only reads downloadCount, whereas both existing byte-serving paths conditionally increment it before returning access to the file.

Knowledge Base Used: Uploads, Share Links, Upload Links, and Tracked Links


const vt = getViewerType(file.mimeType, file.name);
if (vt === "text" || vt === "markdown" || vt === "csv" || vt === "html") {
const text = await fetch(url).then((r) => r.text());

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.

P2 Error responses become preview content

For text-like files, fetch(url).then((r) => r.text()) accepts HTTP error responses as document content, so storage errors such as 403, 404, or 410 responses are rendered or parsed as the shared file instead of entering the component's error state.

Knowledge Base Used: File Explorer, File Viewer, and Virtual Filesystem

Excel files previously fell through to the unsupported-preview state.
Add a spreadsheet viewer using SheetJS (installed from the SheetJS CDN
tarball, since the npm-registry xlsx package is stale and has known
vulnerabilities), which handles both legacy .xls and modern .xlsx.

The viewer converts the active sheet to CSV and reuses the existing
CsvPreview table (search, sort, pagination), with a tab bar for
multi-sheet workbooks. The library is dynamically imported client-side
only, matching the docx/pdf viewer pattern. Works in the in-app file
viewer and on share/tracked link pages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Landscape PDFs overflowed horizontally on initial load. Two bugs:

- The container-width ResizeObserver attached in a mount-only effect,
  but on mount the loading skeleton is rendered and the scroll container
  doesn't exist yet, so containerWidth stayed 0 and PDFPage skipped its
  fit-to-width clamp entirely, rendering wide pages at natural size.
  The effect now re-attaches when loading completes.
- With the width actually measured, the auto-scale-on-load effect and
  the fit-width/fit-page handlers double-applied the fit factor (scale
  was set to fitScale and PDFPage multiplied by min(fitScale, 1) again,
  yielding fitScale^2). Removed the redundant auto-scale effect — the
  per-page clamp already makes scale 1 fit the full width — and divided
  the clamp back out in the fit handlers so they hit exact targets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The share-page card is vertically centered and the preview area had no
fixed height, so the page jumped as the loader was swapped for the
rendered document. Give SharedFilePreview a fixed-height container and
force the preview roots to fill it (same [&>div]:h-full pattern the
in-app viewer uses), so the card height is stable from first paint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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