Public: anyone can read this; the crew can change it.
A .zip, .tar or .tar.gz file in a repository shows the list of what is inside it, without unpacking it #10325
Check · Review · Land · Live done — Landed and live in production.
A `.zip`, `.tar` or `.tar.gz`/`.tgz` file in a repository now opens as a table of what is inside it, and nothing is unpacked. The type check, `bun scripts/gate.ts`, `bun scripts/design-lint.ts` and the render and blob tests pass. `bun scripts/fast.ts` named 1,459 of 2,391 test files, so I ran only the render, blob and repo-page tests myself and left the full run to the forge's check.
shipped production is serving it deploy f21b126
A .zip, .tar or .tar.gz/.tgz file in a repository now opens as a table of what is inside it, and nothing is unpacked. The type check, bun scripts/gate.ts, bun scripts/design-lint.ts and the render and blob tests pass. bun scripts/fast.ts named 1,459 of 2,391 test files, so I ran only the render, blob and repo-page tests myself and left the full run to the forge's check.
What changed
kinds.ts: there is a newarchivekind. The extension names it and the bytes must confirm it:PK\x03\x04for zip,ustarat offset 257 for tar, and1f 8bfor.tgzor.tar.gz. A bare.gzstays binary. The inline limit is 50 MB.archive.ts: this reads the entry list and nothing else.
- zip: it reads the central directory, including zip64 sizes.
- tar: it reads the 512-byte headers, checks each checksum, and handles pax and GNU long names.
- tar.gz: it uses gunzipSync with maxOutputLength set to 128 MB.
- It adds no dependency.
archive-view.ts:
- Table: path, size and modified date (UTC), sorted by path, with the folder part muted. - Footer: entry count and total unpacked size. - Escaping: names are escaped, and control and bidi characters in them are replaced.
- Limits:
- 2,000-entry cap: past 2,000 entries it lists the first 2,000, sorted, and says how many it left out. The count and total size still cover every entry. - Fallbacks: a truncated or damaged archive, an encrypted zip, a gzip bomb and an archive over 50 MB each fall back to the Download card with one sentence saying why. None of them returns a 500.
repo.ts: the binary card now shows that sentence in place of "not shown", andrenderers.tsregisters the new renderer.- Tests:
test/render-archive.test.tscovers everything the issue lists. It builds a zip, a tar and a tar.gz in the test, and also covers an encrypted zip and a bad tar checksum.render-kinds.test.tshas samples for the new extensions.
Not done
- Styling: I added no CSS class. The table reuses
.cardand.table, so there is nothing new to register fordesign-lint. A short note went intodocs/design/areas/render.md. - Zip names: they are decoded as UTF-8, not CP437.
- Over 128 MB: a
.tar.gzthat inflates past 128 MB is not listed. Instead of opening a bigger limit, it falls back to the Download card.
Try it: open a .zip, .tar or .tar.gz file in a repository's blob page.
The pre-push hook wanted a commit message starting with Try:, so there is an empty commit for that.
The web side is solid: the parser is bounded, names are escaped, the fallbacks work and the tests cover every case the issue lists. Fix these in one round.
First: the terminal door is missing. A person can list an archive on the page and not in the terminal. Add a TUI path where the workspace shows a file (see src/tui/screens/workspace.ts near line 734, which already shows mediaNote for audio and video). It should show the same entry list, or at least the count and size, through listArchive, gated the same way as the page. Add a test for it.
Second: there is no docs/features/archive-listing.md. The how-to is only a paragraph in docs/design/areas/render.md. Write the feature doc: what opens, the 2,000-entry and 50 MB limits, the 128 MB unpack cap, and the fallback sentences.
Third: the binary fallback card is worded badly (see the comment on src/web/pages/repo.ts). Last: the table sits inside a .card (see the comment on src/web/render/archive-view.ts).
A .zip, .tar or .tar.gz in a repository now shows its entry list on the page and a count-and-size note in the terminal. I changed everything @juniper asked for. The archive tests, the gate, tsc and the design lint pass, as do the two TUI test files I ran (render-media, tui-apps). I did not run the full suite.
- Terminal:
src/tui/ui/archive-note.tsis new andsrc/tui/screens/workspace.tscalls it where the workspace reads a binary file. It goes throughlistArchiveand is gated like the page: the kind comes from name and bytes, and the 50 MB cap applies.
- A readable archive gets a note like zip, 14 entries, 3.4 MB unpacked.
- The fallback sentences are the same as on the page.
- I chose count and size rather than the full list. The terminal's empty-state card shows one line of at most 44 characters, so a list has no room there.
- Feature doc:
docs/features/archive-listing.mdcovers what opens, the 2,000-entry, 50 MB and 128 MB limits, and the fallback sentences.docs/design/areas/render.mdlinks to it. - Fallback card:
blobBodynow takes the sentence ready-made. The lower-casing is gone, and the card title is "Archive" when a sentence is passed. The note readssize · sentence, so no em dash joins it to a full stop. The over-50 MB sentence is a singleoverSize()that the page and the terminal share. - Card removed: the table is no longer in a
.card..archive-viewis now a hook class on the<table>, andrender.mdsays so. - Locale: counts now go through a new
count()helper that groups digits with commas whatever the server locale. The 128 MB message and the footer use it. - Tests: the new tests in
test/render-archive.test.tscover:
- count() and overSize();
- a name with bidi and control characters;
- the table not sitting in a card;
- the terminal note for an archive, a non-archive, an archive over 50 MB and a damaged archive.
I disagree with nothing.
This pull now opens a .zip, .tar or .tar.gz file in a repository and shows its entry list. The previous check failed only because no commit message said why this goes ahead of the pipeline. I added an empty commit with a Why now: line, and the gate script now passes locally.
- Why now: an archive in a repository showed nothing, so people couldn't see what a shipped archive held without downloading it.
- Not run: I did not run
tscor the tests again, because the only change since the failed check is the empty commit. - To try: open a zip, tar or tar.gz file in a repository on the preview at https://lab--10325--forge-trevato-apps.open-platform.sh.
Every earlier ask is met: the terminal door (archive-note.ts, same gating and sentences as the page), the feature how-to in docs/features/archive-listing.md (README untouched), the table no longer in a card, one fallback sentence on the Download card, and locale-proof counts. Each is covered by a test that would fail without it. The reader is bounded: central directory and headers only, gunzip capped, 2,000 entries, and every failure becomes a sentence, not a 500. Names are escaped and control and bidi characters are replaced. No dependency was added and no CSS class was added.
Next, both non-blocking:
- Non-blocking: check the three-column table at 360px. Let the path column wrap (
overflow-wrap:anywhere) so long paths never push Size and Modified off screen. Use an existing token or utility, not a new class. - Non-blocking: zip names are decoded as UTF-8 only. Honour the zip UTF-8 flag (bit 11) and decode the rest as CP437, or say so in the feature doc.
A
.zip,.taror.tar.gz/.tgzfile in a repository now opens as a table of what is inside it, and nothing is unpacked. The type check,bun scripts/gate.ts,bun scripts/design-lint.tsand the render and blob tests pass.bun scripts/fast.tsnamed 1,459 of 2,391 test files, so I ran only the render, blob and repo-page tests myself and left the full run to the forge's check.What changed
kinds.ts: there is a newarchivekind. The extension names it and the bytes must confirm it:PK\x03\x04for zip,ustarat offset 257 for tar, and1f 8bfor.tgzor.tar.gz. A bare.gzstays binary. The inline limit is 50 MB.archive.ts: this reads the entry list and nothing else.- zip: it reads the central directory, including zip64 sizes. - tar: it reads the 512-byte headers, checks each checksum, and handles pax and GNU long names. - tar.gz: it uses
gunzipSyncwithmaxOutputLengthset to 128 MB. - It adds no dependency.archive-view.ts:- Table: path, size and modified date (UTC), sorted by path, with the folder part muted. - Footer: entry count and total unpacked size. - Escaping: names are escaped, and control and bidi characters in them are replaced.
- 2,000-entry cap: past 2,000 entries it lists the first 2,000, sorted, and says how many it left out. The count and total size still cover every entry. - Fallbacks: a truncated or damaged archive, an encrypted zip, a gzip bomb and an archive over 50 MB each fall back to the Download card with one sentence saying why. None of them returns a 500.
repo.ts: the binary card now shows that sentence in place of "not shown", andrenderers.tsregisters the new renderer.test/render-archive.test.tscovers everything the issue lists. It builds a zip, a tar and a tar.gz in the test, and also covers an encrypted zip and a bad tar checksum.render-kinds.test.tshas samples for the new extensions.Not done
.cardand.table, so there is nothing new to register fordesign-lint. A short note went intodocs/design/areas/render.md..tar.gzthat inflates past 128 MB is not listed. Instead of opening a bigger limit, it falls back to the Download card.Try it: open a
.zip,.taror.tar.gzfile in a repository's blob page.The pre-push hook wanted a commit message starting with
Try:, so there is an empty commit for that.Closes #10289