fix(documents): report picker and card results through the shared outcome line, and register the disconnect as a destructive control

This commit is contained in:
Marc-André Bombeck committed 2026-09-26 13:32:52 +02:00
1 parent d109d67889
commit 9e38ee3a99
4 files changed
+46 -14

No files matched your search

@@ -579,6 +579,13 @@ const REGISTRY: DestructiveEntry[] = [
recovery: "permanent",
confirm: ["AlertDialog"],
})),
{
file: "components/settings/integrations/document-sources-card.tsx",
destroys:
"the stored Paperless-ngx or Papra address and API token; documents already imported stay in the vault — reconnecting means typing the token again",
recovery: "permanent",
confirm: ["AlertDialog"],
},
];
// ── Detectors ─────────────────────────────────────────────────────────────
@@ -161,6 +161,13 @@ const PINNED_AFFORDANCES: Record<
"src/components/documents/document-card.tsx": { "text-success": 1 },
"src/components/documents/document-detail-sheet.tsx": { "toast.success": 2 },
"src/components/documents/documents-view.tsx": { "toast.success": 3 },
// The document picker's per-row result: tinted only for the `imported` /
// `duplicate` outcome the import route returned for that one document, i.e.
// a document the vault now holds. The run's headline goes through
// WrittenOutcomeLine; a row sits inside a button, where its <p> cannot.
"src/components/documents/sources/document-source-picker.tsx": {
"text-success": 1,
},
"src/components/error-details.tsx": { CheckCircle2: 2, "text-success": 1 },
"src/components/illness/episode-menu.tsx": {
CheckCircle2: 2,
@@ -69,6 +69,9 @@ import { invalidateKeys, queryKeys } from "@/lib/query-keys";
import { cn } from "@/lib/utils";
import type { InboundDocumentKindValue } from "@/lib/validations/inbound-documents";
import { WrittenOutcomeLine } from "@/components/outcome/written-outcome-line";
import type { WrittenOutcome } from "@/lib/outcome/written-outcome";
import { formatBytes } from "../vault-utils";
import {
errorCodeOf,
@@ -306,7 +309,7 @@ export function DocumentSourcePicker({
data-slot="document-source-selection"
>
{finished
? t("documents.sourcePicker.summary", counts)
? null
: running
? t("documents.sourcePicker.importing")
: t("documents.sourcePicker.selectedCount", {
@@ -469,6 +472,14 @@ export function DocumentSourcePicker({
</button>
) : null}
{finished ? (
<WrittenOutcomeLine
outcome={runOutcome(counts)}
message={t("documents.sourcePicker.summary", counts)}
testId="document-source-summary"
/>
) : null}
{stopMessage ? (
<p
role="alert"
@@ -597,6 +608,17 @@ export function DocumentSourcePicker({
);
}
/** How the run went, in the shared outcome vocabulary. */
function runOutcome(counts: {
imported: number;
present: number;
failed: number;
}): WrittenOutcome {
const held = counts.imported + counts.present;
if (counts.failed === 0) return held > 0 ? "success" : "empty";
return held > 0 ? "partial" : "failed";
}
/** One row's progress or result, under its title. */
function RunLine({ run }: { run: RowRun }) {
const { t } = useTranslations();
@@ -16,7 +16,7 @@
* (UI standards §12).
*/
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { Archive, CheckCircle2, Loader2, Unlink } from "lucide-react";
import { Archive, Check, Loader2, Unlink } from "lucide-react";
import { useState } from "react";
import {
@@ -38,6 +38,7 @@ import { PasswordInput } from "@/components/ui/password-input";
import { SettingsCardActions } from "@/components/settings/_card-actions";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { SettingsCard } from "@/components/settings/settings-card";
import { WrittenOutcomeLine } from "@/components/outcome/written-outcome-line";
import {
errorCodeOf,
sourceErrorMessage,
@@ -111,8 +112,8 @@ export function DocumentSourcesCard() {
>
{systemName(option)}
{isConnected ? (
<CheckCircle2
className="text-success size-3.5"
<Check
className="size-3.5"
aria-label={t("settings.documentSources.statusConnected")}
/>
) : null}
@@ -328,16 +329,11 @@ function SourceForm({
</div>
{outcome ? (
<p
role={outcome.tone === "error" ? "alert" : "status"}
data-slot="document-sources-outcome"
className={cn(
"text-sm",
outcome.tone === "error" ? "text-destructive" : "text-success",
)}
>
{outcome.message}
</p>
<WrittenOutcomeLine
outcome={outcome.tone === "error" ? "failed" : "success"}
message={outcome.message}
testId="document-sources-outcome"
/>
) : null}
<SettingsCardActions>