An earlier result should survive a same-name download, but the naming rule depends on the job. The browser chooses a fresh name around existing files and active destinations. The collector had to repair different addresses collapsing to one filename. Adding a new result and finding the same source again need different rules.

CSV export in the separate Rust edition exposed another case: taking a path reserved by a different account. Its repair record and the source inspected for this article are covered later. The collector and public-module tests below are historical results from the earlier public article, not tests rerun during this writing work.

First make a name the filesystem can accept

SafeDownloadName replaces path separators, forbidden Windows characters and control characters, removes trailing dots and spaces, and falls back to a default if the result is empty. Reserved device names such as CON and NUL receive a prefix. A server-provided string is not assumed to be a valid local destination.

Overlong names are shortened while preserving a reasonably short extension, with care not to leave half a surrogate pair before that extension. This is not a universal library for every Unicode normalization and operating-system rule. It addresses concrete failure modes in the product environment, and the tests state that narrower purpose rather than treating sanitized text as an unlimited filesystem guarantee.

Avoid both disk entries and destinations already in progress

A native download chooses another numbered name while its candidate path either exists on disk or appears in activeDownloadPaths. The number is inserted after the stem and before the extension. The second report therefore has another destination instead of silently replacing the first.

A disk-existence check alone cannot distinguish two downloads starting together, because neither may have created its file yet. The application reserves the selected destination and releases it on the terminal event. This coordinates active transfers inside the application. The reservation set is not an operating-system lock shared with every external program, and I do not present the existence-check sequence as such a guarantee.

Buffered saves add exclusive creation at the write boundary

For content already in memory, such as a generated CSV, saveDownloadBuffer opens a chosen destination in wx mode so only a new file can be created. If another writer has created it first, EEXIST leads to the next numbered candidate. The interval between checking existence and creating the file is handled at the creation boundary.

This path also avoids destinations reserved by native downloads. Its unit fixture keeps an existing owned file and a reserved next candidate while invoking two saves concurrently, then checks distinct destinations and preserved contents. I explain the native and buffered paths separately because they do not gain identical collision guarantees merely by displaying downloads in the same list.

A person-chosen destination has another context

A private native download uses a save dialog so the person chooses the destination. Cancelling stops the transfer without adding a record. Describing that route as identical to automatic numbering would misrepresent the behavior. Its message also needs to explain that a chosen file remains on disk.

A collision with an active reserved destination cancels the new item. That set does not determine every policy for choosing an existing path in a save dialog. Describe automatic numbering, explicit destination choice and competition with external programs under their own conditions.

Check extension preservation and content preservation separately

Name tests cover long extensions, reserved devices, forbidden characters, split-surrogate avoidance and dot-only names. The buffer test checks preservation of an existing file and both concurrent new contents. The file-audit suite also designs sequential same-name downloads and six simultaneous ones with path and hash comparisons.

The initial article read those browser sources and tests without newly launching the product. The shared receipt example rejects problematic names rather than repairing them, a smaller independent contract. It does not reproduce the operational naming policy. I keep that source review separate from the collector repair below, with evidence for a valid name, preservation of earlier results and the correctness of actual content.

Two collection addresses became one local filename

In the follow-up development task I called the collector’s actual save function inside an owned synthetic temporary folder. Converting extensionless /guide into an HTML filename produced guide.html, which was also the destination for /guide.html. Saving two different documents in sequence returned the same path and replaced the first document’s bytes with the second. This directly reproduced a counterexample in current code; it did not establish damage to historical customer captures.

Making a name safe can create the same ambiguity. Different addresses can collapse after forbidden-character replacement, case differences or truncation of long names. A readable local name does not by itself preserve the distinctions in the source address. That was the assumption I needed to check at the actual storage boundary.

An existing green test record did not answer this counterexample. The twenty Python and eleven Node tests passed before the repair, but they did not distinguish these two saves. After reproduction I changed the storage identity rather than shrinking collection scope or discarding an inconvenient address.

A new-download number and a source identity serve different purposes

The browser needs a separate destination for a new same-name result, so numbering is useful. The collector needs to find a stored address again to read links and continue. A new number on every attempt could preserve files while obscuring which source each one belongs to. That path uses a deterministic source identity.

The repaired naming function adds the SHA-256 of the entire URL to a readable basename. Scheme, path and query differences participate in that string identity; overlong names continue to use the existing shorter hash path. I did not bulk-move or delete earlier files. The destination selected by a new run and the existence of older output are handled separately.

This digest describes a URL string, not the bytes of the document received from it. Content at the same address can change while the address identity remains stable, and two different addresses can supply equal bytes. The byte digest in the shared receipt example therefore answers a different question. Expected-content matching, source authentication and freshness still require their own evidence.

Do not resume an old file on its name alone

