Skip to content
Blog
LaunchesSeptember 29, 2026

The invisible web zip mistake

A web build for itch must carry index.html at the root of the archive. Zipping the folder instead of its contents breaks the browser player, with no useful message. It is the most banal mistake of the format, it always happens by hand, and I wrote a script to make it impossible.

A banal mistake, and a silent one

To ship a web game on itch, you upload a zip archive. The rule is simple: index.html must sit at the root of the archive. But the natural gesture, right-clicking the build folder, produces an archive whose root is a folder. The itch page then breaks cold, with nothing useful to say. It is the most banal mistake of the format, and it has two properties that make it dangerous: it is invisible at the moment you make it, and it always happens by hand, in the least supervised gesture of the whole chain, the one after the tests.

While preparing Sillon's 0.1.0 demo, I decided this mistake should no longer be possible, rather than hoping to stop making it.

The script that refuses to hand back control

The packager zips the folder's contents, never the folder. Then, and this is the heart of it, it reopens the archive it just wrote and requires index.html at the root, otherwise it refuses to hand back control. It does not trust its own gesture: it checks the result, the way the browser would.

And that check was proven against the real fault, not only against the good archive: I deliberately built the faulty zip, the one that wraps a folder, and verified it was indeed rejected. A guardrail you have never seen turn red protects you from nothing; you do not even know it runs.

A check is only worth something once it has been proven against the real failure. Manufacture the fault it claims to catch and watch it refuse. A check verified only on the healthy case is a decoration.

What the packager also demands

Two more refusals went into the script, each paid for by a near miss.

It filters out the test harness files: my web harness lives in the same served folder as the player's page, and a test file shipped to the public would be an open door into the suites. Sorting what ships from what stays home is no longer a memory, it is a line of code.

It demands a version number, read from the project configuration. Without one, the first hotfix would be indistinguishable from the original and no player report could be attached to a state of the code. The day I wrote it, the version key did not exist yet in the project: the script stopped and said so, rather than inventing a number. It is what forced me to set 0.1.0.

The screenshot that saw what the tests did not

The same delivery fixed a defect of an entirely different kind, found while preparing the itch page. On the catalog's detail screen, the price was biting into the text: placed at a fixed height, tuned to the longest card of the day it was written. The brake's card runs five lines; the coin and its amount landed in the middle of the fourth, on the screen the player opens for every purchase. Weeks of existence, no assertion. It took a screenshot captured to illustrate the store page.

The position is now measured on the rendered block, and the assertion guarding it opens all eight cards and reads the nodes placed on screen. Rebuilding the placement formula inside the assertion would be comparing it to itself.

The freeze gate

The script's last role is less visible: it marks the end of the right to modify. Any code change after the export instantly expires the page captures and the assertions played against the shipped build. The packager is not an end-of-chain utility, it is the freeze gate: whatever comes after it is called the next version.

The dry run reported its numbers: 38.6 MB zipped for Windows, 12.3 MB for the web, 9 files. Numbers that none of my memories of hand-made zips could have produced.

What I take away

Delivery mistakes live exactly where the tests stop: in the manual gestures after validation. The answer is not more vigilance, it is a tool that performs the gesture, verifies its own result, and has proven once that it knows how to say no.