fix: drop the add-anyway override from the upload tray

Storing the same bytes twice splits reading progress and shelves across two
records that can never converge. The skipped row still names the book that
holds the file; allow_duplicates stays on the API for a wrong verdict.
This commit is contained in:
2026-08-14 14:18:08 -04:00
parent d321315acf
commit 3f39f1f8ae
2 changed files with 11 additions and 49 deletions
@@ -118,7 +118,13 @@
{job.error}
</span>
{:else if duplicates.length > 0}
<span class="flex min-w-0 items-baseline gap-2 text-[10px] text-muted-foreground">
<!--
Reported, with no offer to override. Storing the same bytes twice
splits reading progress and shelves across two records that can
never converge, and there is no version of that the reader wants.
`allow_duplicates` stays on the API for when the match is wrong.
-->
<span class="block min-w-0 text-[10px] text-muted-foreground">
{#if target}
<a
href={resolve('/(root)/(library)/book/[bookId]', { bookId: String(target) })}
@@ -129,23 +135,6 @@
{:else}
<span class="truncate">{duplicateNote(duplicates)}</span>
{/if}
<!-- The hash reads a sample of the file, so a match is strong
evidence rather than proof. Someone who knows better needs a
way to overrule it.
Only for a book that was skipped whole: sending a partly-new
folder again would build a second book holding both the file
that just went in and the one that was already here. -->
{#if job.status === 'skipped' && !queue.active}
<button
type="button"
class="shrink-0 underline underline-offset-2 hover:text-foreground"
onclick={() => queue.addAnyway(job.id)}
>
Add anyway
</button>
{/if}
</span>
{:else}
<span class="block font-mono text-[10px] text-muted-foreground tabular-nums">
+4 -31
View File
@@ -21,8 +21,6 @@ export interface UploadJob {
* and still carries these, since the reader asked for those files too.
*/
duplicates?: DuplicateFile[];
/** Send this one again with `allow_duplicates`, from "Add anyway". */
force?: boolean;
}
export interface UploadSummary {
@@ -174,27 +172,6 @@ export class UploadQueueState {
void this.#run();
}
/**
* Sends a job again, telling the server to store it even though it matched.
*
* The hash samples a small part of the file, so a match is strong evidence
* rather than proof; this is how someone who knows better overrules it.
*/
addAnyway(id: string) {
this.jobs = this.jobs.map((job) =>
job.id === id
? {
...job,
status: 'queued' as UploadStatus,
force: true,
duplicates: undefined,
error: undefined
}
: job
);
void this.#run();
}
async #run(onFinished?: (summary: UploadSummary) => void) {
if (this.#running) return;
this.#running = true;
@@ -217,14 +194,10 @@ export class UploadQueueState {
const body = new FormData();
for (const file of job.files) body.append('files', file);
const query =
`library_id=${encodeURIComponent(String(job.libraryId))}` +
(job.force ? '&allow_duplicates=true' : '');
const response = await fetch(`/api/books/fromFiles?${query}`, {
method: 'POST',
body
});
const response = await fetch(
`/api/books/fromFiles?library_id=${encodeURIComponent(String(job.libraryId))}`,
{ method: 'POST', body }
);
if (!response.ok) throw new Error(`The server returned ${response.status}`);