Repository navigation
feat(web): preview archives, sqlite databases, and decoded images - #21467
maria-rcks wants to merge 5 commits into
Conversation
| if ( | ||
| ![1, 2, 3, 4].includes(metadata.components) || | ||
| ![8, 16].includes(metadata.bitsPerSample) || | ||
| ![0, 1, 2, 3].includes(metadata.type) | ||
| ) |
There was a problem hiding this comment.
🟠 High files/imageDecode.worker.ts:123
Tiled RGB/RGBA previews are emitted with missing color channels, so the resulting PNG is incorrect. The tiled path in tiff's readTileData writes only one sample per pixel, but this worker accepts multi-component tiled images and converts their data as RGB; reject tiled images with more than one component or decode all samples.
| if ( | |
| ![1, 2, 3, 4].includes(metadata.components) || | |
| ![8, 16].includes(metadata.bitsPerSample) || | |
| ![0, 1, 2, 3].includes(metadata.type) | |
| ) | |
| if ( | |
| (metadata.tiled && metadata.components > 1) || | |
| ![1, 2, 3, 4].includes(metadata.components) || | |
| ![8, 16].includes(metadata.bitsPerSample) || | |
| ![0, 1, 2, 3].includes(metadata.type) | |
| ) |
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/files/imageDecode.worker.ts around lines 123-127:
Tiled RGB/RGBA previews are emitted with missing color channels, so the resulting PNG is incorrect. The tiled path in `tiff`'s `readTileData` writes only one sample per pixel, but this worker accepts multi-component tiled images and converts their data as RGB; reject tiled images with more than one component or decode all samples.
| } else { | ||
| files = new Map(); | ||
| let entries: ArchiveEntry[]; | ||
| if ( |
There was a problem hiding this comment.
🟡 Medium files/binaryPreview.worker.ts:165
A valid empty ZIP uploaded as download with MIME application/zip falls through to tarEntries and is rejected as having no readable entries. This branch recognizes ZIPs only by filename or the local-file signature; also recognize the ZIP end-of-central-directory signature (or pass the known ZIP format through).
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/files/binaryPreview.worker.ts around line 165:
A valid empty ZIP uploaded as `download` with MIME `application/zip` falls through to `tarEntries` and is rejected as having no readable entries. This branch recognizes ZIPs only by filename or the local-file signature; also recognize the ZIP end-of-central-directory signature (or pass the known ZIP format through).
| if (view.getUint16(offset + 10, true) > ARCHIVE_MAX_ENTRIES) | ||
| throw new Error("This archive contains more than 2,048 entries."); |
There was a problem hiding this comment.
🟠 High files/boundedZip.ts:22
Valid ZIP64 archives with at most 2,048 entries are rejected as oversized, so ZIP64 ZIP/JAR/DOCX files cannot be previewed. ZIP64 uses 0xffff in this legacy EOCD field as a sentinel rather than the actual count; validate the ZIP64 count or defer this limit to the parsed entries.length check below.
- if (view.getUint16(offset + 10, true) > ARCHIVE_MAX_ENTRIES)
- throw new Error("This archive contains more than 2,048 entries.");🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/files/boundedZip.ts around lines 22-23:
Valid ZIP64 archives with at most 2,048 entries are rejected as oversized, so ZIP64 ZIP/JAR/DOCX files cannot be previewed. ZIP64 uses `0xffff` in this legacy EOCD field as a sentinel rather than the actual count; validate the ZIP64 count or defer this limit to the parsed `entries.length` check below.
| /\.(zip|jar)$/i.test(input.name) || | ||
| new DataView(input.bytes).getUint32(0, true) === 0x04034b50 | ||
| ) { | ||
| const archive = await boundedZip(input.bytes, ARCHIVE_ENTRY_MAX_BYTES); |
There was a problem hiding this comment.
🟠 High files/binaryPreview.worker.ts:169
A ZIP/JAR with any member larger than 10 MB fails here before its entries are listed, so users cannot browse the archive even though the entry handler is intended to reject only opening oversized members. Pass the 50 MB BINARY_PREVIEW_MAX_BYTES limit to enumeration and keep the 10 MB check in the entry handler.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/files/binaryPreview.worker.ts around line 169:
A ZIP/JAR with any member larger than 10 MB fails here before its entries are listed, so users cannot browse the archive even though the `entry` handler is intended to reject only opening oversized members. Pass the 50 MB `BINARY_PREVIEW_MAX_BYTES` limit to enumeration and keep the 10 MB check in the `entry` handler.
| onError={() => setError("Unable to load image.")} | ||
| /> | ||
| </div> | ||
| <PreviewImage key={url} src={url} name={props.name} mimeType={props.mimeType} /> |
There was a problem hiding this comment.
🟡 Medium files/AttachmentFilePreview.tsx:281
When an image fails to load, PreviewImage shows its local failure surface instead of setting this component’s error, so the parent FileSurfaceFailure and its retry action never appear. Report image load failures through setError so retry can refresh the asset URL and reload the image.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/files/AttachmentFilePreview.tsx around line 281:
When an image fails to load, `PreviewImage` shows its local failure surface instead of setting this component’s `error`, so the parent `FileSurfaceFailure` and its retry action never appear. Report image load failures through `setError` so retry can refresh the asset URL and reload the image.
| ) | ||
| throw new Error("This TIFF color format is not supported."); | ||
| await validateTiffStrips(bytes, metadata); | ||
| const image = decodeTiff(bytes, { pages: [0] })[0]!; |
There was a problem hiding this comment.
🟠 High files/imageDecode.worker.ts:130
TIFFs with a non-default Orientation tag fail to produce a preview, so valid camera or scanner images (for example, Orientation=6) show “Unable to load image.” The decodeTiff call at line 130 reaches tiff@7.1.3's decodeImageData, which throws Unsupported orientation; handle the orientation before decoding or reject it explicitly with a clear error.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/files/imageDecode.worker.ts around line 130:
TIFFs with a non-default `Orientation` tag fail to produce a preview, so valid camera or scanner images (for example, `Orientation=6`) show “Unable to load image.” The `decodeTiff` call at line 130 reaches `tiff@7.1.3`'s `decodeImageData`, which throws `Unsupported orientation`; handle the orientation before decoding or reject it explicitly with a clear error.
| } finally { | ||
| await reader.cancel(); | ||
| } | ||
| } else if (image.compression !== 1 && image.compression !== 32773) |
There was a problem hiding this comment.
🟠 High files/imageDecode.worker.ts:104
PackBits-compressed TIFFs pass this validation but fail during decodeTiff, so valid images using compression 32773 cannot be previewed. The tiff decoder throws Unsupported Compression: PackBits; reject 32773 here unless a PackBits decoder is added.
| } else if (image.compression !== 1 && image.compression !== 32773) | |
| } else if (image.compression !== 1) |
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/files/imageDecode.worker.ts around line 104:
PackBits-compressed TIFFs pass this validation but fail during `decodeTiff`, so valid images using compression `32773` cannot be previewed. The `tiff` decoder throws `Unsupported Compression: PackBits`; reject `32773` here unless a PackBits decoder is added.
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: unavailable · PR result: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
ApprovabilityVerdict: Needs Human Approval Macroscope's review found this PR requires human approval — This PR introduces substantial new archive, SQLite, HEIF, and TIFF preview workflows, plus changes existing image and Office preview paths and production bundling. The added lint suppressions and unresolved archive/image correctness findings make the runtime and maintenance impact unsuitable for automatic approval. Needs human approval because:
Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more. |
| ) { | ||
| if (view.getUint32(offset, true) !== 0x06054b50) continue; | ||
| if (offset + 22 + view.getUint16(offset + 20, true) !== bytes.byteLength) continue; | ||
| if (view.getUint16(offset + 10, true) > ARCHIVE_MAX_ENTRIES) |
There was a problem hiding this comment.
🟠 High files/boundedZip.ts:26
A ZIP can declare at most 2,048 entries here while containing hundreds of thousands of central-directory records, causing JSZip.loadAsync(bytes) to allocate an object for every record before the later entries.length check; opening it can exhaust the preview worker's memory or hang it. The check reads only the EOCD's declared count, but JSZip 3.10.1 parses records until it reaches the end of the directory, so validate the actual record count with a byte-size bound before calling loadAsync, or use a parser that enforces the limit during parsing.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/files/boundedZip.ts around line 26:
A ZIP can declare at most 2,048 entries here while containing hundreds of thousands of central-directory records, causing `JSZip.loadAsync(bytes)` to allocate an object for every record before the later `entries.length` check; opening it can exhaust the preview worker's memory or hang it. The check reads only the EOCD's declared count, but JSZip 3.10.1 parses records until it reaches the end of the directory, so validate the actual record count with a byte-size bound before calling `loadAsync`, or use a parser that enforces the limit during parsing.
archives and sqlite files now open in the files viewer from attachments and agent links. ZIP/JAR, TAR, TAR.GZ/TGZ and GZ show folders, sizes and dates, with text/image entry previews. sqlite opens bytes in memory with read-only table counts and 50-row pages. TIFF and HEIC/HEIF fall back to worker decoding after native decoding fails, preserving image zoom. inputs are capped at 50 MB, archive expansion at 200×/50 MB/2,048 entries, and image dimensions at 32 megapixels. mobile is out of scope. PSD remains unsupported because layered compositing needs a separate decoder and validation pass.
sql.js 1.15.0 supplies maintained browser SQLite WASM; tiff 7.1.3 supplies maintained TIFF decoding. both and tiff's iobuffer dependency use MIT licenses and are registered in the license configuration; OSV returned no known advisories for these versions. existing JSZip and heic-to are reused. parsers load lazily in workers; the new archive/database renderer and all parsers are absent from the main source map. source-map comparison identified imageCompression/imageDimensions and PierreEntryIcon moving into main. the image viewer now loads through a dynamic import, the existing HEIF classifier preserves the compression boundary, and shared icons retain their own chunk. production builds through testbox measured base 6932387 → original head d34e168 → 26597b3: bootstrap 7.68 → 7.70 → 6.66 kB; main 1,061.01 → 1,072.10 → 1,050.76 kB, before gzip. the new viewer and decoder modules remain absent from main. HEIC/HEIF chips share TIFF's image icon across file trees, attachments, composer references and transcript links.
verified real ZIP/JAR/TAR/TGZ/TAR.GZ/GZ and sqlite/.db attachments, a real Codex turn's file links, text/image entries, all 125 sqlite rows across three pages, table switching, TIFF/HEIC decoding and zoom, side panel/full screen, light/dark, and 390px paging. corrupt and excessive-ratio ZIPs reject; a DOCX still renders with the shared ZIP guards. scoped lint, web/shared typechecks, 108 shared preview tests and the production web build passed through testbox. the fix pass also verified a fresh Codex turn returning HEIC/HEIF/TIFF links, light and 390px dark icon rendering, TIFF attachment decoding and zoom, and HEIC decoding from an agent link. the final production bundle also opened and zoomed the TIFF attachment and paged the sqlite table. 27 icon tests, 10 browser image/dialog tests, 108 shared preview tests, web typecheck and scoped lint passed through testbox. the new recording export failed when the preview host disconnected; the existing recordings remain above. native Electron and relay/tunnel clients were not exercised. the ci fix at 1b88a01 moves the dynamic decoder import outside the hook so React Compiler can optimize AttachmentImage without loading the decoder eagerly. the existing asset test now covers all eight supported binary extensions with server-assigned MIME types despite a text/html claim, while unsupported 7z files stay downloads; the desktop picker expectation includes HEIF. reproduced all three failures on 26597b3, then passed 44 asset tests, 9 desktop window tests, 108 shared preview tests, all 1,044 React Compiler coverage cases, 25 file-panel tests, 27 icon tests and 13 browser image/dialog/HTML-recovery tests through testbox. the follow-up at 98422ab also updates the mobile file-chip test to accept the eight binary formats, retains 7z rejection, and verifies those formats through exact host-file URLs; all 19 mobile file-chip tests and 44 asset tests pass. the unrelated workflow-highlighting test passed all three cases in isolation without changes. scoped lint passed with one pre-existing warning, and web/server/desktop/mobile typechecks and the production web build passed. same-worker production builds measured 26597b3 → 1b88a01: bootstrap 6.66 → 6.66 kB and main 1,050.76 → 1,050.76 kB before gzip; the binary viewer and decoder sources remain absent from main. this ci pass changes no UI behavior and retains the existing evidence; real-provider e2e was not repeated. final github ci and the one final pr-check snapshot passed on 98422ab, including Test, Test Web, all eight server shards, lint, build/typecheck, release smoke, Rust, and native fingerprint checks.
stack: main ← #21355 ← #21363 ← #21362 ← #21466 ← #21467 ← #21469.
Built by gpt-6.1-sol in Codex, orchestrated from T3 Code.