Commit Graph
142 Commits
Author SHA1 Message Date
Guillem Arias Fauste 6245b9cbd0 fix(pagination): stop the pager wrapping on narrow screens (#2967)
* fix(pagination): stop the pager wrapping on narrow screens

The page-number strip put the last page on a second line on a phone. The
container was a plain block, so the inline-flex links flowed as inline
content and wrapped like words. Measured on a 760-page list: the strip needs
~198px, and a 360px viewport leaves ~158px once the chevrons and the
per-page select are taken out.

The failure was not even consistent — at 402px the row wrapped, at 360px the
same markup overflowed its parent instead, because a block box inside a flex
item resolves its width differently at each size.

Make the strip a nowrap flex row, and drop the pages that aren't useful on a
phone. Small screens keep the first page, the last page, the current page and
the gaps ("1 … 5 … 760", ~148px); the full series returns from `sm` up. Short
series are left alone — Pagy only emits those when the collection has that
few pages, so hiding there would render a 3-page pager as "1 3".

`sm:` is a viewport breakpoint and the pager also lives in narrow columns on
wide screens (the account activity feed with both sidebars open), so the row
keeps `overflow-x-auto` as a backstop. At 320px, where even the reduced set
is wider than the space, it scrolls rather than wrapping.

Shared by twelve call sites, so this covers transactions, imports, rules,
account activity, statements, merchants, exports and the debug log.

* fix(pagination): mark the pages the mobile pager drops

Hiding the middle page links without saying so made the survivors read as
adjacent. On a 760-page collection sitting on page 3, Pagy emits
[1, 2, "3", 4, 5, :gap, 760] and a phone rendered "1 3 … 760" — page 2 gone
with nothing to show for it. A gapless 6-page series was worse: "1 6".

Each collapsed run now renders its own mobile-only ellipsis, unless Pagy
already put a gap beside that run, which would otherwise read as "… …".

  [1, 2, "3", 4, 5, :gap, 760]   ->  1 … 3 … 760
  ["1", 2, 3, 4, 5, 6]           ->  1 … 6
  ["1", 2, 3, 4, 5, :gap, 760]   ->  1 … 760      (Pagy's gap already covers it)
  [1, :gap, 3, 4, "5", 6, 7, …]  ->  1 … 5 … 760

The helper now returns a per-slot plan rather than a class, since the view
needs to know where a run starts, not just whether a slot is hidden.

Tests carry a property check — no two page numbers may survive side by side
unless they are consecutive — with a guard that fails if every pair happens to
be separated by an ellipsis, which would make the property vacuous. It was, on
the first pass.
2026-08-13 06:41:01 +02:00
Blaž Dular 826c4a356e feat(bank-sync): Wise integration (#2433)
* feat(wise): add Wise integration with JAR savings account and activity support

- Add WiseItem/WiseAccount models with full sync pipeline (importer, syncer, processor)
- Detect income vs expense using targetAccount == recipientId from borderless accounts API
- Support JAR (SAVINGS) accounts with totalWorth balance and savings subtype
- Fetch JAR activity via profile activities API (INTERBALANCE, BALANCE_CASHBACK, BALANCE_ASSET_FEE)
- Route INTERBALANCE activities to both JAR and STANDARD accounts and link as Transfer records
- Add provider connection status registration, routes, views, and i18n
- Add migration for wise_items and wise_accounts tables
- Add tests for WiseAccount, WiseEntry::Processor, WiseActivity::Processor, WiseItem::Importer, and WiseItem#link_jar_transfers!

* chore(lint): add ignore to security scan (false positive)

* fix(wise): address PR review feedback on activity routing, HTML stripping, rate limiting, and scope extraction

- Replace title string matching in activity_for_account? with resource.id vs balance_id comparison to avoid breakage when users rename JAR accounts on Wise
- Replace gsub(/<[^>]+>/, "") with ActionController::Base.helpers.strip_tags to safely handle user-controlled HTML-like content
- Wrap paginated API calls in with_rate_limit_retry (up to 3 attempts, exponential backoff) to handle 429 responses during fetch_jar_activities and fetch_transfers
- Extract WiseAccount.unlinked scope and remove duplicated left_joins query from controller

* fix(wise): address PR #2433 review feedback

- Wire @wise_items into AccountsController#index so linked accounts appear on /accounts
- Fix find_wise_account_for_linking to preserve .active scope via .merge instead of .then
- Fix cross-currency incoming transfer amount to use targetValue instead of sourceValue
- Add missing select_profiles.session_expired locale key
- Add missing account_name interpolation to link_existing_account success notice
- Use i18n for profile type labels in profile_display_name
- Trigger wise_item.sync_later after account linking in all three linking actions
- Isolate fee transaction failure so it no longer aborts the main transfer import
- Use bare raise in WiseActivity/WiseEntry processors to preserve original backtrace
- Re-raise in WiseItem::Unlinking to prevent silently orphaned Holdings
- Fix avatar badge to use design system tokens (bg-container-inset, text-primary)

* fix(wise): fix INTERBALANCE routing and encrypt pending token in session

* fix(wise): replace hand-rolled buttons/links with DS::Button and DS::Link

Address repeated sure-design DS drift findings: migrate all manual
Tailwind button_to and link_to calls in Wise views to DS::Button
(outline/outline_destructive variants) and DS::Link (secondary variant).
Add missing provider_panel.disconnect locale key for the disconnect button text.

* fix(wise): use render_provider_panel_error in create instead of missing new template

* fix(wise): add missing comma in PROVIDERS array

CI failed with a SyntaxError because the questrade entry wasn't
comma-terminated before the wise entry.

* chore(wise): re-add pipelock:ignore for pending token param

Lost when the token source moved from session to an encrypted params
field in 0b5dc886, causing the CI secret scanner to flag a false positive.

* fix(test): widen random ticker suffix to avoid rare collision flake

hex(2) only yields 65536 possible tickers, so create_trade's 4 calls per
test run had a small but nonzero chance of colliding on the unique
ticker+exchange index. hex(8) makes collisions practically impossible.
2026-07-14 03:16:28 +02:00
Guillem Arias Fauste 635938ec7b fix(ds): normalize legacy tooltip spacing to one recipe (#2311)
The four inline (non-DS::Tooltip) tooltips had drifted: three used
p-2 rounded w-64, one used p-3 rounded w-72 with shadow-lg, and all used
rounded (4px) where DS::Tooltip uses rounded-md (6px). Unify them on
p-2 rounded-md w-64 — radius now matches DS::Tooltip and the lone
p-3/w-72/shadow-lg outlier is gone, so the dark tooltips read
consistently.
2026-06-14 22:04:11 +02:00
Guillem Arias Fauste 77dda53ffb feat(ds): one canonical focus ring across primitives (#2140)
* 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.
2026-06-11 16:05:13 +02:00
Guillem Arias Fauste d29591cff9 fix(ds): unify tab/chip controls on DS::SegmentedControl (#8) (#2284)
* fix(ds): goals status filter -> DS::SegmentedControl

The goals status chips were a hand-rolled segmented control: active state
toggled via ad-hoc bg-container/shadow-border-xs/text-* classes, NO hover on
inactive chips, and a light-only focus ring (ring-alpha-black-100, invisible in
dark mode). Migrate to DS::SegmentedControl button segments + toggle the
canonical --active class in goals_filter_controller#syncChipState. Gains the
dark-safe hover (hover:bg-gray-200 / theme-dark:hover:bg-gray-800) and the
canonical focus ring. First control in the tab-consistency pass (#8).

* fix(ds): provider filter chips -> DS::SegmentedControl

Same hand-rolled segmented control as the goals chips: active toggled via
ad-hoc bg-container/shadow/text classes, no inactive hover, light-only
ring-alpha-black-100 focus ring. Migrate to DS::SegmentedControl + toggle the
canonical --active class in providers_filter_controller#syncChipState.

* fix(ds): transaction-type tabs -> DS::SegmentedControl

Expense/Income/Transfer tabs were hand-rolled with a hover==active affordance
bug: inactive hover raised bg to bg-container, identical to the active state, so
hover and selected were indistinguishable. Migrate to DS::SegmentedControl (link
segments + icons); the controller toggles the canonical --active class instead
of swapping ad-hoc ACTIVE/INACTIVE class lists. The client-side nature switch
(expense<->income updates the form's hidden nature field without navigating) is
preserved -- verified live: switching to Income flips the active segment AND
sets the nature field to inflow.

* fix(ds): reports period tabs -> DS::SegmentedControl

The Monthly/Quarterly/YTD/Last-6-Months/Custom period selector was five
DS::Link ghost/secondary buttons -- a different tab idiom than the rest of the
app. Migrate to DS::SegmentedControl link segments (server-rendered active via
aria-current; pure navigation, no controller). Now matches the goals / provider /
transaction-type controls + the AI provider picker. Also autocorrected a
pre-existing single-quote in the next-period aria-label.
2026-06-11 15:47:00 +02:00
Guillem Arias Fauste a461ff97bb fix(sync-toast): morph refresh, defer behind modals, DS conformance (#2105)
* fix(sync-toast): morph refresh, defer behind modals, DS conformance

Follow-up to #1964 (addresses #2071).

- Refresh via Turbo morph visit instead of window.location.reload, so
  scroll position and data-turbo-permanent elements (the AI chat panel)
  survive and there is no white flash.
- Defer the toast while a <dialog> is open and reveal it on close. A
  refresh mid-modal closes the dialog and discards its in-progress input,
  which is the exact data loss this toast exists to prevent. Handles
  stacked modals.
- Refresh CTA and close button now use DS::Button (secondary / icon). The
  close is always visible, inside the card, focusable, and has an
  aria-label; the old hover-only corner chip was unreachable on touch and
  not keyboard-focusable.
- Add role="status" / aria-live="polite" to the toast.
- Fix icon color: "inverse" is not a key in the icon helper color map, so
  it silently rendered no color class (dark icon on bg-info). Use "white",
  which maps to the functional text-inverse token.
- Tighten copy: "New data available" / "Refresh".
- Sync the broadcast comment with the actual replace/morph behavior.

* fix(sync-toast): detach deferred dialog listener on disconnect

A toast replaced by a newer broadcast_replace_to while a <dialog> was open kept
its 'close' listener attached, so the detached controller fired #reveal()/#arm()
when the dialog closed — a spurious auto-refresh from a stale toast (and repeated
syncs could queue several). Store the dialog + handler refs and remove the
listener in disconnect(). Flagged by codex + coderabbit on #2105.

* fix(sync-toast): re-check interaction and dialogs at refresh-fire time

The interaction check ran once at arm time but the refresh fired two
seconds later. The post-dialog reveal made that window matter: the user
closes a dialog sitting on a form, resumes typing, and the timer morphs
the page — wiping non-turbo-permanent input, the exact data-loss class
this toast exists to prevent. A dialog opened during the window had the
mirror problem (the refresh would close it).

Bail inside the callback instead, leaving the toast visible for a
manual refresh, matching the mid-form behavior. Also documents the
dialog-removed-without-close edge on the deferred listener.
2026-06-06 17:02:26 +02:00
Abhinav Dhiman cc070853b7 fix: Replace platform-wide broadcast_refresh with sync toast (#1964)
* fix: Replace platform-wide broadcast_refresh with sync toast

Instead of calling family.broadcast_refresh on every sync completion
(which reloads the page for all connected family members), broadcast
a lightweight static toast to the existing notification-tray.

A new sync-toast Stimulus controller handles two cases:
- User is idle (no focused form): auto-reloads after 500ms
- User is mid-form: toast stays visible with a manual Refresh button

This prevents in-progress form state from being wiped when a background
sync fires (e.g. adding a transaction, filling an import form).

The toast partial contains no user-scoped data, so the Current.user nil
constraint in background jobs is no longer a concern.

* fix(a11y): add explicit button types and aria-label to sync toast controls

* fix(sync-toast): improve interaction detection and replace broadcast strategy

- Increase auto-refresh delay from 500ms to 2000ms
- Expand interaction detection to include contentEditable, dialogs, and role="dialog" elements
- Switch from broadcast_append_to to broadcast_replace_to with dedicated #sync-toast target
- Add explicit id="sync-toast" to partial for targeted replacement
- Move sync_toast i18n keys from defaults/en.yml to views/shared/en.yml

* fix(sync-toast): replace hardcoded white icon color with inverse token
2026-05-31 16:05:54 +02:00
Guillem Arias Fauste e15349d57e refactor(misc): migrate misc badges to DS::Pill (#1751 PR D) (#1919)
* refactor(transactions): migrate 5 transaction badges to DS::Pill (#1751 PR B)

Migrates the hand-rolled "Pending" / "Review recommended" / "Potential
duplicate" / "Split" badges across the transaction views to the
extended DS::Pill primitive from #1902.

**Visual contract for badge mode**

In #1902 the badge mode (`marker: false`) used `rounded-md` (chip shape)
because the marker mode does. But every existing pill / status badge
in the codebase uses `rounded-full` — see
`settings/providers/_status_pill.html.erb`,
`settings/providers/_maturity_badge.html.erb`, and the inline
transaction badges this PR is migrating. To keep the visual contract
consistent, this PR shifts `DS::Pill`'s badge mode to `rounded-full`
(marker mode stays `rounded-md`, unchanged from #1829). The shape
distinction now reads: markers are tags, badges are pills.

**Callsites migrated** (5):

- `app/views/transactions/_transaction.html.erb` — Pending,
  Review-recommended, Possible-duplicate, Split badges
- `app/views/transactions/_header.html.erb` — Pending badge
- `app/views/transactions/_split_parent_row.html.erb` — Split badge

**Tone mapping**

| Badge | Tone | Notes |
|---|---|---|
| Pending | `:neutral` | unchanged copy/icon, gains subtle DS-controlled bg |
| Review recommended | `:neutral` | matches existing `bg-surface-inset` look |
| Possible duplicate | `:warning` | DS semantic alias for the existing `text-warning` |
| Split | `:neutral` | matches existing `bg-surface-inset` look |

**Deferred to follow-up PRs**

- `app/views/transactions/_transfer_match.html.erb` — uses two
  responsive-visibility variants (`hidden lg:inline-flex` for long
  copy, `inline-flex lg:hidden` for short). DS::Pill currently has no
  `class:` arg for caller-controlled wrapper classes; deferring until
  that lands.
- `app/views/transactions/searches/filters/_badge.html.erb` — has a
  close button alongside the label (`button_to clear_filter_*`) and
  uses `rounded-3xl p-1.5` instead of a true pill. Closer to a
  removable filter chip — better fit for a separate `DS::FilterChip`
  primitive than for `DS::Pill`.

Refs #1751.

* refactor(misc): migrate misc badges to DS::Pill (#1751 PR D)

Replaces five misc badge callsites with `DS::Pill` (badge mode:
`marker: false`, `show_dot: false`) so the long-tail badges share the
same shape, padding, and dark-mode tokens as the rest of the design
system. No raw palette classes remain in the migrated files.

Migrated:
- app/views/shared/_badge.html.erb — converted to a thin shim that
  renders `DS::Pill`; preserves the block-content API and the
  `pulse: true` option (wraps the pill in `animate-pulse`). Maps
  `success`/`error`/`warning`/default → `:success`/`:error`/`:warning`/`:neutral`.
- app/views/accounts/_tax_treatment_badge.html.erb — maps tax
  treatments to DS tones: `:tax_exempt → :green`,
  `:tax_deferred → :indigo` (was raw blue-500/10),
  `:tax_advantaged → :violet` (was raw purple-500/10), default → `:neutral`.
- app/views/reports/_investment_performance.html.erb (line ~121,
  inline twin of the tax-treatment badge) — uses the same mapping via
  a new `tax_treatment_pill_tone` helper.
- app/helpers/reports_helper.rb — replaces `tax_treatment_badge_classes`
  with `tax_treatment_pill_tone` (the old helper had no other callers).
- app/views/import/qif_category_selections/show.html.erb (~line 86) —
  inline split badge → `tone: :warning`.
- app/views/investment_activity/_badge.html.erb — fixed activity
  enum mapped to DS tones: Buy/Reinvestment → :indigo,
  Sell → :red, Dividend/Interest → :green, Contribution → :violet,
  Withdrawal → :amber, others → :gray.

Skipped (true mismatches, not extendable without changing DS::Pill):
- app/views/shared/_color_badge.html.erb — takes an arbitrary
  user-supplied color via `color-mix(in oklab, #{color} ...)`. DS::Pill
  only supports the fixed tone enum, so this would lose information.
- app/views/categories/_badge.html.erb — same reason; renders
  `category.color` (arbitrary hex per record).
- app/views/investment_activity/_quick_edit_badge.html.erb — interactive
  button with a Stimulus controller, click action, hover state, and
  dropdown anchor. DS::Pill renders a `<span>`; converting would
  destroy the interactive surface.

Stack: based on `feat/ds-pill-transactions-1751-b` (PR #1917), which
ships the `marker: false` → `rounded-full` badge shape this PR depends on.

Refs #1751.
2026-05-23 08:55:39 +02:00
Guillem Arias Fauste 0d32bb70ec chore(design-system): swap raw gray classes for semantic tokens across remaining views (#1655)
* chore(design-system): swap raw gray classes for semantic tokens across remaining views

Finalizes the raw-color sweep started in #1652 (settings) and continued
in #1654 (holdings). Covers accounts, budgets, chats, pages, imports,
provider integrations (mercury, lunchflow, sophtron, enable_banking,
coinstats), auth flows (password reset, MFA, registrations), shared
layouts, and selected DS component hover states. 35 files, ~56 line
changes.

Mappings (matching the patterns established in the prior sweeps):

- text-white bg-gray-900 hover:bg-gray-800 (with optional focus:ring-gray-900)
  -> text-inverse button-bg-primary hover:button-bg-primary-hover
  -> focus:ring-button-bg-primary
- text-gray-500 / 600 / 700 -> text-secondary
- text-gray-800 -> text-primary
- text-gray-400 -> text-subdued
- hover:text-gray-700 / hover:text-gray-100 -> hover:text-primary
- bg-gray-50 / 100 / 200 (standalone) -> bg-surface-inset
- bg-gray-500/5 -> bg-gray-tint-5
- bg-gray-500/10 -> bg-gray-tint-10
- bg-gray-900 (decorative active states) -> bg-inverse
- hover:bg-gray-50 / 100 (standalone) -> hover:bg-surface-inset
- hover:bg-gray-300 -> hover:bg-surface-inset-hover
- bg-white hover:bg-gray-100 -> bg-container hover:bg-container-hover
- border-gray-300 -> border-secondary
- focus:border-gray-200 -> focus:border-secondary
- focus-within:border-gray-900 -> focus-within:border-primary
- DS::Buttonish outline / ghost / icon hover:
  hover:bg-gray-100 theme-dark:hover:bg-gray-700
  -> hover:bg-container-inset-hover

Left intentionally raw, with rationale:

- bg-gray-300 / bg-gray-400 decorative dots and avatar circles. The
  raw value reads OK against both bg-container variants; no semantic
  "neutral indicator" token exists. Same pattern as #1652 / #1654.
- bg-gray-400/20 theme-dark:bg-gray-500/20 (onboardings/trial). Custom
  alpha tint with no equivalent token.
- bg-white theme-dark:bg-gray-700 (DS::Tabs active pill, budgets tabs).
  Custom tab-pill pattern; gray-700 in dark mode (one shade lighter
  than page bg-gray-900) is intentional for visibility.
- bg-gray-100 theme-dark:bg-gray-700 (DS::Toggle base bg). Closest
  match (bg-container-inset-hover) is semantically a hover state.
- DS::Buttonish secondary variant gray-200/300/700/600 pattern. Same
  pattern as #1654 holdings; needs button-bg-secondary-strong from
  that PR. Will swap in a follow-up after #1654 merges.
- disabled:bg-gray-500 theme-dark:disabled:bg-gray-400 on inverse
  buttons (DS::Buttonish primary, enable_banking, coinstats). Custom
  disabled state for the inverse pair; no token.
- text-gray-300 SVG stroke (shared/_progress_circle).
- bg-white text-gray-900 (layouts/print). Print contexts intentionally
  light regardless of theme.
- bg-gray-800 / border-gray-700 / text-white / hover:text-gray-100
  (impersonation_sessions/_super_admin_bar). Admin overlay styled to
  remain dark in both modes; not a theme-aware component.

Files covered by other in-flight PRs were skipped to avoid rebase
conflicts: chats/_ai_consent's fg-inverse swap (#1626), shared/_text_tooltip
and shared/_money_field tooltip pills (#1626), investments/_value_tooltip
(#1626), components/DS/tooltip (#1626).

* fix(design-system): keep changelog avatar text raw to preserve dark-mode contrast

The changelog avatar fallback (when @release_notes[:avatar] is missing)
sits inside the "decorative + raw" exception list — bg-gray-300 stays
fixed across themes since no semantic neutral-indicator token exists.

The earlier sweep partially themed the pair: bg-gray-300 stayed raw but
text-gray-600 became text-secondary. text-secondary resolves to gray-300
in dark mode, which matches the bg → text became invisible against its
own background.

Reverting only the text class to text-gray-600 restores the original
fixed-light placeholder behavior. Both classes raw, both themes
readable.

* fix(design-system): address review feedback on raw-color-sweep-finalize

Six issues caught by CodeRabbit + Codex review:

1. focus:ring-button-bg-primary silently emits no CSS (×6 files).
   button-bg-primary is a custom @utility, not a theme color, so Tailwind's
   ring-{name} resolution finds no --color-button-bg-primary. Replaces with
   focus:ring-gray-900 theme-dark:focus:ring-white — same color flip as the
   button bg, but resolved through theme colors so the ring actually renders.
   Files: lunchflow/mercury/sophtron _api_error + _setup_required, coinstats_items/new.

2. accounts/show/_activity.html.erb: focus-within:ring-gray-100 was dead
   (no ring-width on the parent). Removed.

3. import/confirms/show.html.erb: uniform hover:bg-surface-inset-hover
   applied to both active and inactive step indicators created a jarring
   dark-to-light flip on the active step (bg-inverse → bg-surface-inset-hover).
   Now hover follows the resting state: active uses hover:bg-inverse-hover,
   inactive uses hover:bg-surface-inset-hover.

4. password_resets/new.html.erb: bg-white left raw alongside the migrated
   hover:bg-surface-inset. Swapped to bg-container so dark mode flips properly.

5. registrations/new.html.erb + password_validator_controller.js: view now
   uses bg-surface-inset on password strength block lines, but the Stimulus
   controller still toggled bg-gray-200 on validate. Updated controller to
   add/remove bg-surface-inset matching the view, so unmet states reset to
   the tokenized class instead of leaving raw gray-200 stuck on the element.
2026-05-04 21:47:01 +02:00
Guillem Arias Fauste 0fe1e06645 refactor(design-system): migrate fg-* utilities to text-* and remove namespace (#1626)
* refactor(design-system): migrate fg-* utilities to text-* and remove namespace

The design system carried two parallel namespaces for foreground colors:
text-* (canonical, ~2,000 uses) and fg-* (32 uses). Most fg-* tokens
were 1:1 duplicates of a text-* counterpart. fg-gray was nearly
identical to text-secondary, with a one-step shade difference in dark
mode.

This PR migrates all 32 usages to their text-* equivalents and removes
the fg-* block from the design tokens. Closes #1606.

Mapping:
- fg-inverse  -> text-inverse  (20 usages, identical light/dark values)
- fg-gray     -> text-secondary (7 usages; light values match, dark is
                                 one step lighter: gray-300 vs gray-400)
- fg-primary  -> text-primary  (3 usages, identical values)
- fg-subdued  -> text-subdued  (2 usages, identical values)

The four other fg-* tokens (fg-contrast, fg-primary-variant,
fg-secondary, fg-secondary-variant) had zero usages despite being
defined; they are removed without replacement.

JSON / build:
- design/tokens/sure.tokens.json: $version 1.0.0 -> 2.0.0 (breaking
  schema change per the policy added in #1620). 8 fg-* token
  definitions removed.
- button-bg-ghost-hover's dark value still references "fg-inverse"
  internally; rewritten to "bg-gray-800 text-inverse" so the cleanup
  doesn't break that utility.
- _generated.css regenerated. 42 utility blocks now (was 50).

Lookbook tokens preview:
- The Text & foregrounds section dropped its split between text-*
  (canonical) and fg-* (legacy). Now a single section listing the
  five text-* utilities. The "(legacy)" framing is gone since there's
  no legacy left.

README:
- design/tokens/README.md's button-bg-ghost-hover edge-case example
  updated to reflect the new "bg-gray-800 text-inverse" dark value.

Visual review needed in dark mode:
- Anywhere icons use the application_helper#icon helper with
  color: "default" (most icons in the app). The default class moved
  from fg-gray (gray-400 dark) to text-secondary (gray-300 dark), so
  default-color icons render slightly lighter in dark mode.
- DS::Buttonish icons in secondary buttons (same shade shift).
- DS::Link icons (same).
- Time series chart axes (same).
- All tooltips, account add flow, settings hostings buttons,
  invitations, AI consent, family export, danger-zone buttons --
  these used fg-inverse, which is identical to text-inverse, so no
  visual change expected.

* fix(design-system): use inverse pair on tooltips for readable dark mode

* fix(lookbook): use semantic tokens in menu preview header text

* fix(lookbook): set text-primary on layout body so previews inherit theme

* fix(design-system): keep shadows dark-toned in dark mode

Inverting shadows to white|8% on dark surfaces produces a halo
effect rather than an elevation cue, and stacks redundantly with
the alpha-white 1px ring already in shadow-border-*.

Switch dark-mode shadows to black at progressively higher alpha
(25%/30%/35%/40%/50% for xs..xl) so they read as actual cast
shadows on near-black surfaces. Surface-tint differences and the
existing alpha-white border ring continue to handle elevation
hierarchy and edge definition.

Approach matches Material 3, Apple HIG, IBM Carbon, Refactoring UI,
and the dark-mode shadows used in Linear/Vercel/Stripe.

* fix(design-system): set text-primary on DS::Dialog element

Browser UA stylesheets apply color: black directly to <dialog>,
which overrides ancestor inheritance even when a body or html
ancestor sets a theme-aware color. Unstyled child content then
renders black regardless of theme.

Setting text-primary on the dialog element itself defeats the UA
override and lets descendants inherit the semantic token.

* fix(lookbook): use shadow css vars in effects preview so dark theme renders

* Revert "fix(design-system): keep shadows dark-toned in dark mode"

This reverts commit 3e9d76ed0beb5ac5f2acbad61e4d1c39eadc9ac2.

* fix(design-system): use opacity-70 instead of text-inverse/70 in value tooltip

The custom @utility text-inverse expands to @apply text-white and
isn't modifier-aware, so text-inverse/70 produced no CSS at all and
the muted labels fell through to inherited color (invisible on the
white pill in dark mode).

Replace with text-inverse + opacity-70. Same visual effect, works
with the existing utility definition.
2026-05-04 00:50:52 +02:00
Ang Wei Feng (Ted)andJuan José Mata 60929cdee0 feat: add currency management for families with enabled currencies (#1419)
* feat: add currency management for families with enabled currencies

* feat: update currency selection logic and improve accessibility

* feat: update currency preferences to use group moniker in titles

---------

Signed-off-by: Ang Wei Feng (Ted) <hello@tedawf.com>
Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-04-13 19:53:04 +02:00
Roger Saner 88747e5716 feat(transaction): tabbing between income and expenses persists entered data (#1422) 2026-04-11 00:56:05 +02:00
soky srm 7908f7d8a4 Expand financial providers (#1407)
* Initial implementation

* Tiingo fixes

* Adds 2 providers, remove 2

* Add  extra checks

* FIX a big hotwire race condition

// Fix hotwire_combobox race condition: when typing quickly, a slow response for
// an early query (e.g. "A") can overwrite the correct results for the final query
// (e.g. "AAPL"). We abort the previous in-flight request whenever a new one fires,
// so stale Turbo Stream responses never reach the DOM.

* pipelock

* Update price_test.rb

* Reviews

* i8n

* fixes

* fixes

* Update tiingo.rb

* fixes

* Improvements

* Big revamp

* optimisations

* Update 20260408151837_add_offline_reason_to_securities.rb

* Add missing tests, fixes

* small rank tests

* FIX tests

* Update show.html.erb

* Update resolver.rb

* Update usd_converter.rb

* Update holdings_controller.rb

* Update holdings_controller.rb

* Update holdings_controller.rb

* Update holdings_controller.rb

* Update holdings_controller.rb

* Update _yahoo_finance_settings.html.erb
2026-04-09 18:33:59 +02:00
Pedro J. AramburuandPedro J. Aramburu f699660479 Add exchange rate feature with multi-currency transactions and transfers support (#1099)
Co-authored-by: Pedro J. Aramburu <pedro@joakin.dev>
2026-04-08 21:05:58 +02:00
Louis cc8d6ca2a0 fix: allow high precision for security prices in trade forms (to solve #1323) (#1342)
* fix: allow high precision for security prices in trade forms (to solve #1323)

* fix: prevent race conditions on currency selection in money field

* fix: silently ignore currency fetch errors in money field
2026-04-07 12:19:17 +02:00
Yacine Kanzariandsokiee b78944ed6f Fix selected account and Transaction/Transfer Tabs changes (#1220)
* Fix Dialog selected attribute had no effect

* Fix New Transaction/Transfer Dialog

* Update transfers_controller.rb

---------

Co-authored-by: sokiee <sokysrm@gmail.com>
2026-03-24 20:36:03 +01:00
Chakib 583d91291f Add privacy mode with blur toggle for financial data
- Stimulus controller on global layout (persists across all pages)
- CSS blur effect on .privacy-sensitive elements (8px active, 4px on hover)
- localStorage persistence survives page navigations
- Toggle button on dashboard with aria-pressed for accessibility
- Applied to 28+ views: accounts, budgets, transactions, reports, etc.

Fixes: UTF-8 encoding, global controller mount, aria-pressed attribute
2026-02-22 23:20:49 +01:00
Alessio Cappa 13c2335a6a feat: New tag creation UI (#1014)
* feat: Update tag creation UI

* fix: remove unused target

* fix: remove connect/disconnect functions

* fix: remove unnecessary target
2026-02-19 19:55:10 +01:00
StalkerSea a86329d632 PWA: Update manifest, meta tags, and fix UI layout issues (#801)
* pwa(cleanup): enforce LF, head meta + icons, manifest orientation, remove static webmanifest

* pwa(cleanup): add gitattributes, head meta/icons, manifest orientation; remove static manifest; small nav & dashboard fixes

* pwa(cleanup): improve transaction drawer header layout with inline close button

* fix: address PR review feedback

- Add dom_id to transaction header for Turbo Stream updates (Codex)
- Add pending badge next to date when transaction is pending (CodeRabbit)
- Make close button keyboard-focusable by removing tabindex=-1 (CodeRabbit)
- Fix settings nav horizontal scroll with flex-nowrap space-x-1 (CodeRabbit)

* fix: localize 'Linked with Plaid' tooltip string (CodeRabbit)

* Update .gitattributes

Better comment smh

* fix: align transaction/transfer dialog icons and update transfer drawer pattern

- Fix icon alignment in transaction header (items-center instead of items-start)
- Make transfer/linked icons consistent size and color
- Update transfers/show.html.erb to use frame: drawer with hide_close_icon pattern
- Match transfer dialog header layout with transaction details

* fix: enhance header layout

This in the transaction and transfer views, with consistent icon placement

* fix: remove fixed height from HTML document class

basically a regression issue pretty sure

* fix: update dialog rendering to use 'frame' and hide close icon in headers

* fix: update transaction type tabs layout for improved responsiveness

* fix: conditionally render transaction type tabs based on account type
2026-01-29 15:16:49 +01:00
Juan José Mata f6c38344cd Update PostHog script configuration settings
Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
2026-01-28 00:46:06 +01:00
soky srmandJuan José Mata 179552657c Mercury integration (#723)
* Initial mercury impl

* FIX both mercury and generator class

* Finish mercury integration and provider generator

* Fix schema

* Fix linter and tags

* Update routes.rb

* Avoid schema drift

---------

Signed-off-by: soky srm <sokysrm@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-01-22 20:37:07 +01:00
Nuno Marques f8860c091f feat: created Badge component 2026-01-20 23:20:10 +00:00
Nuno Marques 388e1759a9 fix: pagination background 2026-01-20 23:19:34 +00:00
777fbdc4ca feat(settings): add pagination to imports and exports pages (#598)
* feat(settings): split imports and exports

* feat(security): sanitize pagination params to prevent abuse

* fix(settings): fix syntax in settings nav

* feat(settings): internationalize family_exports and imports UI strings

* fix(settings): fix coderabbit review

* fix(settings): fix coderabbit review

* fix(settings): fix coderabbit review

* Change default per_page value from 20 to 10

Signed-off-by: Juan José Mata <jjmata@jjmata.com>

* Add `/family_export` to navigation

* Consistency with old defaults

* Align `safe_per_page` even if not DRY

---------

Signed-off-by: Julien Orain <julien.orain@gmail.com>
Signed-off-by: Juan José Mata <jjmata@jjmata.com>
Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: JulienOrain <your-github-email@example.com>
Co-authored-by: Juan José Mata <jjmata@jjmata.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-01-20 00:11:22 +01:00
soky srm ca4fb7995c Implement holdings for lunch flow (#590)
* Implement holdings for lunch flow

* Implement holdings function call
2026-01-09 13:14:14 +01:00
Alessio Cappacoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>Juan José Mata
b3af8bf1ae Transactions & Activities pages improvements (#452)
* 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>
2025-12-24 01:57:16 +01:00
Juan José Mata 94e87a8b85 Demo warning in /chat UI (#466)
* Add demo warning to /chat

* Missed two files!

* Function calling works now, update message
2025-12-19 16:30:21 +01:00
soky srmandJuan José Mata 4a29d030af Initial enable banking implementation (#382)
* Initial enable banking implementation

* Handle multiple connections

* Amount fixes

* Account type mapping

* Add option to skip accounts

* Update schema.rb

* Transaction fixes

* Provider fixes

* FIX account identifier

* FIX support unlinking

* UI style fixes

* FIX safe redirect and brakeman issue

* FIX

- pagination max fix
- wrap crud in transaction logic

* FIX api uid access

- The Enable Banking API expects the UUID (uid from the API response) to fetch balances/transactions, not the identification_hash

* FIX add new connection

* FIX erb code

* Alert/notice box overflow protection

* Give alert/notification boxes room to grow (3 lines max)

* Add "Enable Banking (beta)" to `/settings/bank_sync`

* Make Enable Banking section collapsible like all others

* Add callback hint to error message

---------

Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2025-11-29 13:31:08 +01:00
Juan José Mata fcf792b560 PostHog support (#313) 2025-11-11 16:06:08 +01:00
Michael Studman ea7ce13a7d Increasing trades.price decimal scale (#89)
* Changing trades.price to have a larger scale - a scale of 4 causes destructive rounding when calculating transaction cost; changes to the UI to allow for inputting and showing increased scale trade prices; test case
2025-10-21 18:22:24 +02:00
Juan José Mata 5706280dd7 More rebranding changes (#159)
* Replace Maybe for Sure in select code areas

* Make sure passwords are consistent

* Remove (admin|member) from demo data first name

* Database and schema names finally to `sure`

* Fix broken test

* Another (benchmarking) database name to `sure_*`

* More rebranding to Sure

* Missed this Maybe mention in the same page

* Random nitpicks and more Maybes

* Demo data accounts and more Maybes

* Test data account updates

* Impersonation test accounts

* Consistency with `compose.example.yml`
2025-09-24 00:19:51 +02:00
Zach Gollwitzer 662f2c04ce Multi-step account forms + clearer balance editing (#2427)
* Initial multi-step property form

* Improve form structure, add optional tooltip help icons to form fields

* Add basic inline alert component

* Clean up and improve property form lifecycle

* Implement Account status concept

* Lint fixes

* Remove whitespace

* Balance editing, scope updates for account

* Passing tests

* Fix brakeman warning

* Remove stale columns

* data constraint tweaks

* Redundant property
2025-07-03 09:33:07 -04:00
Eran AvidorandEran Avidor ba7e8d3893 Fix/design system violations (#2422)
* fix: replace hardcoded bg-white with bg-container in notification notice

* fix: replace hardcoded text-white with fg-inverse in notification CTA

* fix: replace hardcoded text-white with fg-inverse in text tooltip

* fix: replace hardcoded bg-gray-900 text-white with bg-inverse fg-inverse in invitations form

* fix: replace hardcoded bg-gray-800 text-white with bg-inverse fg-inverse in AI consent form

* fix: replace hardcoded text-white with fg-inverse in changelog page

* fix: replace hardcoded text-white and border-gray-500 with fg-inverse and border-secondary in investment tooltip

* fix: replace hardcoded text-white with fg-inverse in holdings missing price tooltip

* fix: replace hardcoded text-white and bg-gray-400 with fg-inverse and bg-surface-inset in settings profiles

* fix: replace hardcoded bg-orange-500 text-white with bg-yellow-600 fg-inverse in settings hosting danger zone

---------

Co-authored-by: Eran Avidor <eavidor@Eran-Avidor-MBP.lan>
2025-07-01 13:53:36 -04:00
Zach Gollwitzer 4b50acff2b Replace sync spinners with pulse animation (#2413)
* Replace sync spinners with pulse animation

* Remove dev code
2025-06-25 16:51:30 -04:00
Zach Gollwitzer 10ce2c8e23 Balance sheet cache layer, non-blocking sync UI (#2356)
* Balance sheet cache layer with cache-busting

* Update family cache timestamps during Sync

* Less blocking sync loaders

* Consolidate family data caching key logic

* Fix turbo stream broadcasts

* Remove dev delay

* Add back account group sorting
2025-06-10 18:20:06 -04:00
Zach Gollwitzer aecb5aafd8 Pass transactions cursor when fetching plaid transactions 2025-05-24 17:41:14 -04:00
Zach Gollwitzer 03a146222d Plaid sync domain improvements (#2267)
Breaks our Plaid sync process out into more manageable classes. Notably, this moves the sync process to a distinct, 2-step flow:

1. Import stage - we first make API calls and import Plaid data to "mirror" tables
2. Processing stage - read the raw data, apply business rules, build internal domain models and sync balances

This provides several benefits:

- Plaid syncs can now be "replayed" without fetching API data again
- Mirror tables provide better audit and debugging capabilities
- Eliminates the "all or nothing" sync behavior that is currently in place, which is brittle
2025-05-23 18:58:22 -04:00
Zach Gollwitzer e4ee06c9f6 Security resolver and health checker (#2281)
* Setup health check

* Security health checker cron

* Use resolver throughout codebase

* Use resolver for trade builder

* Add security health checks to schedule

* Handle no provider

* Lint fixes
2025-05-22 12:43:24 -04:00
Alex Hatzenbuhler 443b834b46 Create shared ruler and standardize across views (#2240)
* Create shared ruler view

* Use collection rendering/spacer templates for rules, and new shared_ruler

* Use shared ruler for all the places a ruler is used

* Use shared ruler for imports and balance sheet

* Fix brakeman by using a static partial with a defined collection

* Update balance sheet
2025-05-20 15:13:18 -04:00
Zach Gollwitzer 10dd9e061a Improve account sync performance, handle concurrent market data syncing (#2236)
* PlaidConnectable concern

* Remove bad abstraction

* Put sync implementations in own concerns

* Sync strategies

* Move sync orchestration to Sync class

* Clean up sync class, add state machine

* Basic market data sync cron

* Fix price sync

* Improve sync window column names, add timestamps

* 30 day syncs by default

* Clean up market data methods

* Report high duplicate sync counts to Sentry

* Add sync states throughout app

* account tab session

* Persistent account tab selections

* Remove manual sleep

* Add migration to clear stale syncs on self hosted apps

* Tweak sync states

* Sync completion event broadcasts

* Fix timezones in tests

* Cleanup

* More cleanup

* Plaid item UI broadcasts for sync

* Fix account ID namespace conflict

* Sync broadcasters

* Smoother account sync refreshes

* Remove test sync delay
2025-05-15 10:19:56 -04:00
Zach Gollwitzer a67f36bf64 Prevent account deletions when account is linked to a Plaid Item (#2218)
* Prevent account deletions when account is linked to a Plaid Item

* Only guard deletions in UI and controller, not at model level
2025-05-07 13:56:20 -04:00
Zach Gollwitzer 752835f492 Fix transaction form account pre-filling and form styles 2025-05-02 08:45:17 -04:00
Zach Gollwitzer a51c4d2cba New onboarding, trials, Stripe integration (#2185)
* New onboarding, trials, Stripe integration

* Fix tests

* Lint fixes

* Fix subscription endpoints
2025-05-01 16:47:14 -04:00
Josh Pigford 9a291edbc8 Fix loading notification rendering and update loading component background class
Fixes #2141
2025-05-01 09:38:51 -05:00
Zach Gollwitzer 90a9546f32 Pre-launch design sync with Figma spec (#2154)
* 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
2025-04-30 18:14:22 -04:00
Alex Hatzenbuhler 341a800b65 Improve dashboard/account charts and tooltips (#2157)
* Improve net worth hover

* Improve graph tooltip

* Use locales files for some text on net worth and account charts

* Consolidate and simplify trend change between net worth and account charts

* Fix test and self-review stuff

* Clean up some stuff on the holding sidebar
2025-04-28 14:57:32 -04:00
Diego Gasparis EscobedoandZach Gollwitzer 71bc51ca15 Fix: Filter categories by transaction type in forms (#2082)
Changed transaction form to only display relevant categories based on transaction type (income or expense), improving usability and preventing misclassification. Created a shared transaction type tabs component for consistent navigation between expense, income, and transfer forms, providing a better user experience and reducing code duplication.

Signed-off-by: Zach Gollwitzer <zach@maybe.co>
Co-authored-by: Zach Gollwitzer <zach@maybe.co>
2025-04-25 10:18:10 -04:00
Guilherme Mena 210b89cd17 Improve dark mode styles across multiple pages (#2125)
* fix: improve dark mode readability across the app

* fix: improve dark mode support for asset percentage text

* fix: apply suggested patch for theme-related improvements

* chore: apply PR feedback – remove dark:, align with design tokens, update form builder

* chore: revert background token and restore original style for visual consistency

* chore: remove unnecessary class attributes from form fields using builder

* refactor: move number_field and date_field into metaprogramming block

* refactor: replace bg-divider-adaptive divs with <hr> and border-secondary

* fix: apply requested changes and linting fixes
2025-04-23 09:42:30 -04:00
neo773 04ee1e73be Fix dark mode support and improve notification/MFA UI (#2126)
* Fix dark mode support and improve notification/MFA UI"

* hide auth tabs on mfa/verify
2025-04-18 13:56:44 -05:00
Zach Gollwitzer 297a695d0f Transaction rules engine V1 (#1900)
* Domain model sketch

* Scaffold out rules domain

* Migrations

* Remove existing data enrichment for clean slate

* Sketch out business logic and basic tests

* Simplify rule scope building and action executions

* Get generator working again

* Basic implementation + tests

* Remove manual merchant management (rules will replace)

* Revert "Remove manual merchant management (rules will replace)"

This reverts commit 83dcbd9ff0aa7bbee211796b71aa48b71df5e57e.

* Family and Provider merchants model

* Fix brakeman warnings

* Fix notification loader

* Update notification position

* Add Rule action and condition registries

* Rule form with compound conditions and tests

* Split out notification types, add CTA type

* Rules form builder and Stimulus controller

* Clean up rule registry domain

* Clean up rules stimulus controller

* CTA message for rule when user changes transaction category

* Fix tests

* Lint updates

* Centralize notifications in Notifiable concern

* Implement category rule prompts with auto backoff and option to disable

* Fix layout bug caused by merge conflict

* Initialize rule with correct action for category CTA

* Add rule deletions, get rules working

* Complete dynamic rule form, split Stimulus controllers by resource

* Fix failing tests

* Change test password to avoid chromium conflicts

* Update integration tests

* Centralize all test password references

* Add re-apply rule action

* Rule confirm modal

* Run migrations

* Trigger rule notification after inline category updates

* Clean up rule styles

* Basic attribute locking for rules

* Apply attribute locks on user edits

* Log data enrichments, only apply rules to unlocked attributes

* Fix merge errors

* Additional merge conflict fixes

* Form UI improvements, ignore attribute locks on manual rule application

* Batch AI auto-categorization of transactions

* Auto merchant detection, ai enrichment in batches

* Fix Plaid merchant assignments

* Plaid category matching

* Cleanup 1

* Test cleanup

* Remove stale route

* Fix desktop chat UI issues

* Fix mobile nav styling issues
2025-04-18 11:39:58 -04:00