Skip to content

Release v0.10.1 - #118

Merged
otsobide merged 8 commits into
mainfrom
develop
Aug 28, 2026
Merged

Release v0.10.1#118
otsobide merged 8 commits into
mainfrom
develop

Conversation

@otsobide

Copy link
Copy Markdown
Owner

Six commits since v0.10.0, all of them cleanup of what that release left behind.

What ships

The naming machinery v0.10.0 made unreachable is gone. When extraction
stopped renaming entries to fit the host, it left NamePlan, the three
extract_*_planned backend variants, Substitutions,
ExtractOptions::with_replacements, NameRules::rewrite, six of NameError's
seven variants, the CLI's adjustments, and the desktop's whole naming dialog
with the unwritable_names command behind it.

Two of those removals were more than housekeeping:

  • Tar went from two write paths to one. unpack_in derives the destination
    from the entry's own name and so could not write a renamed entry — and it is
    also the traversal guard, the canonicalizing containment check that stops a
    write following a symlink already in the output. The renamed branch had to
    repeat that check by hand. Both are gone and every entry goes through
    unpack_in again.
  • The CLI's refusal message was lying, and v0.10.0 shipped it that way. It
    told users a replacement would unblock the extraction and sent them to the
    desktop dialog to give one. There is no replacement, and that dialog collected
    answers that reached nothing. It now says what is true: nothing was extracted,
    no option would change that, extract on a system that can hold the names.

Version

0.10.1, patch. Nothing gains or loses a capability — see #117 for the
reasoning, including the one judgement call: the desktop visibly loses its
naming dialog, which never worked.

All twelve version locations agree. Lockfiles were regenerated by cargo update --workspace and npm install --package-lock-only rather than hand-edited, and
cargo build --locked passes.

Verified

Every commit was run locally before the next was started — cargo is on the
development machine now, so this did not wait on CI.

  • make test green: 595 Rust tests, 88 Vitest cases.
  • The root workspace goes 506 → 494 by exactly the twelve tests that covered
    machinery which no longer exists; the desktop naming suite 18 → 4.
  • The app was launched in dev mode and driven: it compressed a real file to a
    valid zip, CRC checked, one entry named exactly as the source.

Pre-release checklist

  • Twelve version locations agree on 0.10.1
  • cargo build --locked passes
  • develop green, including test (rust, windows) and test (rust, macos)
  • Tag v0.10.1 on the merge commit — release.yml refuses a tag whose
    commit is not on main and checks it against apps/cli/Cargo.toml and
    tauri.conf.json

`NamePlan` has been the identity since extraction stopped renaming entries, so
every `extract_*_planned` was its plain counterpart with an extra argument and a
lookup that always answered `None`. The type, the three variants and the
wrappers that called them are gone; `extract_zip`, `extract_7z` and
`extract_tar` are now the functions themselves.

The one that mattered is tar. It carried **two** write paths, because
`unpack_in` derives the destination from the entry's own name and so cannot
write a renamed entry at all — and `unpack_in` is also the traversal guard tar
has always used, the canonicalizing containment check that stops a write from
following a symlink already sitting in the output directory. The renamed branch
therefore had to repeat that check by hand. With nothing renamed, that branch
and its hand-rolled containment check are both gone, and every entry goes
through `unpack_in` again.

No behaviour changes and no test changes: 506 passing before, 506 passing after.
That is the point of doing this separately from anything that alters what the
code does.

The explicit `..` refusal before `unpack_in` stays. It existed because the
renamed branch had no other guard, and it is now belt and braces — but it is
also what names the offending entry, which `unpack_in`'s own `Ok(false)` does
not.
`adjustments`, `Adjustment` and `Outcome::Extracted { adjusted }` are gone. They
reported the names extraction had rewritten to fit the host, and extraction
stopped rewriting names in v0.10.0, so the list was provably empty at the only
place it was built.

**More seriously, the refusal message was lying, and shipped that way.** Two of
its sentences described the old scheme:

    Going ahead needs a replacement for '?' (1 entry), and this command cannot
    ask for them mid-run without becoming interactive: the Collapse desktop app
    asks once per character, checks the answer is writable too, and extracts
    with it.
    The names above with nothing to replace are adjusted for you once it can go
    ahead.

Neither is true. There is no replacement to give, and the desktop dialog it
points at collects answers that reach nothing. A message that sends someone to a
feature that no longer works is worse than one that says less, so it now says
what is actually the case: nothing was extracted, no option would change that,
extract on a system that can hold the names.

`explain` had the same problem in two lines — a trailing run "would be dropped"
and a device name "would be written under an adjusted name". Both described
adjustments that no longer happen.

The characters are still listed with their counts, and deliberately: there is no
answer to give, but one character across forty entries is an archive that is
awkward here, while forty characters is one that does not belong on this machine
at all, and that is the user's call.

