@@ -89,7 +89,7 @@
<%= t(".report_window_note") %>
-
+ <% end %>
<%
ibkr_item = Current.family.ibkr_items.first_or_initialize(name: "Interactive Brokers")
diff --git a/app/views/settings/providers/_indexa_capital_panel.html.erb b/app/views/settings/providers/_indexa_capital_panel.html.erb
index c751e0feb..255126e42 100644
--- a/app/views/settings/providers/_indexa_capital_panel.html.erb
+++ b/app/views/settings/providers/_indexa_capital_panel.html.erb
@@ -29,10 +29,12 @@
type: :password %>
<%= t("indexa_capital_items.panel.fields.api_token.description") %>
-
-
- <%= t("indexa_capital_items.panel.alternative_auth") %>
-
+ <%= render DS::Disclosure.new(variant: :inline) do |disclosure| %>
+ <% disclosure.with_summary_content do %>
+
+ <%= t("indexa_capital_items.panel.alternative_auth") %>
+
+ <% end %>
<%= form.text_field :username,
label: t("indexa_capital_items.panel.fields.username.label"),
@@ -49,7 +51,7 @@
placeholder: is_new_record ? t("indexa_capital_items.panel.fields.password.placeholder_new") : t("indexa_capital_items.panel.fields.password.placeholder_update"),
type: :password %>
-
+ <% end %>
<%= form.submit is_new_record ? t("indexa_capital_items.panel.save_button") : t("indexa_capital_items.panel.update_button"),
diff --git a/app/views/settings/providers/_snaptrade_panel.html.erb b/app/views/settings/providers/_snaptrade_panel.html.erb
index ec877bdcd..1c814b234 100644
--- a/app/views/settings/providers/_snaptrade_panel.html.erb
+++ b/app/views/settings/providers/_snaptrade_panel.html.erb
@@ -57,25 +57,31 @@
<% if items&.any? && items.first.user_registered? %>
<% item = items.first %>
-
-
-
-
- <%= t("providers.snaptrade.status_connected", count: item.snaptrade_accounts.count) %>
- <% if item.unlinked_accounts_count > 0 %>
- (<%= t("providers.snaptrade.needs_setup", count: item.unlinked_accounts_count) %>)
- <% end %>
-
+ <%= render DS::Disclosure.new(
+ variant: :inline,
+ data: {
+ controller: "lazy-load",
+ action: "toggle->lazy-load#toggled",
+ lazy_load_url_value: connections_snaptrade_item_path(item),
+ lazy_load_auto_open_param_value: "manage"
+ }
+ ) do |disclosure| %>
+ <% disclosure.with_summary_content do %>
+
+
+
+ <%= t("providers.snaptrade.status_connected", count: item.snaptrade_accounts.count) %>
+ <% if item.unlinked_accounts_count > 0 %>
+ (<%= t("providers.snaptrade.needs_setup", count: item.unlinked_accounts_count) %>)
+ <% end %>
+
+
+
+ <%= t("providers.snaptrade.manage_connections") %>
+ <%= icon "chevron-right", class: "w-3 h-3 group-open:rotate-90 motion-safe:transition-transform motion-safe:duration-150" %>
+
-
- <%= t("providers.snaptrade.manage_connections") %>
- <%= icon "chevron-right", class: "w-3 h-3 transition-transform group-open:rotate-90" %>
-
-
+ <% end %>
<% end %>
diff --git a/app/views/trades/_header.html.erb b/app/views/trades/_header.html.erb
index 94e8a2c5a..eea444354 100644
--- a/app/views/trades/_header.html.erb
+++ b/app/views/trades/_header.html.erb
@@ -31,7 +31,7 @@
- <%= I18n.l(entry.date, format: :long) %>
+ <%= entry.date ? I18n.l(entry.date, format: :long) : "—" %>
<% end %>
diff --git a/app/views/transactions/_header.html.erb b/app/views/transactions/_header.html.erb
index d80c13bed..a5ca70b63 100644
--- a/app/views/transactions/_header.html.erb
+++ b/app/views/transactions/_header.html.erb
@@ -19,7 +19,7 @@
- <%= I18n.l(entry.date, format: :long) %>
+ <%= entry.date ? I18n.l(entry.date, format: :long) : "—" %>
<% if entry.transaction.pending? %>
">
diff --git a/app/views/valuations/_header.html.erb b/app/views/valuations/_header.html.erb
index d4a376876..2a8566273 100644
--- a/app/views/valuations/_header.html.erb
+++ b/app/views/valuations/_header.html.erb
@@ -19,6 +19,6 @@
- <%= I18n.l(entry.date, format: :long) %>
+ <%= entry.date ? I18n.l(entry.date, format: :long) : "—" %>
<% end %>
diff --git a/config/locales/views/plaid_items/en.yml b/config/locales/views/plaid_items/en.yml
index 0dbe8ee51..25e87511c 100644
--- a/config/locales/views/plaid_items/en.yml
+++ b/config/locales/views/plaid_items/en.yml
@@ -15,6 +15,7 @@ en:
connection_lost_description: This connection is no longer valid. You'll need
to delete this connection and add it again to continue syncing data.
delete: Delete
+ deletion_in_progress: (deletion in progress...)
error: Error occurred while syncing data
no_accounts_description: We could not load any accounts from this financial
institution.
diff --git a/test/components/DS/pill_test.rb b/test/components/DS/pill_test.rb
new file mode 100644
index 000000000..f108593b4
--- /dev/null
+++ b/test/components/DS/pill_test.rb
@@ -0,0 +1,59 @@
+require "test_helper"
+
+class DS::PillTest < ViewComponent::TestCase
+ test "marker mode (default) renders uppercase sub-12px chrome" do
+ render_inline(DS::Pill.new(label: "Beta", tone: :violet))
+
+ pill = page.find("span", text: "Beta")
+ assert_includes pill[:class], "uppercase"
+ # Marker keeps sub-12px text via arbitrary value (intentional — see component docs).
+ assert_match(/text-\[1[01]px\]/, pill[:class])
+ end
+
+ test "marker: false renders normal-case DS-scale chrome" do
+ render_inline(DS::Pill.new(label: "Active", tone: :success, marker: false))
+
+ pill = page.find("span", text: "Active")
+ refute_includes pill[:class], "uppercase"
+ # Badge mode snaps to text-xs / text-sm — no sub-12px arbitrary values.
+ assert_match(/text-(xs|sm)/, pill[:class])
+ refute_match(/text-\[1[01]px\]/, pill[:class])
+ end
+
+ test "semantic tone aliases resolve to visual palette tones" do
+ {
+ success: :green,
+ warning: :amber,
+ error: :red,
+ destructive: :red,
+ info: :indigo,
+ neutral: :gray
+ }.each do |alias_name, expected_visual|
+ pill = DS::Pill.new(label: "x", tone: alias_name)
+ assert_equal expected_visual, pill.tone, "Expected #{alias_name} → #{expected_visual}, got #{pill.tone}"
+ end
+ end
+
+ test "unknown tone falls back to violet" do
+ pill = DS::Pill.new(label: "x", tone: :nonexistent)
+ assert_equal :violet, pill.tone
+ end
+
+ test "red tone palette resolves to red-* tokens" do
+ pill = DS::Pill.new(label: "Failed", tone: :error)
+ assert_includes pill.palette[:dot], "color-red-500"
+ assert_includes pill.palette[:bg], "color-red-50"
+ end
+
+ test "icon option renders glyph in place of dot" do
+ render_inline(DS::Pill.new(label: "Syncing", tone: :info, marker: false, icon: "loader"))
+
+ # Lucide icon helper renders the inline SVG; verifying we see at least one