refactor(ui): validation as errors on attempt, help text behind the ? (#1561)
The founder flagged the app as bloated with standing instructional text. Ny verifikation: the what's-missing lines (Ange en beskrivning, Minst två rader...) rendered from the first frame because their only gate was form validity, which an empty form fails: instructions dressed as validation. The submit buttons now stay enabled and an attempt on an incomplete form is what surfaces the lines, in destructive red, per the error-on-submit idiom. The Enter-to-advance flow keeps the old completeness predicate so navigation is untouched. Matcha mot befintlig verifikation: the two-sentence explainer moved behind a ? (HelpPopover, convention 7), the N:1 note tightened, the Visa även matchade switch became a quiet link (switches are settings idiom), and Stark träff, the normal auto-selected case, renders as muted text instead of a chip (chips mark exceptions, convention 5). Deleted always-visible paraphrase lines and their orphaned keys: items_card_description, picker_description, references_subtitle, sort_stack_hint, dimensions hints, document_help, and the dead fill_balance_hint key that had no render site at all. Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Jakob Wennberg
Claude Fable 5
parent
1e9f245f7c
commit
abff8d90f6
@@ -10,15 +10,19 @@ import { formatVoucher } from '@/lib/bookkeeping/voucher-series-resolver'
|
||||
|
||||
/**
|
||||
* Map the endpoint's 0-1 match confidence (attached only when candidates are
|
||||
* ranked for a specific transaction) to a labelled strength badge, so the user
|
||||
* can tell an exact-amount hit from a fuzzy guess before vouching for an
|
||||
* immutable verifikat. Returns null when no confidence was attached.
|
||||
* ranked for a specific transaction) to a strength label, so the user can tell
|
||||
* an exact-amount hit from a fuzzy guess before vouching for an immutable
|
||||
* verifikat. Returns null when no confidence was attached.
|
||||
*
|
||||
* Chips mark exceptions (convention 5): a strong hit is the normal,
|
||||
* auto-selected case and renders as muted text; only the fuzzy guesses that
|
||||
* deserve a second look get a chip.
|
||||
*/
|
||||
function confidenceBadge(
|
||||
function confidenceMark(
|
||||
confidence: number | undefined,
|
||||
): { label: string; variant: 'success' | 'secondary' | 'outline' } | null {
|
||||
): { label: string; variant: 'secondary' | 'outline' | null } | null {
|
||||
if (confidence == null) return null
|
||||
if (confidence >= 0.85) return { label: 'Stark träff', variant: 'success' }
|
||||
if (confidence >= 0.85) return { label: 'Stark träff', variant: null }
|
||||
if (confidence >= 0.6) return { label: 'Trolig träff', variant: 'secondary' }
|
||||
return { label: 'Svag träff', variant: 'outline' }
|
||||
}
|
||||
@@ -132,7 +136,7 @@ export function MatchVerifikationPicker({
|
||||
// green "Stark träff" can't visually encourage an accidental double-match:
|
||||
// "Redan matchad" is the signal that matters there (N:1 stays opt-in).
|
||||
const strength =
|
||||
(selected.linked_transaction_count ?? 0) > 0 ? null : confidenceBadge(selected.confidence)
|
||||
(selected.linked_transaction_count ?? 0) > 0 ? null : confidenceMark(selected.confidence)
|
||||
return (
|
||||
<div className="flex items-center gap-2 rounded-lg border border-border bg-secondary/40 px-3 py-2 text-sm">
|
||||
<span className="font-mono text-xs shrink-0">{formatVoucher(selected)}</span>
|
||||
@@ -140,9 +144,13 @@ export function MatchVerifikationPicker({
|
||||
<span className="tabular-nums shrink-0">{formatCurrency(amount)}</span>
|
||||
<span className="truncate text-muted-foreground flex-1 min-w-0">{selected.entry_description}</span>
|
||||
{strength && (
|
||||
<Badge variant={strength.variant} className="shrink-0 text-[10px]">
|
||||
{strength.label}
|
||||
</Badge>
|
||||
strength.variant ? (
|
||||
<Badge variant={strength.variant} className="shrink-0 text-[10px]">
|
||||
{strength.label}
|
||||
</Badge>
|
||||
) : (
|
||||
<span className="shrink-0 text-[11px] text-muted-foreground">{strength.label}</span>
|
||||
)
|
||||
)}
|
||||
{(selected.linked_transaction_count ?? 0) > 0 && (
|
||||
<Badge variant="secondary" className="shrink-0 text-[10px]">
|
||||
@@ -175,7 +183,7 @@ export function MatchVerifikationPicker({
|
||||
{filtered.map((line) => {
|
||||
const amount = line.debit_amount > 0 ? line.debit_amount : -line.credit_amount
|
||||
const strength =
|
||||
(line.linked_transaction_count ?? 0) > 0 ? null : confidenceBadge(line.confidence)
|
||||
(line.linked_transaction_count ?? 0) > 0 ? null : confidenceMark(line.confidence)
|
||||
return (
|
||||
<button
|
||||
key={line.line_id}
|
||||
@@ -200,9 +208,13 @@ export function MatchVerifikationPicker({
|
||||
{line.line_description || line.entry_description}
|
||||
</span>
|
||||
{strength && (
|
||||
<Badge variant={strength.variant} className="shrink-0 text-[10px]">
|
||||
{strength.label}
|
||||
</Badge>
|
||||
strength.variant ? (
|
||||
<Badge variant={strength.variant} className="shrink-0 text-[10px]">
|
||||
{strength.label}
|
||||
</Badge>
|
||||
) : (
|
||||
<span className="shrink-0 text-[11px] text-muted-foreground">{strength.label}</span>
|
||||
)
|
||||
)}
|
||||
{(line.linked_transaction_count ?? 0) > 0 && (
|
||||
<Badge variant="secondary" className="shrink-0 text-[10px]">
|
||||
|
||||
Reference in New Issue
Block a user