The two tests that pinned the old sentences are rewritten rather than deleted —
one of them now asserts the dead pointer is *absent*, so it cannot come back.
506 passing, unchanged.
The whole conversation is gone: `unwritable_names`, the `names` module that
shaped its answer for the webview, the sheet in `App.vue` with its fields,
validation and adjustment notes, `src/names.js`, and the `replacements`
argument `extract_archive` took.

It had stopped being a conversation. Extraction refuses a name it cannot write
rather than asking what to call it, so the dialog collected answers that reached
nothing and then reported a refusal the user could not act on — the worst shape
a question can have.

`Extraction` goes with it. A naming refusal had a variant of its own so the
sheet could stay open on it while everything else went to the error banner; with
no sheet there is one destination for both, so `extract_archive` returns
`Result<Vec<String>, String>` like every other command.

**Four places had to move together**, which is what `tests/ipc.rs` exists to
police: the `generate_handler!` list, the command itself, the `invoke` call in
`App.vue`, and the stub switch in `tests/App.test.js`. Its `BASELINE` and
signature table are updated too, so the five-command surface is now four and
nothing can quietly become untyped.

Tests: `tests/names.rs` is rewritten rather than deleted, down from eighteen
cases to four, because one guarantee in it survives and is worth more now that
it is the only one — a refusal must reach the user **with its reason attached**.
A NUL prints as nothing, so an entry named without its fault looks perfectly
fine, and a user told "extraction failed" would think the archive is broken when
it is merely foreign. Ten dialog cases went from `App.test.js`, and the two that
remain assert a refusal and an ordinary failure both land in the banner.

101 Rust tests and 43 Vitest cases pass.
With both front ends off it, the rest goes: `Substitutions` and its
`FromIterator`, `ExtractOptions::with_replacements` and `replacements`,
`NameRules::rewrite`, `rewrite_entry`, `check_replacements` and
`check_replacement`, `DEVICE_SUFFIX`, `NameProblem::replaceable`,
`NameRules::offending_characters`, and six of `NameError`'s seven variants with
the `in_entry` helper that re-pointed them.

`NameError` is one variant now, `Unwritable`, which is the only one anything
constructed. The other six described a negotiation that no longer happens: no
replacement to be missing, unwritable or separator-bearing, no rewrite to make
two entries collide, no adjustment to empty a component, no answer key to be
more than one character.

`NameReport` stays, and so does its `characters` half. It is no longer a
questionnaire — nothing can be answered — but it is what the CLI groups its
refusal by, and one character across forty entries is a different problem from
forty characters.

Tests: eleven cases in `apps/core/tests/names.rs` exercised the removed
machinery and go with it. `an_entry_splits_the_same_way_on_every_host` goes too,
for a different reason — it used `rewrite_entry` only as a way to observe where
a name splits, and that guarantee is already held by
`the_report_sees_a_colon_in_the_first_component_too` and
`only_windows_refuses_a_backslash_inside_a_component`, both of which survive.
`the_report_separates_the_questions_from_the_stated_adjustments` is rewritten
around what `characters` still means.

In `security.rs`, `a_replacement_cannot_carry_an_entry_out_of_the_output_
directory` is replaced by a comment in its place rather than removed silently.
It proved a hostile answer like `../../escape` could not move an entry out of
the output directory. That hole is now closed by construction instead of by a
check — there is no way to supply a replacement, so the code that would fail
the test does not compile — and the guarantee is still one this crate makes,
so it is worth someone finding the reasoning where the test used to be.

494 passing across the root workspace, down from 506 by exactly the twelve that
tested machinery that no longer exists.
`architecture.md` and `desktop.md` described `Substitutions`, `NamePlan`, the
`extract_*_planned` variants and the desktop's `unwritable_names` command as
inert surface awaiting removal. They have been removed, so the documents now
describe what is there.

Two facts worth keeping rather than just deleting: `NameError` is down to one
variant because the other six described a negotiation that no longer happens,
and tar went from two write paths to one — the renamed branch had to repeat by
hand the containment check `unpack_in` performs, and both went together.

The desktop's command surface is four, not five.
…machinery

chore: remove the naming machinery v0.10.0 left behind
All eight version strings and the four lockfiles. The release guard checks two
of them (issue #77), so the rest are by hand and by eye — though this time the
lockfiles were regenerated by `cargo update --workspace` and `npm install
--package-lock-only` rather than edited, since both toolchains are on this
machine now, and `cargo build --locked` was run afterwards because that is what
`make cli/release` uses and a drifted lockfile fails there first.

Patch rather than minor. Nothing since v0.10.0 gains or loses a capability: the
naming machinery that came out was already unreachable, and what shipped in its
place was a CLI refusal message that promised a replacement nobody could give
and pointed at a desktop dialog that collected answers reaching nothing. That
message now says what is true.

The one thing a user could notice is that the desktop's naming dialog is gone.
It never worked — it asked a question, took an answer and refused anyway — so
its removal takes away nothing that functioned, which is why this is a patch and
not a minor.
@otsobide
otsobide merged commit fc6e886 into main Aug 28, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant