Skip to content

CL-6560: Recover previewable content for stored file uploads - #273

Merged
TheGreatAxios merged 2 commits into
mainfrom
cl-empty-upload
Aug 21, 2026
Merged

CL-6560: Recover previewable content for stored file uploads#273
TheGreatAxios merged 2 commits into
mainfrom
cl-empty-upload

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Contributor

Summary

A real file uploaded through the Library's Files page (e.g. a ~10 KB SKILL.md) read back with an honest-looking row (Kind: File, Updated: 15s ago) but the detail pane said "This document has no content yet." — implying nothing had been uploaded.

This was stored-but-unreadable, not silently dropped. Verified directly against Postgres: createFileArtifact (@corbits/artifacts) is the one path a browser upload goes through, and it writes real bytes into the artifacts.upload side table while deliberately leaving the artifact row's own content column empty (source.upload.id is the reference). The reported artifact's upload row held all 15998 bytes, byte-identical to the original file. No data was lost — every previously-uploaded file with a source.upload reference is fully recoverable.

The bug was in the read path: GET /:artifactId returned the row's raw (empty) content column untouched, and the Library detail pane's doc/sheet/pdf renderers treat empty content as "nothing was ever stored" — correct for a blank co-edited doc, false for a real upload whose bytes just live out-of-band.

Fix

  • createArtifactDbStore().get() (packages/artifacts-hub/src/routes.ts) now resolves the out-of-band blob via resolveDownload (already used by the HTML preview route) and inlines it into content whenever it decodes as text — generalizing what /preview already did for text/html alone to every text-decodable MIME type (Markdown, plain text, CSV, JSON, ...).
  • For a genuinely non-text blob (an image, a real PDF, a legacy .docx/.xlsx), content stays empty — but the Library preview (apps/web/src/pages/library-page.tsx, packages/artifact-ui/src/artifact-renderer.tsx) now tells that apart from a truly-empty artifact via source.upload.mimeType, and shows an honest "We couldn't read this file's contents for preview" instead of "no content yet."
  • Added a ./renderer-kind server-safe subpath to @corbits/artifact-ui so the hub-side route can reuse isTextDecodableMediaType instead of duplicating the check.
  • Confirmed the existing 10 MiB per-file upload ceiling already rejects an oversized file with a 413 before any row is created (no bug there); added a regression test since none previously existed.

Verification

  • Unit tests for resolvePreviewableContent covering: text-decodable blob inlined, non-text blob left empty (no garbage decode), inline content never re-fetched, missing blob reference handled.
  • ArtifactRenderer tests asserting the honest "couldn't read this file's contents" message replaces "no content yet" only when a real upload is known to back the empty content.
  • Ran an end-to-end check against the reporter's real local Postgres row for the actual SKILL.md artifact: resolvePreviewableContent recovers all 15998 stored bytes as readable text where the raw content column was empty.
  • bun test green in packages/artifacts-hub (66 tests) and packages/artifact-ui (80 tests); bun run typecheck clean in packages/artifacts-hub, packages/artifact-ui, and apps/web; eslint clean on all changed files.

Fixes CL-6560.

Test plan

  • packages/artifacts-hub: bun test — 66 pass
  • packages/artifact-ui: bun test — 80 pass
  • apps/web: bun run typecheck clean; targeted bun test on touched files green
  • Manual end-to-end read against the real reported artifact's Postgres row

Covers resolving a file upload's out-of-band bytes back into an
artifact's content when they decode as text, an oversized upload never
landing as a stored-but-empty row, and the Library preview distinguishing
a genuinely empty artifact from one whose stored file just can't be
rendered.
A browser upload's bytes were always intact in the artifacts content
store, but the Library detail pane and its GET /:artifactId read the
artifact row's own content column, which createFileArtifact leaves
empty by design for every out-of-band upload — a real 10 KB Markdown
file read back as "This document has no content yet," implying nothing
had been uploaded.

createArtifactDbStore.get now resolves that out-of-band blob and
inlines it into content whenever it decodes as text, the same thing
the HTML preview route already did, generalized to every text-decodable
MIME type. A non-text blob (an image, a real PDF, a legacy .docx/.xlsx)
is left empty, but the Library preview now tells those two empty cases
apart: source.upload.mimeType marks a real stored file, so the doc,
sheet, and pdf renderers say "We couldn't read this file's contents for
preview" instead of implying the upload never happened.
@TheGreatAxios
TheGreatAxios merged commit 5a78cad into main Aug 21, 2026
5 checks passed
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