* feat(ds): one canonical focus ring across primitives (#2136)
Replaces the grab-bag of per-primitive focus indicators (neutral
ring-alpha-black/white, outline-gray-900/white, faint form-field ring-4)
with a single recipe — the #1737 accessibility follow-up.
- New --color-focus-ring token: blue-600 (light) / blue-500 (dark),
>=4:1 against both surfaces.
- Canonical .focus-ring / .focus-ring-within in components.css: a 2px
outline + 2px offset on :focus-visible only. Outline (not a box-shadow
ring) so the offset gap is transparent on any surface with no layout
shift; :focus-visible so it never shows for mouse/touch.
- Applied to every focusable DS primitive: Button (had none), Link,
Disclosure summary, Tabs nav, MenuItem (replaces the browser-default
box), SearchInput, Tooltip trigger, Popover trigger, Select panel
(focus-within), Toggle (peer-driven outline-focus-ring). .form-field
adopts it via :focus-within, replacing the ~1:1 ring-4.
- Dialog close button is a DS::Button icon variant, so it inherits the
focus-visible-only ring and keeps no resting border (fixes "stuck ring").
Verified in-browser, light+dark: focus-visible ring on button, input, and
full-width menu row — consistent blue 2px+offset, legible on both surfaces.
Remaining follow-up: >=44px touch targets (disclosure trigger, composer
send); bespoke notification / account-new close buttons that still carry a
permanent border.
* fix(ds): #2136 interactive-state follow-ups — touch target + close-button chrome
- Disclosure default trigger: add min-h-11 (44px) so the standalone disclosure
summary clears the touch-target minimum (was px-3 py-2 ~36px). Composer send +
the coming-soon icons are already DS::Button icon/md (w-11 h-11).
- Notification close buttons (sync_toast, notice): drop the resting
border-alpha-black-50 box ("frame shouts, glyph muted"); keep a bg-container +
shadow-xs chip so the corner control stays visible over the page, and brighten
the muted glyph on hover (text-subdued -> hover:text-primary).
* refactor(ds): focus ring -> neutral hugging box-shadow (was blue outline)
Per design feedback: the blue 2px outline + 2px offset read as a loud,
detached frame on the otherwise-neutral UI. Switch the canonical .focus-ring
to a soft box-shadow ring that hugs the control (follows border-radius, no
gap), in the theme-aware neutral focus-ring token (alpha-black/white-400).
Transparent outline kept as a forced-colors fallback; toggle peer-driver
switched from outline-* to ring-* to match. Still one token, :focus-visible
only. Strength is tunable (currently subtle ~1.5:1).
* fix(ds): focus ring vanished on shadowed controls — outline, not box-shadow
The neutral box-shadow ring lived in the components layer, so any utility-layer
shadow-* (or .form-field's focus-within:shadow-none) on the same element
overrode it and the ring silently disappeared on shadowed buttons/inputs. Draw
the same subtle neutral ring with a hugging `outline` (outline-offset: 0)
instead — a separate property with no box-shadow conflict, and it doubles as the
forced-colors indicator. Toggle peer-driver switched ring-* -> outline-* to
match. Look is unchanged (neutral, hugging, subtle); it just no longer vanishes.
* fix(a11y): enlarge sync-toast close-button touch target (p-0.5 -> p-1.5)
The hover-revealed close button had ~2px padding around a 20px icon (~24px
total), at the WCAG 2.5.8 AAA boundary. p-1.5 brings the interactive area to
~32px. Addresses CodeRabbit review on #2140.
* fix(ds): keep form-field's resting halo; stop the outline color flash
Two testing findings:
- .form-field reverts to its original always-on soft ring
(focus-within ring-4 at low alpha, theme-aware) instead of adopting
the keyboard-only outline. It's a resting decoration, not a focus
indicator, and the lower-opacity halo was the better look. The
canonical block's comment documents the deliberate opt-out.
- .focus-ring/.focus-ring-within now carry a base transparent 2px
outline so consumers with transition-all (form-field had it) animate
transparent -> token on focus instead of passing through
currentColor, which flashed as a black border appearing and then
fading out.
* feat(ds): focus-ring token clears WCAG 3:1 non-text contrast
alpha-black-400 (20%) measured ~1.6:1 against white — visible but below
the AA bar for focus indicators. Bump to the 700 stop (50%): ~3.95:1 on
light containers, ~4.6:1 on dark. Recipe unchanged; one token edit via
tokens:build.
* fix(ds): ring the hand-rolled privacy toggle too
The header pair showed two different focus treatments: panel-right (a
DS::Button) got the new token ring while the hand-rolled privacy
toggle next to it fell back to the browser-default ring — the sweep
covered DS primitives but not bespoke buttons. Both privacy toggles
(mobile + desktop) now carry .focus-ring.
Also documents the transition interplay on the focused-state rule:
consumers with transition-colors fade the ring in over 150ms because
Tailwind v4's color transition list includes outline-color. Verified
settled value at the intended 50% alpha via Playwright.
* fix(ds): ring the sidebar and settings nav links
The reshoot caught both nav species falling back to the browser's blue
default ring — main sidebar items and settings nav items are bespoke
link_to markup the primitive sweep missed, and they're the primary
keyboard path in the app. Both adopt .focus-ring (main nav adds
rounded-lg so the outline follows a shape).
* fix(ds): retire the legacy base-layer button ring for the canonical outline
The @layer base button rule still painted a ring-2 ring-offset-2
box-shadow on :focus-visible. Box-shadow and outline are independent
properties, so .focus-ring (an outline) could never clear it and every
button-tag primitive double-painted both indicators on keyboard focus.
Apply the canonical recipe to the base button rule itself: every
<button> now gets the transparent resting outline + focus-ring token on
:focus-visible by default.
Two bespoke buttons suppressed the outline with focus:outline-none and
relied on the base ring for their keyboard indicator (category dropdown
rows, the sign-up password toggle). Drop the suppression so they pick
up the canonical outline — :focus-visible keeps it keyboard-only, which
is what the suppression was protecting against anyway.
* fix(ds): segmented control adopts the canonical focus recipe
The segment rule inlined its own focus-visible outline (offset 2,
alpha-400 colors) with a comment noting it was temporary until the
canonical token landed — this branch is that token. Drop the inlined
utilities: button segments get the outline from the base button rule,
and link segments now carry .focus-ring.
* feat(ds): elevate dropdown overlays and stabilize selection check gutter
Menus and popovers floated at the same elevation as inline cards
(shadow-border-xs), so dropdowns blended into the content beneath them.
Bump DS::Menu and DS::Popover panels to shadow-border-lg.
DS::MenuItem rendered its leading icon only when present, so a selection
check shifted the row's text out of alignment with the unselected rows.
Add a `selected:` param that reserves a fixed-width check gutter (check
when selected, empty otherwise) so row text stays aligned. Apply the same
reserved gutter to the bespoke category dropdown row, and add a
`selectable` menu preview.
* fix(ds): expose menu selection via menuitemradio + aria-checked
Selectable DS::MenuItem rows conveyed selection only visually. Render them
as role="menuitemradio" with aria-checked so assistive tech gets the
selection state of single-select lists, merging the menu ARIA contract with
any caller-supplied aria. Addresses CodeRabbit review feedback.
* fix(ds): include selectable roles in menu roving-focus query
DS::MenuItem selectable rows render as role=menuitemradio, but the menu
controller built its roving-focus list from [role=menuitem] only, leaving
single-select menus with no keyboard focus/arrow handling. Query the
menuitemradio/menuitemcheckbox roles too. Addresses Codex review feedback.
PR #1840 bumped DS::Button icon-only `:md` size from `w-9 h-9` (36×36)
to `w-11 h-11` (44×44) for WCAG 2.5.5 enhanced touch target. DS::Menu's
`:icon` variant uses DS::Button at the default `:md` size, so every
row-level "..." action-list trigger grew from 36×36 to 44×44.
For dense lists where each row has a trigger — most visibly the
transaction category dropdown (`category/dropdowns/_row.html.erb`) —
the per-row height bump (+8px) compounds: a 5-category panel that
used to fit in ~220px now wants ~260px, the badges look smaller
relative to the row chrome, and the overall density that made the
dropdown scannable regresses visibly.
Add an `:icon_sm` variant that renders the trigger as DS::Button at
`size: :sm` (32×32). Meets WCAG 2.5.8 AA (24×24) — appropriate for
compact in-row triggers where 44×44 isn't required. Standalone
toolbar / row-action `...` triggers should keep `:icon` for AAA.
Migrate `category/dropdowns/_row.html.erb` to `:icon_sm` to restore
the pre-#1840 dropdown density.
* feat(splits): add excluded attribute support for split children and improve rendering of split transactions
* address coderabbitai suggestions to improve code quality
* Fix split excluded coercion, DRY helpers, and clean up view partials
Fix boolean coercion bug where string "false" from form params was
truthy in Ruby, causing all split children to be marked excluded.
Use ActiveModel::Type::Boolean for explicit casting in Entry#split!.
Additional changes addressing code review feedback:
- Extract duplicated in_split_group logic from TransactionsController
and TransactionCategoriesController into TransactionsHelper
- Remove redundant local_assigns.fetch calls in partials that already
declare defaults via the Rails 7.1 locals: magic comment
- Simplify ternary in _transaction.html.erb to pass grouped directly
- Guard hidden_field_tag :grouped to only emit when value is "true"
- Add model tests for excluded on split children (boolean and string)
- Add controller test for excluded param through full HTTP stack
- Add test confirming excluded children are dropped from balance queries
* fix(splits): simplify excluded attribute boolean check
* refactor(splits): extract truthy values constant for excluded check
Extract the array of truthy values used for excluded attribute check
into a private constant to improve code maintainability and avoid
duplication of the magic array.
* refactor: simplify split grouping link generation and add test coverage for excluded split parameters
* Make category selection menus opaque for excluded transactions
* Allow keyboard navigation in category selection menu
* Fix category transparency on mobile
* Make checkbox opaque
* Remove text-secondary from amount container
* Submit form directly
* Handle aria labels
* feat: Add toggle on mobile to show/hide checkboxes in transaction page
* fix: Add multi-select toggle also in activities page. Make JS controller compatible also in this view.
* feat: Add category in mobile view
* feat: Add mobile layout for transaction categories
* feat: Add margin for pagination on mobile
* fix: Ensure category exists when displaying the name
* fix: Adjust mobile paddings
* fix: Display "uncategorized" label if no category is set
* fix: Expand transaction name/subtitle
* feat: Add merchant name on desktop view
* feat: Move merchant name before account name
* fix: Add class to hide merchant on mobile
* feat: Add merchant logo on mobile
* fix: add pointer-events-none to merchant image on mobile view
* feat: toggle header checkbox in transaction page when button is clicked
* Remove unnecessary CSS class
* Remove duplicate CSS class
* Remove wrong Enable Banking logo URL
* Update app/views/transactions/_transaction.html.erb
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Alessio Cappa <104093777+alessiocappa@users.noreply.github.com>
* Revert "Update app/views/transactions/_transaction.html.erb"
This reverts commit 9766c50a1d.
* Add translation for Loan Payment/Transfer
* Apply review comments
* Add accessible name for toggle based on review comments
* Use border instead of border-1 class
* Apply review comments
* Missing l10n key
---------
Signed-off-by: Alessio Cappa <104093777+alessiocappa@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
* Fix category dark mode styles
* Fix sidebar account tab states
* Fix dashboard balance sheet group styles
* Fix budget dark mode styles
* Fix chart gradient split
* Fix prose styles in dark mode
* Add back chat nav id for tests
* Add lookbook + viewcomponent, organize design system file
* Build menu component
* Button updates
* More button fixes
* Replace all menus with new ViewComponent
* Checkpoint: fix tests, all buttons and menus converted
* Split into Link and Button components for clarity
* Button cleanup
* Simplify custom confirmation configuration in views
* Finalize button, link component API
* Add toggle field to custom form builder + Component
* Basic tabs component
* Custom tabs, convert all menu / tab instances in app
* Gem updates
* Centralized icon helper
* Update all icon usage to central helper
* Lint fixes
* Centralize all disclosure instances
* Dialog replacements
* Consolidation of all dialog styles
* Test fixes
* Fix app layout issues, move to component with slots
* Layout simplification
* Flakey test fix
* Fix dashboard mobile issues
* Finalize homepage
* Lint fixes
* Fix shadows and borders in dark mode
* Fix tests
* Remove stale class
* Fix filled icon logic
* Move transparent? to public interface
* Add geist font
* Design system css file
* Add cursor ui/ux rules
* Add shadows and shadow borders
* Replace primitives with tokens for common text and backgrounds
* Organize css
* Update switch and checkbox class names
* Add back global color variables
* Consolidate modal form structure into partial + helper
* Scaffold out trade transaction form
* Normalize translations
* Add buy and sell trade form with tests
* Move entryable lists to dedicated controllers
* Delegate entry group contents rendering
* More cleanup
* Extract transaction and valuation update logic from entries controller
* Delegate edit and show actions to entryables
* Trade builder
* Update paths for transaction updates
* Initial entryable models
* Update transfer and tests
* Update transaction controllers and tests
* Update sync process to use new entries model
* Get dashboard working again
* Update transfers, imports, and accounts to use Account::Entry
* Update system tests
* Consolidate transaction management into entries controller
* Add permitted partial key helper
* Move account transactions list to entries controller
* Delegate transaction entries search
* Move transfer relation to entry
* Update bulk transaction management flows to use entries
* Remove test code
* Test fix attempt
* Update demo data script
* Consolidate remaining transaction partials to entries
* Consolidate valuations controller to entries controller
* Lint fix
* Remove unused files, additional cleanup
* Add back valuation creation
* Make migrations fully reversible
* Stale routes cleanup
* Migrations reversible fix
* Move types to entryable concern
* Fix search when no entries found
* Remove more unused code
* Move Transfer to Account namespace
* Fix partial resolution due to namespacing plurality
* Make category and tag controllers consistent with namespacing convention
* Update stale partial reference