Skip to content

ADR-0085: One budget-card primary action and only valid destructive swipes

Context

Two Warm ledger review findings met on the same card (#378, #382). The card had no primary action at all: changing a limit meant discovering either the swipe or the trailing overflow menu. The left swipe always revealed the destructive strip, even for a default category with no limit, where the action was a no-op after the reveal. The gesture closure also captured the old limit state, so a limit cleared by another route could leave a stale destructive gesture behind.

The existing card-height and one-overflow decisions (ADR-0047) still hold: the card must not grow a wrapping action row, and View transactions and the destructive actions must stay separate from editing.

Decision

The card is the edit/set-limit action. A whole-card click with role Button and a localized click label opens the category editor for the displayed month, whether the copy is Edit limit or Set limit. The supporting limit line carries a visible, decorative pencil icon and the same label, so the primary action is discoverable without swipe or overflow. The visible label is not a second interactive node; it is part of the card's single named target and adds no nested focus stop.

View transactions and destructive actions stay distinct. The trailing overflow keeps Edit limit / Set limit (the same action as a second entry point), View transactions, Remove limit (any positive or unconvertible saved limit) and Delete category (custom categories only), with the same confirmations and drill scope as ADR-0047. The card's custom accessibility actions expose all four operations without dragging.

Only valid swipe directions exist. A default category without a limit has no destructive action, so its left drag is clamped at rest, its red reveal is never composed, and its drag cannot call an action. A default category with a limit still reveals and confirms Remove limit; a custom category always reveals and confirms deletion. The pointer input is keyed on the category kind, limit presence, zones and callbacks, accumulates the drag synchronously for the threshold decision, and settles a displaced card back when a limit disappears. Swipe right still edits. When a limit disappears while the card is displaced, the stored offset is settled to zero so re-adding a limit cannot resurrect a stale red reveal.

Consequences

The primary action is visible, keyboard/assistive named and at least as large as the card, without adding a row button or changing the card-height budget at 100% text. A no-op destructive reveal cannot appear, and a live limit removal cannot leave a stale callback. Long limit copy yields space to the visible action label, which stays complete at 320 dp and 200% text. No schema, route, money, draft, lock or dependency contract changes.

Verification

BudgetActionsTest covers card tap and named OnClick activation, the visible Set/Edit label, independent View transactions, tap-versus-drag and threshold/no-op behaviour, default/custom and limit/no-limit cases, the live limit change, a mid-drag limit removal and a limit re-added before the settle completes, 320 dp/200% light/dark and dynamic colour. BudgetContrastTest measures the action label against the card surface. BudgetContentTest, BudgetListMotionTest, DesignSystemMatrixTest, AdaptiveLayoutTest, GalleryInventoryTest and the debug role gallery cover the surrounding hierarchy and the refreshed fixture.