diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9ab5d2c4..76d7c52a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,7 +90,7 @@ jobs: env: PLAID_CLIENT_ID: foo PLAID_SECRET: bar - DATABASE_URL: postgres://postgres:postgres@localhost:5432 + DATABASE_URL: postgres://postgres:postgres@localhost:5432 # pipelock:ignore REDIS_URL: redis://localhost:6379 RAILS_ENV: test @@ -141,7 +141,7 @@ jobs: env: PLAID_CLIENT_ID: foo PLAID_SECRET: bar - DATABASE_URL: postgres://postgres:postgres@localhost:5432 + DATABASE_URL: postgres://postgres:postgres@localhost:5432 # pipelock:ignore REDIS_URL: redis://localhost:6379 RAILS_ENV: test diff --git a/.github/workflows/llm-evals.yml b/.github/workflows/llm-evals.yml index aaa5a13a1..249afa96b 100644 --- a/.github/workflows/llm-evals.yml +++ b/.github/workflows/llm-evals.yml @@ -11,7 +11,7 @@ permissions: env: EVAL_MODELS: gpt-4.1 RAILS_ENV: test - DATABASE_URL: postgres://postgres:postgres@localhost:5432 + DATABASE_URL: postgres://postgres:postgres@localhost:5432 # pipelock:ignore REDIS_URL: redis://localhost:6379 PLAID_CLIENT_ID: foo PLAID_SECRET: bar diff --git a/.sure-version b/.sure-version index 64067de7a..0f0454301 100644 --- a/.sure-version +++ b/.sure-version @@ -1 +1 @@ -0.7.2-alpha.4 +0.7.2-alpha.6 diff --git a/Gemfile b/Gemfile index a58e0ccf9..fcb4f7336 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source "https://rubygems.org" ruby file: ".ruby-version" # Rails -gem "rails", "~> 7.2.2" +gem "rails", "~> 8.1.0" # Drivers gem "pg", "~> 1.5" diff --git a/Gemfile.lock b/Gemfile.lock index 6bc0b988d..b4169c29f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,83 +4,84 @@ GEM Ascii85 (2.0.1) aasm (5.5.1) concurrent-ruby (~> 1.0) - actioncable (7.2.3.1) - actionpack (= 7.2.3.1) - activesupport (= 7.2.3.1) + action_text-trix (2.1.19) + railties + actioncable (8.1.3) + actionpack (= 8.1.3) + activesupport (= 8.1.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.2.3.1) - actionpack (= 7.2.3.1) - activejob (= 7.2.3.1) - activerecord (= 7.2.3.1) - activestorage (= 7.2.3.1) - activesupport (= 7.2.3.1) + actionmailbox (8.1.3) + actionpack (= 8.1.3) + activejob (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) mail (>= 2.8.0) - actionmailer (7.2.3.1) - actionpack (= 7.2.3.1) - actionview (= 7.2.3.1) - activejob (= 7.2.3.1) - activesupport (= 7.2.3.1) + actionmailer (8.1.3) + actionpack (= 8.1.3) + actionview (= 8.1.3) + activejob (= 8.1.3) + activesupport (= 8.1.3) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (7.2.3.1) - actionview (= 7.2.3.1) - activesupport (= 7.2.3.1) - cgi + actionpack (8.1.3) + actionview (= 8.1.3) + activesupport (= 8.1.3) nokogiri (>= 1.8.5) - racc - rack (>= 2.2.4, < 3.3) + rack (>= 2.2.4) rack-session (>= 1.0.1) rack-test (>= 0.6.3) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (7.2.3.1) - actionpack (= 7.2.3.1) - activerecord (= 7.2.3.1) - activestorage (= 7.2.3.1) - activesupport (= 7.2.3.1) + actiontext (8.1.3) + action_text-trix (~> 2.1.15) + actionpack (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.2.3.1) - activesupport (= 7.2.3.1) + actionview (8.1.3) + activesupport (= 8.1.3) builder (~> 3.1) - cgi erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (7.2.3.1) - activesupport (= 7.2.3.1) + activejob (8.1.3) + activesupport (= 8.1.3) globalid (>= 0.3.6) - activemodel (7.2.3.1) - activesupport (= 7.2.3.1) - activerecord (7.2.3.1) - activemodel (= 7.2.3.1) - activesupport (= 7.2.3.1) + activemodel (8.1.3) + activesupport (= 8.1.3) + activerecord (8.1.3) + activemodel (= 8.1.3) + activesupport (= 8.1.3) timeout (>= 0.4.0) activerecord-import (2.2.0) activerecord (>= 4.2) - activestorage (7.2.3.1) - actionpack (= 7.2.3.1) - activejob (= 7.2.3.1) - activerecord (= 7.2.3.1) - activesupport (= 7.2.3.1) + activestorage (8.1.3) + actionpack (= 8.1.3) + activejob (= 8.1.3) + activerecord (= 8.1.3) + activesupport (= 8.1.3) marcel (~> 1.0) - activesupport (7.2.3.1) + activesupport (8.1.3) base64 - benchmark (>= 0.3) bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) + json logger (>= 1.4.2) - minitest (>= 5.1, < 6) + minitest (>= 5.1) securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) - addressable (2.8.7) - public_suffix (>= 2.0.2, < 7.0) + uri (>= 0.13.1) + addressable (2.9.0) + public_suffix (>= 2.0.2, < 8.0) aes_key_wrap (1.1.0) afm (1.0.0) after_commit_everywhere (1.6.0) @@ -160,10 +161,10 @@ GEM cronex (0.15.0) tzinfo unicode (>= 0.4.4.5) - css_parser (1.21.1) + css_parser (1.22.0) addressable csv (3.3.5) - date (3.4.1) + date (3.5.1) debug (1.11.0) irb (~> 1.10) reline (>= 0.3.8) @@ -200,7 +201,7 @@ GEM ed25519 (1.4.0) email_validator (2.2.4) activemodel - erb (5.0.1) + erb (6.0.4) erb_lint (0.9.0) activesupport better_html (>= 2.0.1) @@ -325,9 +326,10 @@ GEM inline_svg (1.10.0) activesupport (>= 3.0) nokogiri (>= 1.6) - io-console (0.8.0) - irb (1.15.2) + io-console (0.8.2) + irb (1.18.0) pp (>= 0.6.0) + prism (>= 1.3.0) rdoc (>= 4.0.0) reline (>= 0.4.2) jbuilder (2.13.0) @@ -392,15 +394,15 @@ GEM zeitwerk (~> 2.5) lucide-rails (0.7.3) railties (>= 4.1.0) - mail (2.8.1) + mail (2.9.0) + logger mini_mime (>= 0.1.1) net-imap net-pop net-smtp - marcel (1.1.0) + marcel (1.2.1) matrix (0.4.2) memory_profiler (1.1.0) - method_source (1.1.0) mini_histogram (0.3.1) mini_magick (5.2.0) benchmark @@ -417,7 +419,7 @@ GEM mutex_m (0.3.0) net-http (0.9.1) uri (>= 0.11.1) - net-imap (0.5.8) + net-imap (0.6.4.1) date net-protocol net-pop (0.1.2) @@ -426,22 +428,22 @@ GEM timeout net-smtp (0.5.1) net-protocol - nio4r (2.7.4) - nokogiri (1.19.2-aarch64-linux-gnu) + nio4r (2.7.5) + nokogiri (1.19.3-aarch64-linux-gnu) racc (~> 1.4) - nokogiri (1.19.2-aarch64-linux-musl) + nokogiri (1.19.3-aarch64-linux-musl) racc (~> 1.4) - nokogiri (1.19.2-arm-linux-gnu) + nokogiri (1.19.3-arm-linux-gnu) racc (~> 1.4) - nokogiri (1.19.2-arm-linux-musl) + nokogiri (1.19.3-arm-linux-musl) racc (~> 1.4) - nokogiri (1.19.2-arm64-darwin) + nokogiri (1.19.3-arm64-darwin) racc (~> 1.4) - nokogiri (1.19.2-x86_64-darwin) + nokogiri (1.19.3-x86_64-darwin) racc (~> 1.4) - nokogiri (1.19.2-x86_64-linux-gnu) + nokogiri (1.19.3-x86_64-linux-gnu) racc (~> 1.4) - nokogiri (1.19.2-x86_64-linux-musl) + nokogiri (1.19.3-x86_64-linux-musl) racc (~> 1.4) oauth2 (2.0.18) faraday (>= 0.17.3, < 4.0) @@ -516,7 +518,7 @@ GEM useragent (~> 0.16.3) posthog-ruby (3.3.3) concurrent-ruby (~> 1) - pp (0.6.2) + pp (0.6.3) prettyprint prettyprint (0.2.0) prism (1.4.0) @@ -525,10 +527,10 @@ GEM activesupport (>= 7.0.0) rack railties (>= 7.0.0) - psych (5.2.6) + psych (5.4.0) date stringio - public_suffix (6.0.2) + public_suffix (7.0.5) puma (6.6.0) nio4r (~> 2.0) pundit (2.5.2) @@ -559,22 +561,22 @@ GEM rack (>= 3.0.0) rack-test (2.2.0) rack (>= 1.3) - rackup (2.2.1) + rackup (2.3.1) rack (>= 3) - rails (7.2.3.1) - actioncable (= 7.2.3.1) - actionmailbox (= 7.2.3.1) - actionmailer (= 7.2.3.1) - actionpack (= 7.2.3.1) - actiontext (= 7.2.3.1) - actionview (= 7.2.3.1) - activejob (= 7.2.3.1) - activemodel (= 7.2.3.1) - activerecord (= 7.2.3.1) - activestorage (= 7.2.3.1) - activesupport (= 7.2.3.1) + rails (8.1.3) + actioncable (= 8.1.3) + actionmailbox (= 8.1.3) + actionmailer (= 8.1.3) + actionpack (= 8.1.3) + actiontext (= 8.1.3) + actionview (= 8.1.3) + activejob (= 8.1.3) + activemodel (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) bundler (>= 1.15.0) - railties (= 7.2.3.1) + railties (= 8.1.3) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest @@ -582,16 +584,15 @@ GEM rails-html-sanitizer (1.7.0) loofah (~> 2.25) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - rails-i18n (7.0.10) + rails-i18n (8.1.0) i18n (>= 0.7, < 2) - railties (>= 6.0.0, < 8) + railties (>= 8.0.0, < 9) rails-settings-cached (2.9.6) activerecord (>= 5.0.0) railties (>= 5.0.0) - railties (7.2.3.1) - actionpack (= 7.2.3.1) - activesupport (= 7.2.3.1) - cgi + railties (8.1.3) + actionpack (= 8.1.3) + activesupport (= 8.1.3) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) @@ -599,23 +600,24 @@ GEM tsort (>= 0.2) zeitwerk (~> 2.6) rainbow (3.1.1) - rake (13.3.0) + rake (13.4.2) rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) rbs (3.9.4) logger rchardet (1.10.0) - rdoc (6.14.2) + rdoc (7.2.0) erb psych (>= 4.0.0) + tsort redcarpet (3.6.1) redis (5.4.0) redis-client (>= 0.22.0) redis-client (0.25.0) connection_pool regexp_parser (2.10.0) - reline (0.6.1) + reline (0.6.3) io-console (~> 0.5) representable (3.2.0) declarative (< 0.1.0) @@ -646,17 +648,17 @@ GEM rspec-mocks (~> 3.13) rspec-support (~> 3.13) rspec-support (3.13.6) - rswag-api (2.16.0) - activesupport (>= 5.2, < 8.1) - railties (>= 5.2, < 8.1) - rswag-specs (2.16.0) - activesupport (>= 5.2, < 8.1) - json-schema (>= 2.2, < 6.0) - railties (>= 5.2, < 8.1) + rswag-api (2.17.0) + activesupport (>= 5.2, < 8.2) + railties (>= 5.2, < 8.2) + rswag-specs (2.17.0) + activesupport (>= 5.2, < 8.2) + json-schema (>= 2.2, < 7.0) + railties (>= 5.2, < 8.2) rspec-core (>= 2.14) - rswag-ui (2.16.0) - actionpack (>= 5.2, < 8.1) - railties (>= 5.2, < 8.1) + rswag-ui (2.17.0) + actionpack (>= 5.2, < 8.2) + railties (>= 5.2, < 8.2) rubocop (1.76.1) json (~> 2.3) language_server-protocol (~> 3.17.0.2) @@ -766,7 +768,7 @@ GEM standardwebhooks (1.1.0) stimulus-rails (1.3.4) railties (>= 6.0.0) - stringio (3.1.7) + stringio (3.2.0) stripe (15.3.0) swd (2.0.3) activesupport (>= 3) @@ -785,7 +787,7 @@ GEM tailwindcss-ruby (4.1.8-x86_64-linux-musl) terminal-table (4.0.0) unicode-display_width (>= 1.1.1, < 4) - thor (1.4.0) + thor (1.5.0) timeout (0.6.1) tpm-key_attestation (0.14.1) bindata (~> 2.4) @@ -815,10 +817,10 @@ GEM base64 vernier (1.8.0) version_gem (1.1.9) - view_component (3.23.2) - activesupport (>= 5.2.0, < 8.1) + view_component (4.12.0) + actionview (>= 7.1.0) + activesupport (>= 7.1.0) concurrent-ruby (~> 1) - method_source (~> 1.0) web-console (4.2.1) actionview (>= 6.0.0) activemodel (>= 6.0.0) @@ -841,14 +843,14 @@ GEM crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) websocket (1.2.11) - websocket-driver (0.8.0) + websocket-driver (0.8.1) base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - yard (0.9.37) - zeitwerk (2.7.3) + yard (0.9.42) + zeitwerk (2.8.2) PLATFORMS aarch64-linux-gnu @@ -921,7 +923,7 @@ DEPENDENCIES rack-attack (~> 6.6) rack-cors rack-mini-profiler - rails (~> 7.2.2) + rails (~> 8.1.0) rails-i18n rails-settings-cached rchardet diff --git a/app/helpers/styled_form_builder.rb b/app/helpers/styled_form_builder.rb index 277d34aa3..7899dd5ea 100644 --- a/app/helpers/styled_form_builder.rb +++ b/app/helpers/styled_form_builder.rb @@ -1,5 +1,10 @@ class StyledFormBuilder < ActionView::Helpers::FormBuilder - class_attribute :text_field_helpers, default: field_helpers - [ :label, :check_box, :radio_button, :fields_for, :fields, :hidden_field, :file_field ] + # Rails 8 renamed two field_helpers entries: :text_area -> :textarea and + # :check_box -> :checkbox. Exclude both spellings of the non-text helpers, and + # alias the legacy method names below so existing `form.text_area` call sites + # stay styled. (Harmless on Rails 7.2, where the old names are present instead.) + NON_TEXT_FIELD_HELPERS = [ :label, :check_box, :checkbox, :radio_button, :fields_for, :fields, :hidden_field, :file_field ].freeze + class_attribute :text_field_helpers, default: field_helpers - NON_TEXT_FIELD_HELPERS text_field_helpers.each do |selector| class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 @@ -14,6 +19,9 @@ class StyledFormBuilder < ActionView::Helpers::FormBuilder RUBY_EVAL end + # Keep `form.text_area` styled after Rails 8 renamed the helper to `textarea`. + alias_method :text_area, :textarea if method_defined?(:textarea) + def radio_button(method, tag_value, options = {}) merged_options = { class: "form-field__radio" }.merge(options) super(method, tag_value, merged_options) diff --git a/app/javascript/controllers/clipboard_controller.js b/app/javascript/controllers/clipboard_controller.js index 7e0b1a493..d895ac7b8 100644 --- a/app/javascript/controllers/clipboard_controller.js +++ b/app/javascript/controllers/clipboard_controller.js @@ -1,28 +1,69 @@ import { Controller } from "@hotwired/stimulus"; +// Single source of truth so the icon-swap and label-flash feedback last the +// same time when both copy buttons appear on one page. +const RESET_DELAY_MS = 2000; + export default class extends Controller { static targets = ["source", "iconDefault", "iconSuccess"]; + static values = { copiedText: String }; copy(event) { event.preventDefault(); - if (this.sourceTarget?.textContent) { - navigator.clipboard - .writeText(this.sourceTarget.textContent) - .then(() => { - this.showSuccess(); - }) - .catch((error) => { - console.error("Failed to copy text: ", error); - }); - } + // Capture the button now: `event.currentTarget` is reset to null once the + // event finishes dispatching, so it can't be read inside the async `.then`. + const button = event.currentTarget; + const text = this.sourceTarget?.textContent; + if (!text) return; + + navigator.clipboard + .writeText(text) + .then(() => { + this.showSuccess(button); + }) + .catch((error) => { + console.error("Failed to copy text: ", error); + }); } - showSuccess() { - this.iconDefaultTarget.classList.add("hidden"); - this.iconSuccessTarget.classList.remove("hidden"); - setTimeout(() => { - this.iconDefaultTarget.classList.remove("hidden"); - this.iconSuccessTarget.classList.add("hidden"); - }, 3000); + showSuccess(button) { + // Markup that ships explicit default/success icons (invite codes, MFA, + // profiles) toggles between them. + if (this.hasIconDefaultTarget && this.hasIconSuccessTarget) { + this.iconDefaultTarget.classList.add("hidden"); + this.iconSuccessTarget.classList.remove("hidden"); + setTimeout(() => { + this.iconDefaultTarget.classList.remove("hidden"); + this.iconSuccessTarget.classList.add("hidden"); + }, RESET_DELAY_MS); + return; + } + + // A single-icon button (e.g. DS::Button) has no icons to swap, so confirm + // the copy by briefly flipping the button's own label. + this.flashLabel(button); + } + + flashLabel(button) { + // DS::Button renders its icon as an and its text in `span.truncate`, + // so scope to that class rather than the first in case an icon ever + // ships wrapped in a span. + const label = button?.querySelector("span.truncate") ?? button?.querySelector("span"); + if (!label || !this.hasCopiedTextValue) return; + + clearTimeout(this.labelResetTimer); + if (this.originalLabel == null) { + this.originalLabel = label.textContent; + } + + label.textContent = this.copiedTextValue; + this.labelResetTimer = setTimeout(() => { + label.textContent = this.originalLabel; + this.originalLabel = null; + }, RESET_DELAY_MS); + } + + disconnect() { + clearTimeout(this.labelResetTimer); } } diff --git a/app/models/message.rb b/app/models/message.rb index 736ac8785..27eca7cc0 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -12,6 +12,8 @@ class Message < ApplicationRecord after_create_commit -> { broadcast_append_to chat, target: chat.messages_target }, if: :broadcast? after_update_commit -> { broadcast_update_to chat }, if: :broadcast? + # Clears the "Thinking…" bubble if the provider errors before any text streams. + after_destroy_commit -> { broadcast_remove_to chat }, if: :broadcast? scope :ordered, -> { order(created_at: :asc) } diff --git a/app/models/transfer.rb b/app/models/transfer.rb index f2fd234cd..771253911 100644 --- a/app/models/transfer.rb +++ b/app/models/transfer.rb @@ -74,7 +74,7 @@ class Transfer < ApplicationRecord end def date - inflow_transaction.entry.date + inflow_transaction&.entry&.date end def sync_account_later diff --git a/app/views/goals/_status_callout.html.erb b/app/views/goals/_status_callout.html.erb index a2c609ad3..a8118fbc9 100644 --- a/app/views/goals/_status_callout.html.erb +++ b/app/views/goals/_status_callout.html.erb @@ -4,11 +4,11 @@ variant_classes = case goal.status when :behind - "bg-warning/10 border-warning/20 text-warning" + "bg-warning/10 border-warning/20" when :on_track - "bg-success/10 border-success/20 text-success" + "bg-success/10 border-success/20" else - "bg-surface-inset border-secondary text-secondary" + "bg-surface-inset border-secondary" end icon_glyph = case goal.status @@ -18,10 +18,16 @@ else "info" end + icon_color = case goal.status + when :behind then "warning" + when :on_track then "success" + else "default" + end + label = t("goals.status.#{goal.status}", default: goal.status.to_s.titleize) %> -
- <%= icon(icon_glyph, size: "sm") %> +
+ <%= icon(icon_glyph, size: "sm", color: icon_color) %> <%= label %> · "><%= context %> diff --git a/app/views/holdings/_missing_price_tooltip.html.erb b/app/views/holdings/_missing_price_tooltip.html.erb index 94793a882..9ae5b3e55 100644 --- a/app/views/holdings/_missing_price_tooltip.html.erb +++ b/app/views/holdings/_missing_price_tooltip.html.erb @@ -3,7 +3,7 @@ <%= icon "info", size: "sm", color: "current" %> <%= tag.span t(".missing_data"), class: "font-normal text-xs" %>
-