Per the user's directive — DS-compliance over pixel-perfect alignment
with the design mock. Walked the design audit and applied every swap
that lands within ±2px of the original.
Swaps:
- _health_strip: gap-[18px] → gap-5 (+2), px-[14px] → px-3.5 (=),
text-[13px] → text-sm (+1).
- _search_filters: chip group p-[3px] → p-1, rounded-[10px] →
rounded-xl (concentric with rounded-lg inner pills), chip py-[5px]
→ py-1.
- _status_pill: text-[11px] → text-xs.
- _group_heading: mt-[18px] → mt-5.
- _maturity_badge: text-[10px] → text-xs.
- provider_card: tagline + foot text-[13px] → text-sm.
Kept arbitrary: `min-w-[200px]` in _search_filters — nearest scale
tokens are min-w-48 (192px) and min-w-52 (208px); both are noticeable
layout shifts for a one-off responsive guard. Worth keeping the
arbitrary here.
Net: 9 of 10 arbitrary values gone. Visual delta: max +2px on a
single value. Design mock and DS scale now agree.
- Sync all: replace the hand-rolled `button_to` with `DS::Link.new(variant: "outline", method: :post)` — same component as the
"Identify Patterns" button on the recurring-transactions page.
- Search input: switch to the icon-overlay pattern used by the
Manage-currencies and transaction filter rows
(relative wrapper + absolutely positioned search icon +
bordered input with `focus:ring-gray-500`). Brings the keyboard
focus state in line with the rest of the app's filterable lists.
- SnapTrade panel: restore the "needs registration" status row that
the drawer-cleanup pass dropped along with the redundant
Configured/Not configured footer. The unregistered case is
meaningful state, not redundant chrome.
- Move the slim health-strip computation out of the controller and
into `SettingsHelper#provider_health_strip` (Convention 2: skinny
controllers).
- Extract `concise_time_ago` helper so the "drop leading 'about '"
trick stops being duplicated 3x.
- `Settings::ProviderCard#maturity_label` (instance) now delegates
to `.maturity_label` (class) instead of duplicating the lookup.
- Drop unused `warn_or_err` local in `_connection_row`.
- Replace the `data-controller` string-injection + html_safe in
`_connection_row` with `tag.details(data: ...)`; safer and more
idiomatic.
- Add a system test for the empty-filter message wiring.
- Icons were rendering at 20px because the application_helper's `icon`
default size (`md` = w-5 h-5) was beating the inline class override
in compiled CSS source order. Pass `size: "sm"` and use the project's
`!w-3.5 !h-3.5` important-prefix pattern (precedent: dashboard.html.erb)
so chevron, refresh-cw, search, check, circle-alert, and arrow-right
all render at the design's 14px.
- Search input was 54px tall because @tailwindcss/forms applies
`padding: 8px 12px` to bare `<input type="search">`. Override with
`!p-0 focus:ring-0 focus:shadow-none` so the wrapping div's padding
alone defines the box (38px total — matches the design).
Pixel-level alignment to the design's §05 mock + cleanup from a DS
audit pass.
Paddings, margins, font sizes
- Health strip: my-4 → mt-4 mb-5 to match the design's 16px / 20px
vertical breathing room.
- Search filters bar: gap-2 → gap-2.5; mt-2 → mt-5 mb-3 (was missing
the 12px bottom margin entirely).
- Search box: rounded-lg → rounded-[10px]; px-3 py-2 → px-[14px]
py-[9px]. Search icon downsized w-4 → w-3.5 to match.
- Chip group: p-1 → p-[3px]; rounded-lg → rounded-[10px].
- Chip: py-1 → py-[5px]; rounded-md → rounded-lg.
- Group heading: mt-2 → mt-[18px]; mb-1 → mb-1.5.
- Status pill: text-xs → text-[11px].
- Provider card: gap-3 → gap-2.5 (outer + top); name gets explicit
text-sm; tagline + foot 14px → 13px; arrow icon w-4 → w-3.5.
- Sync icon button: p-1 → fixed w-7 h-7 (28×28) so the row hit
target matches the design's column width.
- Connect drawer header logo glyph: text-[10px] → text-xs (matches
the available card's logo-glyph treatment).
Component / partial cleanup (DS audit follow-ups)
- New _maturity_badge partial replaces the inline span that was
duplicated in 3 places (_connection_row, _drawer_header,
provider_card.html.erb).
- Settings::ProviderCard.maturity_label class method centralizes the
MATURITY_LABELS lookup; callers no longer reach into the constant.
- _connection_row title: <h2> → <h3> (the row sits inside the
"Your connections" h2 group heading; nested h2s flattened the
outline).
- show.html.erb encryption error: <h3> → <h2> for the same reason.
Locale
- Drop orphaned keys: settings.providers.groups.connected and
groups.needs_attention (no view code uses them) plus the leftover
show.coinbase_title block.
- Health strip "needs reconsent" → "needs attention" so the strip
copy lines up with the per-row status pill ("Action needed") and
the original group heading wording.
A11y
- focus-visible:ring-2 on chip buttons, provider-card link, and
focus-within:ring-2 on the search input wrapper. Keyboard users
now get a visible focus state.
- Search input: explicit autocomplete="off" (erb_lint hint).
Per the design review, the "Add another provider · Browse providers"
card was a redirect to content one scroll-tick away. A search input
plus kind chips lets users self-segment the catalog and is the right
tool once it grows beyond the four to twelve providers we ship today.
- New providers_filter Stimulus controller — case-insensitive free
text search across name/region/kind, plus a chip group with
All / Banks / Crypto / Investment that toggle visibility via
Tailwind's `hidden` class.
- _search_filters partial: search box (count-pluralized placeholder)
+ chip group, ARIA-labelled and aria-pressed for the chips.
- ProviderCard exposes filter_data (target + name/region/kind data
attrs) so the controller can match without re-rendering.
- Lunchflow's `kind` was "Lunch" — switched to "Bank" so it falls
under the Banks chip alongside its actual offering (it aggregates
banks).
- Drops the add_provider_cta partial and its locale entries; adds
search_filters.* and an empty_filter message.
Picks up the remaining items from Claude Design's review of #1710
that the previous review-feedback commit didn't cover.
DS / casing
- Sentence-case the page title ("Bank Sync" -> "Bank sync") and
align the nav label.
- Drop the card hover-lift (shadow-border-sm) in favour of
bg-container-hover; per the DS, card hover is colour-only.
- Whole-tile click target on each provider card — the inner
"Connect ->" link was a hit-target inversion.
- Set Sync all to whitespace-nowrap so the label stops wrapping at
narrow viewport widths.
UX simplifications
- Drop the four health-summary tiles (per-row warn/err pills already
surface the signal at the scale this app sees). Removes
Settings::HealthSummary, the @health_counts controller block, and
the now-unused health.* locale keys.
- Hide "Your connections" heading + empty-state line when no
providers are connected — the lede already invites a connect.
- Drop the redundant "Free" tier from per-card meta lines (printed
10x for one fact); "Paid" still surfaces on Plaid.
Tests updated to drop the obsolete tiles assertion and switch the
provider-card click selector to look up the (now whole-card) anchor
by provider name.
- Add Provider::Metadata registry with static display data (region, kind,
tier, maturity, logo) for all 11 providers
- Add Settings::ProviderCard ViewComponent rendering logo square, name,
Beta/Alpha pill, meta line (region · type · tier), tagline, and Connect link
- Add connect_form action + route (GET /settings/providers/:key/connect_form)
that opens the existing panel partial or config form in a DS::Dialog drawer
- Replace the Available accordion loop with a 2-column responsive card grid;
empty state when all providers are connected
- Fix layout override: use turbo_rails/frame layout for frame requests so the
drawer response is not wrapped in the full settings layout (was causing
Turbo to pick the empty outer drawer frame instead of the filled one)
- Add SyncAllProvidersJob and last_sync_all_attempted_at migration (sync-all
throttle support)
- Unify Connected + Action needed into a single "Your connections" section;
items with warn/err status auto-open
- Fix Enable Banking grouping: items with expired sessions were returning
:off (Available) instead of :warn (Your connections); gate now checks
any? instead of any?(&:session_valid?)
- Add reconsent_required locale key for fully-expired EB sessions
- Surface Beta/Alpha maturity pills on connected provider accordion rows
via new badge: param on settings_section helper
- Add i18n taglines for all 11 providers; add connect and empty_available keys
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extend provider_summary to return :err/:warn with meta text by checking
latest sync per item (window function, same pattern as ProviderConnectionStatus)
and Enable Banking session expiry within 7 days
- Partition provider entries into three groups: Connected (:ok), Action needed
(:warn/:err, auto-opened), Available (:off)
- Add Settings::HealthSummary ViewComponent — four-tile grid showing Connected,
Action needed, Errors, and Accounts synced counts
- Render health strip directly under page description; omit Action needed heading
when group is empty
- Add i18n keys for tile labels, group heading, and all meta strings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>