diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb
index 07a7d10f0e1..8f8961f43c2 100644
--- a/app/controllers/pages_controller.rb
+++ b/app/controllers/pages_controller.rb
@@ -4,6 +4,10 @@ class PagesController < ApplicationController
skip_authentication only: :redis_configuration_error
def dashboard
+ if Current.user&.ui_layout_intro?
+ redirect_to chats_path and return
+ end
+
@balance_sheet = Current.family.balance_sheet
@accounts = Current.family.accounts.visible.with_attached_logo
@@ -45,6 +49,10 @@ def dashboard
@breadcrumbs = [ [ "Home", root_path ], [ "Dashboard", nil ] ]
end
+ def intro
+ @breadcrumbs = [ [ "Home", chats_path ], [ "Intro", nil ] ]
+ end
+
def changelog
@release_notes = github_provider.fetch_latest_release_notes
diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb
index 2839b60e0cd..6e8dbbf0e15 100644
--- a/app/controllers/settings/profiles_controller.rb
+++ b/app/controllers/settings/profiles_controller.rb
@@ -1,5 +1,5 @@
class Settings::ProfilesController < ApplicationController
- layout "settings"
+ layout :layout_for_settings_profile
def show
@user = Current.user
@@ -36,4 +36,10 @@ def destroy
redirect_to settings_profile_path
end
+
+ private
+
+ def layout_for_settings_profile
+ Current.user&.ui_layout_intro? ? "application" : "settings"
+ end
end
diff --git a/app/models/assistant/configurable.rb b/app/models/assistant/configurable.rb
index 1da95d14b87..d0285f14f96 100644
--- a/app/models/assistant/configurable.rb
+++ b/app/models/assistant/configurable.rb
@@ -6,13 +6,51 @@ def config_for(chat)
preferred_currency = Money::Currency.new(chat.user.family.currency)
preferred_date_format = chat.user.family.date_format
- {
- instructions: default_instructions(preferred_currency, preferred_date_format),
- functions: default_functions
- }
+ if chat.user.ui_layout_intro?
+ {
+ instructions: intro_instructions(preferred_currency, preferred_date_format),
+ functions: []
+ }
+ else
+ {
+ instructions: default_instructions(preferred_currency, preferred_date_format),
+ functions: default_functions
+ }
+ end
end
private
+ def intro_instructions(preferred_currency, preferred_date_format)
+ <<~PROMPT
+ ## Your identity
+
+ You are Sure, a warm and curious financial guide welcoming a new household to the Sure personal finance application.
+
+ ## Your purpose
+
+ Host an introductory conversation that helps you understand the user's stage of life, financial responsibilities, and near-term priorities so future guidance feels personal and relevant.
+
+ ## Conversation approach
+
+ - Ask one thoughtful question at a time and tailor follow-ups based on what the user shares.
+ - Reflect key details back to the user to confirm understanding.
+ - Keep responses concise, friendly, and free of filler phrases.
+ - If the user requests detailed analytics, let them know the dashboard experience will cover it soon and guide them back to sharing context.
+
+ ## Information to uncover
+
+ - Household composition and stage of life milestones (education, career, retirement, dependents, caregiving, etc.).
+ - Primary financial goals, concerns, and timelines.
+ - Notable upcoming events or obligations.
+
+ ## Formatting guidelines
+
+ - Use markdown for any lists or emphasis.
+ - When money or timeframes are discussed, format currency with #{preferred_currency.symbol} (#{preferred_currency.iso_code}) and dates using #{preferred_date_format}.
+ - Do not call external tools or functions.
+ PROMPT
+ end
+
def default_functions
[
Assistant::Function::GetTransactions,
diff --git a/app/models/user.rb b/app/models/user.rb
index a3ca25adad5..569528c8308 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -23,6 +23,9 @@ class User < ApplicationRecord
normalizes :first_name, :last_name, with: ->(value) { value.strip.presence }
enum :role, { member: "member", admin: "admin", super_admin: "super_admin" }, validate: true
+ enum :ui_layout, { dashboard: "dashboard", intro: "intro" }, validate: true, prefix: true
+
+ before_validation :apply_ui_layout_defaults, on: :create
has_one_attached :profile_image do |attachable|
attachable.variant :thumbnail, resize_to_fill: [ 300, 300 ], convert: :webp, saver: { quality: 80 }
@@ -96,6 +99,11 @@ def ai_enabled?
ai_enabled && ai_available?
end
+ def self.default_ui_layout
+ layout = Rails.application.config.x.ui&.default_layout || "dashboard"
+ layout.in?(%w[intro dashboard]) ? layout : "dashboard"
+ end
+
# Deactivation
validate :can_deactivate, if: -> { active_changed? && !active }
after_update_commit :purge_later, if: -> { saved_change_to_active?(from: true, to: false) }
@@ -170,6 +178,16 @@ def account_order
end
private
+ def apply_ui_layout_defaults
+ self.ui_layout = (ui_layout.presence || self.class.default_ui_layout)
+
+ if ui_layout_intro?
+ self.show_sidebar = false
+ self.show_ai_sidebar = false
+ self.ai_enabled = true
+ end
+ end
+
def ensure_valid_profile_image
return unless profile_image.attached?
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 02854276b29..4b6db6804da 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -1,9 +1,18 @@
-<% mobile_nav_items = [
- { name: "Home", path: root_path, icon: "pie-chart", icon_custom: false, active: page_active?(root_path) },
- { name: "Transactions", path: transactions_path, icon: "credit-card", icon_custom: false, active: page_active?(transactions_path) },
- { name: "Budgets", path: budgets_path, icon: "map", icon_custom: false, active: page_active?(budgets_path) },
- { name: "Assistant", path: chats_path, icon: "icon-assistant", icon_custom: true, active: page_active?(chats_path), mobile_only: true }
-] %>
+<% intro_mode = Current.user&.ui_layout_intro? %>
+<% home_path = intro_mode ? chats_path : root_path %>
+<% mobile_nav_items = if intro_mode
+ [
+ { name: "Home", path: chats_path, icon: "home", icon_custom: false, active: page_active?(chats_path) },
+ { name: "Intro", path: intro_path, icon: "sparkles", icon_custom: false, active: page_active?(intro_path) }
+ ]
+else
+ [
+ { name: "Home", path: root_path, icon: "pie-chart", icon_custom: false, active: page_active?(root_path) },
+ { name: "Transactions", path: transactions_path, icon: "credit-card", icon_custom: false, active: page_active?(transactions_path) },
+ { name: "Budgets", path: budgets_path, icon: "map", icon_custom: false, active: page_active?(budgets_path) },
+ { name: "Assistant", path: chats_path, icon: "icon-assistant", icon_custom: true, active: page_active?(chats_path), mobile_only: true }
+ ]
+end %>
<% desktop_nav_items = mobile_nav_items.reject { |item| item[:mobile_only] } %>
<% expanded_sidebar_class = "w-full" %>
@@ -19,9 +28,11 @@
-
- <%= icon("x", as_button: true, data: { action: "app-layout#closeMobileSidebar" }) %>
-
+ <% unless intro_mode %>
+
+ <%= icon("x", as_button: true, data: { action: "app-layout#closeMobileSidebar" }) %>
+
+ <% end %>
<%= render(
"accounts/account_sidebar_tabs",
@@ -33,20 +44,23 @@
<%# MOBILE - Top nav %>
- <%= icon("panel-left", as_button: true, data: { action: "app-layout#openMobileSidebar"}) %>
+ <% if intro_mode %>
+ <% else %>
+ <%= icon("panel-left", as_button: true, data: { action: "app-layout#openMobileSidebar"}) %>
+ <% end %>
- <%= link_to root_path, class: "block" do %>
+ <%= link_to home_path, class: "block" do %>
<%= image_tag "logomark-color.svg", class: "w-9 h-9 mx-auto" %>
<% end %>
- <%= render "users/user_menu", user: Current.user, placement: "bottom-end", offset: 12 %>
+ <%= render "users/user_menu", user: Current.user, placement: "bottom-end", offset: 12, intro_mode: intro_mode %>
<%# DESKTOP - Left navbar %>
- <%= link_to root_path, class: "block" do %>
+ <%= link_to home_path, class: "block" do %>
<%= image_tag "logomark-color.svg", class: "w-9 h-9 mx-auto" %>
<% end %>
@@ -67,7 +81,7 @@
target: "_blank"
) %>
- <%= render "users/user_menu", user: Current.user %>
+ <%= render "users/user_menu", user: Current.user, intro_mode: intro_mode %>
@@ -112,18 +126,20 @@
<%# SHARED - Main content %>
<%= tag.main class: class_names("grow overflow-y-auto px-3 lg:px-10 py-4 w-full mx-auto max-w-5xl"), data: { app_layout_target: "content" } do %>
-
-
- <%= icon("panel-left", as_button: true, data: { action: "app-layout#toggleLeftSidebar" }) %>
-
- <% if content_for?(:breadcrumbs) %>
- <%= yield :breadcrumbs %>
- <% else %>
- <%= render "layouts/shared/breadcrumbs", breadcrumbs: @breadcrumbs %>
- <% end %>
+ <% unless intro_mode %>
+
+
+ <%= icon("panel-left", as_button: true, data: { action: "app-layout#toggleLeftSidebar" }) %>
+
+ <% if content_for?(:breadcrumbs) %>
+ <%= yield :breadcrumbs %>
+ <% else %>
+ <%= render "layouts/shared/breadcrumbs", breadcrumbs: @breadcrumbs %>
+ <% end %>
+
+ <%= icon("panel-right", as_button: true, data: { action: "app-layout#toggleRightSidebar" }) %>
- <%= icon("panel-right", as_button: true, data: { action: "app-layout#toggleRightSidebar" }) %>
-
+ <% end %>
<% if content_for?(:page_header) %>
<%= yield :page_header %>
diff --git a/app/views/pages/intro.html.erb b/app/views/pages/intro.html.erb
new file mode 100644
index 00000000000..072a76068f3
--- /dev/null
+++ b/app/views/pages/intro.html.erb
@@ -0,0 +1,21 @@
+<% content_for :page_header do %>
+
+
Welcome!
+
+
+<% end %>
+
+
+
+
+ <%= image_tag "logomark-color.svg", class: "w-16 h-16" %>
+
+
Intro experience coming soon
+
+ We're building a richer onboarding journey to learn about your goals, milestones, and day-to-day needs. For now, head over to the chat sidebar to start a conversation with Sure and let us know where you are in your financial journey.
+
+
+ <%= link_to "Start chatting", chats_path, class: "inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-primary text-white font-medium" %>
+
+
+
diff --git a/app/views/settings/profiles/show.html.erb b/app/views/settings/profiles/show.html.erb
index 2e78beec9a1..90991ac27a8 100644
--- a/app/views/settings/profiles/show.html.erb
+++ b/app/views/settings/profiles/show.html.erb
@@ -25,101 +25,103 @@
<% end %>
<% end %>
-<%= settings_section title: t(".household_title"), subtitle: t(".household_subtitle") do %>
-
- <%= styled_form_with model: Current.user, class: "space-y-4", data: { controller: "auto-submit-form" } do |form| %>
- <%= form.fields_for :family do |family_fields| %>
- <%= family_fields.text_field :name,
- placeholder: t(".household_form_input_placeholder"),
- label: t(".household_form_label"),
- disabled: !Current.user.admin?,
- "data-auto-submit-form-target": "auto" %>
+<% unless Current.user.ui_layout_intro? %>
+ <%= settings_section title: t(".household_title"), subtitle: t(".household_subtitle") do %>
+
+ <%= styled_form_with model: Current.user, class: "space-y-4", data: { controller: "auto-submit-form" } do |form| %>
+ <%= form.fields_for :family do |family_fields| %>
+ <%= family_fields.text_field :name,
+ placeholder: t(".household_form_input_placeholder"),
+ label: t(".household_form_label"),
+ disabled: !Current.user.admin?,
+ "data-auto-submit-form-target": "auto" %>
+ <% end %>
<% end %>
- <% end %>
-
-
-
<%= Current.family.name %> · <%= Current.family.users.size %>
-
- <% @users.each do |user| %>
-
-
- <%= render "settings/user_avatar", avatar_url: user.profile_image&.variant(:small)&.url, initials: user.initials %>
-
-
<%= user.display_name %>
-
- <% if Current.user.admin? && user != Current.user %>
-
- <%= render DS::Button.new(
- variant: "icon",
- icon: "x",
- href: settings_profile_path(user_id: user),
- method: :delete,
- confirm: CustomConfirm.for_resource_deletion(user.display_name, high_severity: true)
- ) %>
-
- <% end %>
+
+
+
<%= Current.family.name %> · <%= Current.family.users.size %>
- <% end %>
- <% if @pending_invitations.any? %>
- <% @pending_invitations.each do |invitation| %>
-
-
-
-
<%= invitation.email[0] %>
-
-
-
<%= invitation.email %>
-
-
+ <% @users.each do |user| %>
+
+
+ <%= render "settings/user_avatar", avatar_url: user.profile_image&.variant(:small)&.url, initials: user.initials %>
-
- <% if self_hosted? %>
-
-
<%= t(".invitation_link") %>
-
<%= accept_invitation_url(invitation.token) %>
-
-
-
- <%= icon "copy" %>
-
-
- <%= icon "check" %>
-
-
-
- <% end %>
-
- <% if Current.user.admin? %>
+
<%= user.display_name %>
+
+ <% if Current.user.admin? && user != Current.user %>
+
<%= render DS::Button.new(
variant: "icon",
icon: "x",
- href: invitation_path(invitation),
+ href: settings_profile_path(user_id: user),
method: :delete,
- confirm: CustomConfirm.for_resource_deletion(invitation.email, high_severity: true)
+ confirm: CustomConfirm.for_resource_deletion(user.display_name, high_severity: true)
) %>
- <% end %>
-
+
+ <% end %>
<% end %>
- <% end %>
- <% if Current.user.admin? %>
- <%= link_to new_invitation_path,
- class: "bg-container-inset flex items-center justify-center gap-2 text-secondary mt-1 hover:bg-container-inset-hover rounded-lg px-4 py-2 w-full text-center",
- data: { turbo_frame: :modal } do %>
- <%= icon("plus") %>
- <%= t(".invite_member") %>
+ <% if @pending_invitations.any? %>
+ <% @pending_invitations.each do |invitation| %>
+
+
+
+
<%= invitation.email[0] %>
+
+
+
<%= invitation.email %>
+
+
+
+
+ <% if self_hosted? %>
+
+
<%= t(".invitation_link") %>
+
<%= accept_invitation_url(invitation.token) %>
+
+
+
+ <%= icon "copy" %>
+
+
+ <%= icon "check" %>
+
+
+
+ <% end %>
+
+ <% if Current.user.admin? %>
+ <%= render DS::Button.new(
+ variant: "icon",
+ icon: "x",
+ href: invitation_path(invitation),
+ method: :delete,
+ confirm: CustomConfirm.for_resource_deletion(invitation.email, high_severity: true)
+ ) %>
+ <% end %>
+
+
+ <% end %>
<% end %>
- <% end %>
+ <% if Current.user.admin? %>
+ <%= link_to new_invitation_path,
+ class: "bg-container-inset flex items-center justify-center gap-2 text-secondary mt-1 hover:bg-container-inset-hover rounded-lg px-4 py-2 w-full text-center",
+ data: { turbo_frame: :modal } do %>
+ <%= icon("plus") %>
+ <%= t(".invite_member") %>
+ <% end %>
+ <% end %>
+
-
+ <% end %>
<% end %>
<%= settings_section title: t(".danger_zone_title") do %>
diff --git a/app/views/users/_user_menu.html.erb b/app/views/users/_user_menu.html.erb
index a37d0f2741c..b8889c8c60d 100644
--- a/app/views/users/_user_menu.html.erb
+++ b/app/views/users/_user_menu.html.erb
@@ -1,7 +1,20 @@
-<%# locals: (user:, placement: "right-start", offset: 16) %>
+<%# locals: (user:, placement: "right-start", offset: 16, intro_mode: false) %>
+
+<% intro_mode = local_assigns.fetch(:intro_mode, false) %>
- <%= render DS::Menu.new(variant: "avatar", avatar_url: user.profile_image&.variant(:small)&.url, initials: user.initials, placement: placement, offset: offset) do |menu| %>
+ <%= render DS::Menu.new(
+ variant: "avatar",
+ avatar_url: user.profile_image&.variant(:small)&.url,
+ initials: user.initials,
+ placement: placement,
+ offset: offset
+ ) do |menu| %>
+ <% if intro_mode %>
+ <% menu.with_button do %>
+ <%= render DS::Button.new(variant: "icon", icon: "settings", data: { DS__menu_target: "button" }) %>
+ <% end %>
+ <% end %>
<%= menu.with_header do %>
@@ -30,10 +43,15 @@
<% end %>
<% end %>
- <% menu.with_item(variant: "link", text: "Settings", icon: "settings", href: accounts_path(return_to: request.fullpath)) %>
+ <% menu.with_item(
+ variant: "link",
+ text: "Settings",
+ icon: "settings",
+ href: intro_mode ? settings_profile_path : accounts_path(return_to: request.fullpath)
+ ) %>
<% menu.with_item(variant: "link", text: "Changelog", icon: "box", href: changelog_path) %>
- <% if self_hosted? %>
+ <% if self_hosted? && !intro_mode %>
<% menu.with_item(variant: "link", text: "Feedback", icon: "megaphone", href: feedback_path) %>
<% end %>
<% menu.with_item(variant: "link", text: "Contact", icon: "message-square-more", href: "https://discord.gg/36ZGBsxYEK") %>
diff --git a/config/application.rb b/config/application.rb
index 77d071173e1..8abc65a7e6b 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -41,5 +41,9 @@ class Application < Rails::Application
# Enable Rack::Attack middleware for API rate limiting
config.middleware.use Rack::Attack
+
+ config.x.ui = ActiveSupport::OrderedOptions.new
+ default_layout = ENV.fetch("DEFAULT_UI_LAYOUT", "dashboard")
+ config.x.ui.default_layout = default_layout.in?(%w[dashboard intro]) ? default_layout : "dashboard"
end
end
diff --git a/config/routes.rb b/config/routes.rb
index 72a0abdb7dd..007465439a9 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -304,6 +304,7 @@
get "privacy", to: redirect("about:blank")
get "terms", to: redirect("about:blank")
+ get "intro", to: "pages#intro"
# Defines the root path route ("/")
root "pages#dashboard"
diff --git a/db/migrate/20251030140000_add_ui_layout_to_users.rb b/db/migrate/20251030140000_add_ui_layout_to_users.rb
new file mode 100644
index 00000000000..236498a623d
--- /dev/null
+++ b/db/migrate/20251030140000_add_ui_layout_to_users.rb
@@ -0,0 +1,16 @@
+class AddUiLayoutToUsers < ActiveRecord::Migration[7.2]
+ class MigrationUser < ApplicationRecord
+ self.table_name = "users"
+ end
+
+ def up
+ add_column :users, :ui_layout, :string, if_not_exists: true
+
+ MigrationUser.reset_column_information
+ MigrationUser.where(ui_layout: [ nil, "" ]).update_all(ui_layout: "dashboard")
+ end
+
+ def down
+ remove_column :users, :ui_layout
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index a3cdddc557f..92b317c0040 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.2].define(version: 2025_10_29_204447) do
+ActiveRecord::Schema[7.2].define(version: 2025_10_30_140000) do
# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
enable_extension "plpgsql"
@@ -25,7 +25,7 @@
t.uuid "provider_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.index ["account_id", "provider_type"], name: "index_account_providers_on_account_and_provider_type", unique: true
+ t.index ["account_id", "provider_type"], name: "index_account_providers_on_account_id_and_provider_type", unique: true
t.index ["provider_type", "provider_id"], name: "index_account_providers_on_provider_type_and_provider_id", unique: true
end
@@ -945,6 +945,7 @@
t.datetime "set_onboarding_preferences_at"
t.datetime "set_onboarding_goals_at"
t.string "default_account_order", default: "name_asc"
+ t.string "ui_layout"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["family_id"], name: "index_users_on_family_id"
t.index ["last_viewed_chat_id"], name: "index_users_on_last_viewed_chat_id"
diff --git a/test/controllers/settings/profiles_controller_test.rb b/test/controllers/settings/profiles_controller_test.rb
index deae066df1a..8a83923efaa 100644
--- a/test/controllers/settings/profiles_controller_test.rb
+++ b/test/controllers/settings/profiles_controller_test.rb
@@ -4,6 +4,7 @@ class Settings::ProfilesControllerTest < ActionDispatch::IntegrationTest
setup do
@admin = users(:family_admin)
@member = users(:family_member)
+ @intro_user = users(:intro_user)
end
test "should get show" do
@@ -12,6 +13,19 @@ class Settings::ProfilesControllerTest < ActionDispatch::IntegrationTest
assert_response :success
end
+ test "intro user sees profile without settings navigation" do
+ sign_in @intro_user
+ get settings_profile_path
+
+ assert_response :success
+ assert_select "#mobile-settings-nav", count: 0
+ assert_select "h2", text: I18n.t("settings.profiles.show.household_title"), count: 0
+ assert_select "[data-action='app-layout#openMobileSidebar']", count: 0
+ assert_select "[data-action='app-layout#closeMobileSidebar']", count: 0
+ assert_select "[data-action='app-layout#toggleLeftSidebar']", count: 0
+ assert_select "[data-action='app-layout#toggleRightSidebar']", count: 0
+ end
+
test "admin can remove a family member" do
sign_in @admin
assert_difference("User.count", -1) do
diff --git a/test/fixtures/chats.yml b/test/fixtures/chats.yml
index 6e5c5d38604..c7fc0316683 100644
--- a/test/fixtures/chats.yml
+++ b/test/fixtures/chats.yml
@@ -4,4 +4,8 @@ one:
two:
title: Second Chat
- user: family_member
\ No newline at end of file
+ user: family_member
+
+intro:
+ title: Intro Chat
+ user: intro_user
diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml
index b0987b1e607..51517de91fe 100644
--- a/test/fixtures/users.yml
+++ b/test/fixtures/users.yml
@@ -3,39 +3,51 @@ empty:
first_name: User
last_name: One
email: user1@example.com
- password_digest: $2a$12$XoNBo/cMCyzpYtvhrPAhsubG21mELX48RAcjSVCRctW8dG8wrDIla
+ password_digest: $2a$12$XoNBo/cMCyzpYtvhrPAhsubG21mELX48RAcjSVCRctW8dG8wrDIla
onboarded_at: <%= 3.days.ago %>
role: admin
ai_enabled: true
+ show_sidebar: true
+ show_ai_sidebar: true
+ ui_layout: dashboard
sure_support_staff:
family: empty
first_name: Support
last_name: Admin
email: support@sure.am
- password_digest: $2a$12$XoNBo/cMCyzpYtvhrPAhsubG21mELX48RAcjSVCRctW8dG8wrDIla
+ password_digest: $2a$12$XoNBo/cMCyzpYtvhrPAhsubG21mELX48RAcjSVCRctW8dG8wrDIla
role: super_admin
onboarded_at: <%= 3.days.ago %>
ai_enabled: true
+ show_sidebar: true
+ show_ai_sidebar: true
+ ui_layout: dashboard
family_admin:
family: dylan_family
first_name: Bob
last_name: Dylan
email: bob@bobdylan.com
- password_digest: $2a$12$XoNBo/cMCyzpYtvhrPAhsubG21mELX48RAcjSVCRctW8dG8wrDIla
+ password_digest: $2a$12$XoNBo/cMCyzpYtvhrPAhsubG21mELX48RAcjSVCRctW8dG8wrDIla
role: admin
onboarded_at: <%= 3.days.ago %>
ai_enabled: true
+ show_sidebar: true
+ show_ai_sidebar: true
+ ui_layout: dashboard
family_member:
family: dylan_family
first_name: Jakob
last_name: Dylan
email: jakobdylan@yahoo.com
- password_digest: $2a$12$XoNBo/cMCyzpYtvhrPAhsubG21mELX48RAcjSVCRctW8dG8wrDIla
+ password_digest: $2a$12$XoNBo/cMCyzpYtvhrPAhsubG21mELX48RAcjSVCRctW8dG8wrDIla
onboarded_at: <%= 3.days.ago %>
ai_enabled: true
+ show_sidebar: true
+ show_ai_sidebar: true
+ ui_layout: dashboard
new_email:
family: empty
@@ -43,6 +55,22 @@ new_email:
last_name: User
email: user@example.com
unconfirmed_email: new@example.com
- password_digest: $2a$12$XoNBo/cMCyzpYtvhrPAhsubG21mELX48RAcjSVCRctW8dG8wrDIla
+ password_digest: $2a$12$XoNBo/cMCyzpYtvhrPAhsubG21mELX48RAcjSVCRctW8dG8wrDIla
onboarded_at: <%= Time.current %>
- ai_enabled: true
\ No newline at end of file
+ ai_enabled: true
+ show_sidebar: true
+ show_ai_sidebar: true
+ ui_layout: dashboard
+
+intro_user:
+ family: empty
+ first_name: Intro
+ last_name: User
+ email: intro@example.com
+ password_digest: $2a$12$XoNBo/cMCyzpYtvhrPAhsubG21mELX48RAcjSVCRctW8dG8wrDIla
+ onboarded_at: <%= 1.day.ago %>
+ role: member
+ ai_enabled: true
+ show_sidebar: false
+ show_ai_sidebar: false
+ ui_layout: intro
diff --git a/test/models/assistant/configurable_test.rb b/test/models/assistant/configurable_test.rb
new file mode 100644
index 00000000000..0dc65c728b9
--- /dev/null
+++ b/test/models/assistant/configurable_test.rb
@@ -0,0 +1,21 @@
+require "test_helper"
+
+class AssistantConfigurableTest < ActiveSupport::TestCase
+ test "returns dashboard configuration by default" do
+ chat = chats(:one)
+
+ config = Assistant.config_for(chat)
+
+ assert_not_empty config[:functions]
+ assert_includes config[:instructions], "You help users understand their financial data"
+ end
+
+ test "returns intro configuration without functions" do
+ chat = chats(:intro)
+
+ config = Assistant.config_for(chat)
+
+ assert_equal [], config[:functions]
+ assert_includes config[:instructions], "stage of life"
+ end
+end
diff --git a/test/models/user_test.rb b/test/models/user_test.rb
index 16aa8cc72db..7c4e108da5e 100644
--- a/test/models/user_test.rb
+++ b/test/models/user_test.rb
@@ -152,4 +152,20 @@ def setup
ensure
Setting.openai_access_token = previous
end
+
+ test "intro layout collapses sidebars and enables ai" do
+ user = User.new(
+ family: families(:empty),
+ email: "intro-new@example.com",
+ password: "Password1!",
+ password_confirmation: "Password1!",
+ ui_layout: :intro
+ )
+
+ assert user.save, user.errors.full_messages.to_sentence
+ assert user.ui_layout_intro?
+ assert_not user.show_sidebar?
+ assert_not user.show_ai_sidebar?
+ assert user.ai_enabled?
+ end
end