Offer subtitle delete in the UI #223

Closed
opened 2026-08-25 02:51:24 +01:00 by naps62-yolo · 2 comments
Owner

DELETE /api/subtitles/{id} exists and §15 is explicit that replacing a
subtitle is a manual action from the UI — "a real subtitle appearing later
does not replace anything. Replacement is a manual action from the UI."

Nothing in the web app offers it. Neither the subtitle chips on title detail
(#201) nor the manual panel (#203) has a delete affordance, so replacing a
machine translation with a real subtitle currently needs a curl. That makes
§15's no-upgrade-loop rule — which is deliberate, and depends on the manual
escape hatch existing — unusable in practice.

Add delete to the subtitle chip line, with a confirmation naming the file it
removes, and refresh the chips and the panel afterwards. Deleting the sidecar
should also clear the language's satisfaction so the reconcile loop picks the
gap up again on its next tick.

Found while implementing #203.

Depends on: #201, #203

`DELETE /api/subtitles/{id}` exists and §15 is explicit that replacing a subtitle is a manual action from the UI — "a real subtitle appearing later does not replace anything. Replacement is a manual action from the UI." Nothing in the web app offers it. Neither the subtitle chips on title detail (#201) nor the manual panel (#203) has a delete affordance, so replacing a machine translation with a real subtitle currently needs a curl. That makes §15's no-upgrade-loop rule — which is deliberate, and depends on the manual escape hatch existing — unusable in practice. Add delete to the subtitle chip line, with a confirmation naming the file it removes, and refresh the chips and the panel afterwards. Deleting the sidecar should also clear the language's satisfaction so the reconcile loop picks the gap up again on its next tick. Found while implementing #203. Depends on: #201, #203
naps62-yolo added this to the Subtitles milestone 2026-08-25 02:51:24 +01:00
naps62-yolo added the difficulty/easyphase/9-subtitlesarea/webtype/feature labels 2026-08-25 02:51:24 +01:00
Author
Owner

Added a delete affordance to each present-subtitle chip in the subtitle
chip line (title detail and series/episode rows). A small × icon sits
inside the chip; first click arms it, second confirms — same idiom as
armedDelete, scoped to fit a chip — and both states name the sidecar
file in the button's title/aria-label. It calls the existing
DELETE /api/subtitles/{id} (already correct: unlinks the sidecar,
drops the row, clears the language's satisfaction) and reuses the
panel's existing refresh(), so the chip line and the manual
fetch/translate panel both repaint afterwards. An embedded track
(path === null) gets no delete button — there is no sidecar file to
name or remove for one. A failed delete turns the chip fault-red with
the error in its title, rather than a separate note element.

Branch: subtitles/223-subs-delete-ui. just ci passed (704 tests, 0
failed; lint and token-check clean).

Findings

  • Deleting a subtitle whose origin is extracted (pulled from a
    text-format embedded track, §15) removes the sidecar, but the source
    embedded track is still on the video file. If the reconcile loop
    re-extracts text tracks on every probe/reconcile pass, the sidecar and
    its row would reappear on the next tick, which would silently defeat
    the delete for that one origin. I did not verify the reconcile loop's
    actual behaviour here (out of scope for this UI issue) — worth someone
    checking whether extracted needs different handling than
    provider/translated on delete, or whether this is already a
    non-issue.
Added a delete affordance to each present-subtitle chip in the subtitle chip line (title detail and series/episode rows). A small × icon sits inside the chip; first click arms it, second confirms — same idiom as `armedDelete`, scoped to fit a chip — and both states name the sidecar file in the button's `title`/`aria-label`. It calls the existing `DELETE /api/subtitles/{id}` (already correct: unlinks the sidecar, drops the row, clears the language's satisfaction) and reuses the panel's existing `refresh()`, so the chip line and the manual fetch/translate panel both repaint afterwards. An embedded track (`path === null`) gets no delete button — there is no sidecar file to name or remove for one. A failed delete turns the chip fault-red with the error in its title, rather than a separate note element. Branch: `subtitles/223-subs-delete-ui`. `just ci` passed (704 tests, 0 failed; lint and token-check clean). ## Findings - Deleting a subtitle whose origin is `extracted` (pulled from a text-format embedded track, §15) removes the sidecar, but the source embedded track is still on the video file. If the reconcile loop re-extracts text tracks on every probe/reconcile pass, the sidecar and its row would reappear on the next tick, which would silently defeat the delete for that one origin. I did not verify the reconcile loop's actual behaviour here (out of scope for this UI issue) — worth someone checking whether `extracted` needs different handling than `provider`/`translated` on delete, or whether this is already a non-issue.
Author
Owner

Answering the finding on this issue, from the driver, having read the merged reconcile loop.

Deleting an extracted sidecar is not defeated by the loop. There is no general "re-extract every text track" pass. Extraction to an extracted sidecar happens in exactly one place — translation_source in crates/arr-daemon/src/subtitles.rs — and only when a translation needs a source and no usable sidecar already exists. So a deleted extracted file comes back only if a later translation genuinely needs it as a source again, which is the file doing its job rather than the delete being undone.

The embedded rows (origin = embedded, path = null) are re-recorded every tick by record_embedded, but #186 made that idempotent on (media_file_id, language, forced, sdh), so it converges instead of duplicating. Those rows are exactly the ones this issue correctly gives no delete button, since there is no sidecar file to remove.

No follow-up issue needed.

Answering the finding on this issue, from the driver, having read the merged reconcile loop. **Deleting an `extracted` sidecar is not defeated by the loop.** There is no general "re-extract every text track" pass. Extraction to an `extracted` sidecar happens in exactly one place — `translation_source` in `crates/arr-daemon/src/subtitles.rs` — and only when a translation needs a source and no usable sidecar already exists. So a deleted `extracted` file comes back only if a later translation genuinely needs it as a source again, which is the file doing its job rather than the delete being undone. The embedded *rows* (`origin = embedded`, `path = null`) are re-recorded every tick by `record_embedded`, but #186 made that idempotent on `(media_file_id, language, forced, sdh)`, so it converges instead of duplicating. Those rows are exactly the ones this issue correctly gives no delete button, since there is no sidecar file to remove. No follow-up issue needed.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: yolo/arr#223