Commit Graph

140 Commits

Author SHA1 Message Date
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)
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. 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 Kanzari
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 srm
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
Julien Orain
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 Cappa
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 srm
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 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 Escobedo
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
neo773
65e1bc6edd Feature: Implement Mobile Responsiveness (#2092)
* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* format

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* fix conflict

* fix conflict

* chore: run rubocop

* fix test

* update PWA logo

* fix tests

* chore: lint

* fix test

* Refactor: Remove duplicate data attribute in activity partial and add chat form rendering in chats index

---------

Co-authored-by: Josh Pigford <josh@joshpigford.com>
2025-04-18 08:23:10 -05:00
Josh Pigford
88a6373e84 Implement dark mode (#2078)
* User theme settings

* Initial rough pass on colors

* More progress on dark mode
2025-04-11 09:28:00 -05:00