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

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

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

opened this

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 new archive kind. The extension names it and the bytes must confirm it: PK\x03\x04 for zip, ustar at offset 257 for tar, and 1f 8b for .tgz or .tar.gz. A bare .gz stays 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", and renderers.ts registers the new renderer.
  • Tests: test/render-archive.test.ts covers 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.ts has samples for the new extensions.

Not done

  • Styling: I added no CSS class. The table reuses .card and .table, so there is nothing new to register for design-lint. A short note went into docs/design/areas/render.md.
  • Zip names: they are decoded as UTF-8, not CP437.
  • Over 128 MB: a .tar.gz that 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.

Closes #10289

commented

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 new archive kind. The extension names it and the bytes must confirm it: PK\x03\x04 for zip, ustar at offset 257 for tar, and 1f 8b for .tgz or .tar.gz. A bare .gz stays 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", and renderers.ts registers the new renderer.
  • Tests: test/render-archive.test.ts covers 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.ts has samples for the new extensions.

Not done

  • Styling: I added no CSS class. The table reuses .card and .table, so there is nothing new to register for design-lint. A short note went into docs/design/areas/render.md.
  • Zip names: they are decoded as UTF-8, not CP437.
  • Over 128 MB: a .tar.gz that 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.

Changes requested — before the latest push

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).

commented

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.ts is new and src/tui/screens/workspace.ts calls it where the workspace reads a binary file. It goes through listArchive and 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.md covers what opens, the 2,000-entry, 50 MB and 128 MB limits, and the fallback sentences. docs/design/areas/render.md links to it.
  • Fallback card: blobBody now takes the sentence ready-made. The lower-casing is gone, and the card title is "Archive" when a sentence is passed. The note reads size · sentence, so no em dash joins it to a full stop. The over-50 MB sentence is a single overSize() that the page and the terminal share.
  • Card removed: the table is no longer in a .card. .archive-view is now a hook class on the <table>, and render.md says 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.ts cover:

- 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.

commented

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 tsc or 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.
Approved

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:

  1. 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.
  2. 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.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs