Skip to content

feat(ui): show recovery, refusals and notices after an export - #102

Merged
wow merged 11 commits into
mainfrom
feat/export-trust
Nov 6, 2026
Merged

wow merged 11 commits into
mainfrom
feat/export-trust

Conversation

@wow

@wow wow commented Nov 5, 2026 •

Copy link
Copy Markdown
Owner

Summary

This PR builds the trust surface around an export. A banner above the table shows what the start-up recovery found, and a pending change can be forgotten after a step that says what Forget does. Every IPC error class has a what-to-do line for each place it can be shown. Written rows say which loudness tags were left at the old level, when Serato's Auto Gain is out of date, and when rekordbox must analyse a FLAC written in place again.

  • Refusal copy (src/lib/ipc/refusalCopy.ts):
    • One switch over IpcErrorKind with a never check gives a what-to-do line per surface: row, confirm, banner.
    • The why stays the engine's message. Some messages already carry the engine's own advice: an in-place refusal ("Unlock it, or write a copy to a folder"), a rekordbox USB export, no space. That advice becomes the what-to-do, so it is said once.
    • The advice is looked for only after the fixed words the engine puts after the path, never inside the path. A name like Artist feat. Rihanna.wav or a folder Mr. Brightside therefore stays whole.
    • errorCopy and rowErrorCopy now use this, and their callers keep working.
    • Failed and refused rows read in three lines: what happened, why, what to do.
  • Recovery store (src/state/recovery.ts):
    • It keeps the whole RecoveryStatus.
    • A failed status read is retried three times (after 250, 500 and 1000 ms). After that the banner says the check could not be read.
    • forgetChange calls forget_pending. If the engine refuses, the store reads the status again: a row whose change ended meanwhile goes, and one still pending says why, without the "invalid argument:" prefix.
  • Recovery banner (src/features/recovery/: RecoveryBanner, ForgetConfirm, their copy):
    • Running: shown only after 400 ms, so a quick check does not flash.
    • Pending: each file's name (full path on hover), the reason, and Forget. The section is an explicit region.
    • Finished: a line that can be closed; it also says when a change was undone and its file is back as it was.
    • Failed: what happened, why, and what to do.
    • Skipped: nothing. The only reason the engine skips is that there is no home folder, so nothing can have been interrupted.
    • The banner sits in the page's flow and never covers the list. A long list scrolls inside about 92 px.
    • One live region is mounted from the start and only its text changes.
    • The table's keys wait while the Forget step is open.
  • Forget for a copy into a folder:
    • The step says what is left: no copy yet, an empty file holding its name, or the new copy.
    • Once forgotten, every kept file is listed by its path and what it is. The hidden temp file may be a whole output, and the Finder hides it.
    • Rust: PendingChange gains toFolder, so the UI knows a change was a copy into a folder.
    • Rust: sc_io::txn::forget now lists in kept the empty placeholder that a copy to an exFAT or FAT folder holds its name with. It is listed when it is still empty, or when its folder cannot be reached. Without that, a later export to that folder would skip the name as another file's. It is not deleted.
  • Notices (src/features/export/notices.ts):
    • Stale tags: staleLoudnessTags is named in plain words (Album ReplayGain, Track ReplayGain, R128 gain, RVA2, iTunNORM), and the notice says when the engine cut the list short.
    • Serato Auto Gain: shown only when the file has Serato Autotags and its gain changed. The engine lists GEOB:Serato Autotags / SERATO_AUTOGAIN / SERATO_AUTOTAGS among the stale tags exactly then, because the render reports none without a gain change. If that list was cut short, the Serato tags read when the file was added say the same.
    • FLAC: shown only for FLAC files written in place, on the row and once in the confirm step. rekordbox still knows such a file by its path, and its seek index no longer matches the new frames. A copy in another folder is new to rekordbox and is analysed when it is added. The notice says how to keep the exported grid: import the track from the rekordbox XML after re-analysing, then turn on Analysis Lock.
    • Footer: the last-export summary adds how many of that export's files keep loudness tags at the old level.

User-visible behaviour

  • Start-up banner:
    • "Finishing changes from the last session…" while the check runs. Export, Revert and the rekordbox XML stay disabled, as before.
    • "2 changes from the last session could not be finished", with each file, why, and Forget.
    • "2 changes from the last session were finished." as a line that can be closed.
    • Or "The changes from the last session could not be checked", then why, then "Restart SoundCheck to check again; …".
  • Forget: the step first says what Forget does.
    • SoundCheck stops trying to finish the change at start.
    • A change in place leaves the original or the new version, depending on how far it got.
    • A copy into a folder leaves no copy, an empty file holding its name, or the new copy.
    • Nothing is deleted. The line after it lists each kept file by its path. An empty file holding a copy's name says to remove it before exporting to that folder again.
  • Refusals and failures:
    • The row reads "Failed: the file was not changed" (or "Refused: …"), then the engine's why with "This file" for its own path, then what to do.
    • The cell shows the why and the what-to-do.
    • The confirm step and the banner use their own what-to-do lines ("press Export again", "try again").
  • Row details after a write:
    • "Album ReplayGain, RVA2 and iTunNORM were left as they are; players that use them will play this file at the old level."
    • For a file with Autotags whose gain changed: "Serato's Auto Gain for this file was measured at the old level: in Serato, analyse it again with Set Auto Gain on, or turn Auto Gain off."
    • For a FLAC written in place: "Analyse this FLAC again in rekordbox before playing it from a CDJ: rekordbox keeps an index of where each part of a FLAC it knows sits, and writing it in place moved them, so until then the track can skip. That analysis can put rekordbox's own beat grid in place of the one written here; to keep this grid, import the track from the rekordbox XML after it, then turn on Analysis Lock, which stops rekordbox analysing it again or changing its grid."
    • A FLAC copy gets no notice.
  • Footer: "Last export: … · 2 keep loudness tags at the old level", counting that export's files only.
  • Browser stand-in:
    • /?demo=recovery: the check runs for 2.5 s, then two changes are left pending (a copy to an unplugged stick and a change in place) and one is finished.
    • /?demo=recovery-failed and /?demo=recovery-finished show the other two states.
    • The export demo writes a FLAC, an AIFF with stale tags and a WAV with Serato Autotags.

Tests run

PASS  file-size                8s
PASS  test-files               0s
PASS  cargo-fmt                1s
PASS  cargo-clippy             9s
PASS  cargo-test             110s
PASS  fe-typecheck             3s
PASS  fe-lint                  5s
PASS  fe-test                 14s
RESULT PASS

New test files:

  • src/lib/ipc/refusalCopy.test.ts
  • src/features/export/notices.test.ts
  • src/features/export/ExportConfirm.notices.test.tsx
  • src/features/recovery/RecoveryBanner.test.tsx
  • src/state/recovery.test.ts
  • src/dev/mockNotices.test.tsx
  • src/dev/mockRecovery.test.ts

New Rust tests in crates/sc-io/src/txn/forget/tests.rs:

  • A copy to an unmounted stick lists the placeholder it may have left, and recovery reports to_folder.
  • An empty placeholder is listed and kept; a non-empty file of that name, or a copy past its rename, is not.

Extended tests: errors.test.ts, ExportDetail.test.tsx, cells.test.tsx, ExportConfirm.test.tsx, Footer.test.tsx, sc-engine gate tests.

  • Every error class: the refusal-copy test walks every IpcErrorKind through a Record over the type, so a new class fails to compile there. For each class it checks there is a non-empty what-to-do line on every surface that class reaches.
  • Paths with ". " and a capital: the advice tests use Artist feat. Rihanna.wav, Mr. Brightside.wav, Dr. Dre - Still D.R.E. Remix.aiff, a folder Mr. Brightside/Live. Then Encore.wav, and a volume /Volumes/Mr. DJ.
  • Break checks: each rule was broken on purpose, a test failed, and the code was restored. The rules covered:
    • a class without a case (tsc);
    • an empty what-to-do line, or a dropped surface;
    • the engine's advice or why ignored, or advice looked for inside the path;
    • tag labels shown raw;
    • the Autotags notice without a gain change, or Autotags repeated in the stale-tags line;
    • the FLAC notice before the write, on a copy, or counting copies in the confirm step;
    • the footer counting every exported row, or missing its line;
    • Cancel forgetting anyway, or a forgotten row kept;
    • a copy worded as a change in place, or kept files not listed by path;
    • the placeholder left out of kept (sc-io);
    • a refusal not reading the status again, or keeping the class name;
    • no retry after a failed read, or nothing shown after the last retry;
    • no live region from the start, or the pending section without its role;
    • the running line shown at once, or a skipped check that says something;
    • the stand-in ignoring the URL;
    • the table's keys moving while Forget asks.
  • The whole-flow stand-in tests have a 20 s timeout because they render the app many times on fake timers. Under a full parallel run they come close to the 5 s default.

Output change

  • No: the same input still yields the same numbers, grids and bytes. The Forget answer lists one more path in some cases, and PendingChange gains a field. No audio, grid, tag or sidecar byte changes.
  • Yes: goldens regenerated and reviewed; the commit carries Output-Change: yes; this ships in a minor release.

Docs and dependencies

  • docs/PRODUCT.md / docs/ARCHITECTURE.md updated if behaviour or crate boundaries changed. (PRODUCT.md items 5 and 6; the README lists the recovery demos.)
  • New dependencies, with licence: none.

wow added 11 commits November 5, 2026 23:32
WIP: one switch over every IPC error class gives a what-to-do line per surface (row, confirm step, banner) after the engine's why; errorCopy and rowErrorCopy now use it. The recovery store keeps the whole status and can forget a pending change.
Signed-off-by: Ilham Deneri <ilhamdeneri@gmail.com>
Stale loudness tags, Serato Autotags and FLAC re-analysis notices on written rows, the confirm step and the footer; the start-up recovery banner with a Forget confirm step.

Signed-off-by: Ilham Deneri <ilhamdeneri@gmail.com>
The Forget step's state lives in the recovery store, so the table's keys wait while it is open. Tests cover running, pending with Forget, a refused Forget, finished, undone, failed and skipped.

Signed-off-by: Ilham Deneri <ilhamdeneri@gmail.com>
?demo=recovery runs the check, then leaves two changes pending with Forget; ?demo=recovery-failed and ?demo=recovery-finished show the others. The export demo now writes a FLAC, an AIFF with stale loudness tags and a WAV with Serato Autotags.

Signed-off-by: Ilham Deneri <ilhamdeneri@gmail.com>
PRODUCT.md items 5 and 6 say what the start-up recovery banner shows and what Forget does, how refusals read in three lines, and the stale loudness tag, Serato Auto Gain and rekordbox FLAC notices; the README lists the recovery demos.

Signed-off-by: Ilham Deneri <ilhamdeneri@gmail.com>
The table's keys wait while Forget asks; the stand-in recovery is picked by the URL and forgets as the shell does. The whole-flow stand-in tests get room past the 5 s default.

Signed-off-by: Ilham Deneri <ilhamdeneri@gmail.com>
On exFAT and FAT a copy into a folder holds its name with an empty file before the rename. A change forgotten before that rename now lists the placeholder in kept when it is still empty, or when its folder cannot be reached, so the user can find and remove it; a later export would otherwise skip that name as another file. Nothing is deleted.

Signed-off-by: Ilham Deneri <ilhamdeneri@gmail.com>
A file or folder name with '. ' and a capital (Artist feat. Rihanna.wav, Mr. Brightside) split the refusal there. The advice is now looked for only after the words the engine puts after the path: the in-place refusal's reason, the USB message's fixed sentence, the free-space numbers.

Signed-off-by: Ilham Deneri <ilhamdeneri@gmail.com>
A pending change now says whether it was a copy into a folder (PendingChange.toFolder), so the Forget step says what is left in that case: no copy yet, an empty file holding its name, or the new copy. Once forgotten, every kept file is listed by its path and what it is; the hidden temp file may be a whole output and the Finder hides it.

A refused Forget reads the status again and drops a row whose change ended meanwhile; one still pending says why without the class name. A failed status read is retried three times (250, 500, 1000 ms) before the banner says the check could not be read. One live region is mounted from the start and only its text changes; the pending list is an explicit region.

Signed-off-by: Ilham Deneri <ilhamdeneri@gmail.com>
The footer counts only the files of the last export, not rows an earlier batch left with old loudness tags.

The rekordbox FLAC notice now shows only for FLAC files written in place: rekordbox still knows such a file by its path, and its seek index no longer matches the new frames. A copy in another folder is new to rekordbox and is analysed when it is added. The notice adds what keeps the exported grid: import the track from the rekordbox XML after the analysis, then turn on Analysis Lock.

Signed-off-by: Ilham Deneri <ilhamdeneri@gmail.com>
What Forget keeps for a copy into a folder and how it lists it, the status read retried before the banner says it failed, and the rekordbox FLAC notice for files written in place only.

Signed-off-by: Ilham Deneri <ilhamdeneri@gmail.com>
@wow
wow merged commit 19c2e64 into main Nov 6, 2026
8 checks passed
@wow
wow deleted the feat/export-trust branch November 6, 2026 05:00
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