Distinct new destinations would not resolve the entire issue if the reader still trusted an old guide.html unconditionally. The earlier page reader returned a file with that computed name as HTML for the current address, without establishing whether it belonged to another URL. Saving and reading needed compatible identity rules.

The reader checks the new destination first. An older-format HTML file becomes a candidate only when the collection manifest associates it with the same current URL. Its path must remain inside pages and must not also be claimed by another URL. Unambiguous old HTML belonging to the requested address remains reusable.

When correspondence is missing or multiple addresses claim the same path, the old file is retained and the existing HTTP-fetch path is used. Malformed entries and paths outside the permitted page area are also excluded as reuse evidence. This cannot recreate historical bytes already overwritten. A manifest supplies address-to-file correspondence, not authentication of the content or proof that it is current.

Check each document’s bytes rather than the file count

Fifteen new tests execute the actual storage, cache and report functions with synthetic folders. They cover extension adjustment, case, forbidden characters, truncation and queries, then verify the distinct saved page and asset bytes. They also check updates to the same URL, file-versus-directory paths and concurrent saves. Merely observing two paths was insufficient; each file was read back against its expected bytes.

Legacy-cache tests separate valid unambiguous reuse from fetching again when records are absent, ambiguous or malformed. The actual report and ZIP functions are also called to confirm that both distinct documents and retained old-file bytes survive in the archive. The development result was thirty-five Python tests and eleven existing Node tests, forty-six in total. Those native function tests belong to the preceding development task and were not rerun during this writing task.

The existing collection, sitemap, asset, robots, redirect and report methods, along with parser, options and record structures, were compared and retained. Bytes and modification times of twenty-six protected files were also preserved. This was neither a new live-site crawl nor a rendered-browser test or exhaustive audit of customer captures. Running services and profiles were not replaced.

Restoring source also exposed a runtime dependency

Before-and-after source archives were extracted and checked against hashes for twenty-eight and twenty-nine files, fifty-seven in total. The first source-only restore passed Python checks, but Node policy tests stopped because the existing robots-parser dependency was absent. I preserved the failed logs, extracted folder and already-created archive instead of declaring either a product defect or a completed restore.

The dependency from the existing installation was captured in a separate hashed archive and extracted alongside source in new restore folders. The restored earlier source then passed thirty-one tests and the repaired source forty-six. This reused the existing runtime rather than installing new packages or verifying a complete first installation on another PC. The original installation was retained, and the result was not presented as recovery of overwritten customer originals.

The earlier revision reused the existing file-selection and byte-receipt example. It downloaded the public ZIP, checked bytes and digest, and ran thirty-six synthetic tests after extraction, with a separate saved-record selector check. The module compares bytes supplied in memory; it writes no files and chooses no collision-free destination. It is not automatically installed in the collector. It is an example for examining source identity, output preservation and byte comparison separately.

Avoid another account’s reserved path even before its file exists

The later counterexample involves two accounts sharing a download folder. The first CSV already exists. Another account has reserved the next numbered destination for an active download, but its file is absent. An export that checks only disk entries and its own account’s list can mistake that destination for an available one.

The retained log shows this regression failing. The repaired test keeps the earlier file and adds another account’s active destination without creating its file. It checks that export chooses the following number and leaves the reserved destination absent. The names below illustrate that relationship with synthetic data.

This is why examining only an empty location is insufficient here. An application may already have assigned a destination even though no bytes exist there yet. Reproducing both disk state and active reservation state reveals the missing scope.

Explore the implementation

Synthetic destination names · not executable code

Synthetic names in one shared folder:
report.csv      -> already exists
report (1).csv  -> reserved by another account; file absent
report (2).csv  -> candidate for this export

Bind content access to the account and check collisions across shared storage

The Rust export inspected for this article read CSV contents only from artifacts registered to the current project and account. To choose a name, it checked the displayed download list and records across all profiles, comparing candidate paths in lowercase with active, cancelable destinations. Content lookup stayed within the permitted account; collision checks covered the active records sharing the folder.

A reserved candidate is skipped, and the file writer requests new-file creation. An already-existing destination leads to the next numbered candidate. Ordinary export and the private save-dialog route remain separate, so this numbering does not define the replacement policy of every save dialog. Nor is the reservation check a global coordinator for all external writers and path aliases.

I compared the retained failing log with the same regression passing after the repair. The development record’s 474 passing checks and build concern that Rust revision. I read the logs and test definitions without running product tests or downloads. Independent code review and live-screen review remained unconfirmed in that record, and the full feature migration was still in progress.

To apply this pattern, use accounts and files in an owned synthetic fixture. Add another account’s active destination before its file exists, while keeping content lookup limited to the current account. Check that export avoids the reserved name and preserves both the earlier file and the uncreated reserved location.