Conversation
Closes nashsu#707. A queue full of stopped sources currently has no way out. nashsu#557 established that stopped tasks are retained on purpose so they can be restarted, and 0fd9257 implemented that by turning cancel from "drop the task" into "keep it as cancelled". That closed the recoverability gap but left nothing for tasks that will never succeed: a corrupt PDF, an unsupported format, or a source the user has since deleted. A successful task leaves the queue as soon as it commits, and saveQueue filters any stray done entry, so nothing else accumulates. Failed and cancelled entries stay in the Activity Panel indefinitely. Two removal paths already exist and neither fits. discardTasksForSources is reachable only from scheduled-import reconciliation, never from the UI. clearCompletedTasks drops every non-pending/processing task in one call, has no production caller, and carries neither a selection nor a live-run guard, so exposing it as-is would give up the granularity nashsu#707 asks for. - removeTasks(taskIds) drops failed/cancelled tasks for the active project in one queue write; removeTask(taskId) wraps it for the single-row case. Pending and processing tasks are skipped, not force-stopped. - Skip tasks with a live entry in activeRuns. A "cancelled" status only records that cancellation was requested: the run holds its worker slot until it observes the AbortSignal, and a commit past the interruptible point still finishes. Removing the entry then would free the sourcePath that enqueueBatch dedupes against, so re-importing the same source would mint a fresh task ID that the !activeRuns.has(task.id) guard in processNext cannot match. The two runs cannot interleave their writes, since autoIngest holds a per-source project lock, but the newcomer would take a worker slot and block on that lock; because commitCoordinator chains reservations in start order, a small worker pool can then look stalled while nothing is able to progress. - hasActiveRun(taskId) lets the panel hide the remove affordance for that beat instead of offering a click the core would refuse, which also keeps the bulk confirmation count honest. - retainQueuedSelection() reconciles the selection against the queue after a removal rather than clearing it, so a row that was not removed stays visible and selected. It returns the original Set when nothing changed. - Activity Panel: a trash button on every removable row, and a Remove action in the selection bar next to Restart and Cancel. The bulk action confirms first, because it cannot be undone. - Add common.remove (en/zh/it/ru). The row button first reused common.dismiss, which translates to "hide" (关闭, Nascondi, Скрыть) and misdescribes a permanent action. The confirm string keeps its count in a separate label so no wording depends on grammatical number. Tests cover single and bulk removal, refusal of a processing task, refusal of a cancelled task whose run has not exited, persistence of the surviving queue, and selection reconciliation. Mutation checks: dropping the activeRuns guard turns the active-cancelled test red, persisting an empty snapshot turns the survivor test red, clearing the selection outright and removing the Set identity short-circuit each turn the reconciliation test red. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #707
What
Lets the user permanently remove stopped ingest tasks from the queue, one at a
time from the task row or in bulk from the existing selection bar.
removeTasks(taskIds)iningest-queue.tsdropsfailed/cancelledtasksfor the active project in a single queue write.
removeTask(taskId)is a thinwrapper over it.
then remove. Nothing touches
activeRuns,cancelledInFlightTaskIdsor thecommit coordinator, so an in-flight run keeps owning its own teardown.
activeRunsare skipped too.cancelledonlyrecords that cancellation was requested: the run holds its worker slot until
it observes the AbortSignal, and a commit past the interruptible point still
finishes. Removing the entry then would free the sourcePath that
enqueueBatchdedupes against, so re-importing the same source would mint a fresh task ID
that the
!activeRuns.has(task.id)guard inprocessNextcannot match. Thetwo runs cannot interleave their writes, since
autoIngestholds a per-sourceproject lock, but the newcomer would take a worker slot and sit in the panel
as "processing" while it blocks on that lock; with a small worker pool the
queue can look stalled while nothing is able to progress.
hasActiveRun(taskId)lets the panel hide the remove affordance for that beatrather than offering a click the core would refuse, which also keeps the bulk
confirmation count honest.
retainQueuedSelection()reconciles the selection against the queue after aremoval instead of clearing it, so a row that was not removed stays visible
and selected. It returns the original Set when nothing changed.
a run is still winding down), and a "Remove" action in the bulk bar next to
Restart / Cancel. The bulk action confirms first because the removal cannot be
undone.
common.removeandactivity.removeSelectedConfirm, addedacross all four bundles. The row button previously reused
common.dismiss,which translates to "hide" (
关闭,Nascondi,Скрыть) and misdescribes apermanent action.
Why
#557 established that stopped tasks are retained on purpose so they can be
restarted, and 0fd9257 implemented that by turning cancel from "drop the task"
into "keep it as
cancelled". That closed the recoverability gap but left no wayout for tasks that will never succeed: a corrupt PDF, an unsupported format, or a
source file the user has since deleted. A successful task leaves the queue as
soon as it commits, and
saveQueuefilters any straydoneentry, so nothingelse accumulates.
failedandcancelledentries stay in the Activity Panelindefinitely. #707 reports exactly this.
Two removal paths already exist and neither fits.
discardTasksForSourcesisreachable only through scheduled-import reconciliation, never from the UI.
clearCompletedTasksdrops every non-pending/processing task in one call, has noproduction caller, and carries neither a selection nor a live-run guard, so
exposing it as-is would give up the granularity #707 asks for.
Relationship to the other open queue-cleanup PRs
cancelledonlyfailed+cancelledThis one is the narrowest of the three: it never stops running work, and it
reuses the selection bar that #557 already added rather than introducing another
top-level button.
Worth flagging: #704 also adds an
export async function removeTasks(taskIds: readonly string[]): Promise<number>immediately afterdiscardTasksForSources,so the two cannot be merged mechanically even though the behaviour differs
(#704 accepts
cancelledonly and has no live-run guard). #690 touches the sameseven files and currently shows as conflicting. Happy to rebase, rename, or fold
this into whichever of the others you prefer to land first.
Tests
src/lib/ingest-queue.test.ts:failedtask and asserts the persisted snapshot holds exactly thesurvivor, not merely that the removed ID is absent
and an empty selection are no-ops
retainQueuedSelectiondrops removed IDs, keeps refused and untouched ones,and preserves Set identity when nothing changed
Mutation checks: dropping the
activeRunsguard turns the active-cancelled testred, persisting an empty snapshot turns the survivor test red, and clearing the
selection outright or removing the
Setidentity short-circuit each turn thereconciliation test red.
npm run test:mocks→ 132 files, 1881 tests passing.npm run typecheckandnpm run buildclean. The i18n parity test covers both new key paths.