Commit Graph

4 Commits

Author SHA1 Message Date
Guillem Arias
628e1f89bb feat(goals/new): standalone page render when not in a Turbo frame
Direct nav to /goals/new used to render the index page with an empty
modal frame because the entire template was wrapped in DS::Dialog.
The URL was effectively un-shareable.

Branch on turbo_frame_request? — Turbo Frame requests still render
the DS::Dialog wrapper (the existing in-modal flow on the index page
keeps working). Non-frame requests render a standalone page-level
header (h1 + subtitle + icon) followed by the form_stepper partial.
Same Stimulus controller, same data-goal-stepper-modal-subtitle
selector, so the stepper's subtitle update path works identically.

Controller sets @breadcrumbs so the standalone variant gets the
Home > Goals > New goal trail.

Verified both paths via Playwright: direct GET renders standalone
form with h1 "New goal" + no dialog; click-from-index opens the
DS::Dialog with the stepper inside.
2026-05-11 20:43:41 +02:00
Guillem Arias
f4b360bb96 feat(goals/edit): funding-accounts editor in the edit modal
Previously a user who linked the wrong account at creation had to
delete + recreate the goal. Now the edit modal carries the same
funding-accounts checkbox group as Step 1 of the stepper, pre-checked
with the goal's current links.

- GoalsController#edit loads @linkable_accounts + @currently_linked_account_ids.
- #update accepts account_ids; when supplied, runs the create / update
  inside a Goal.transaction and syncs linked accounts via
  sync_linked_accounts! (set-diff: destroy_all unselected goal_accounts,
  create the new ones). Validates at least one account before touching
  goal_accounts so the user gets a clean re-render.
- Removing an account preserves the goal's existing contributions —
  GoalContribution#account_must_be_linked_to_goal only fires on save,
  so historical rows stay valid.
- _form_edit partial accepts new locals; edit.html.erb threads them
  through.
- 3 new controller tests: identity-only patch leaves links intact;
  account_ids patch replaces the link set; empty account_ids
  re-renders with error.
2026-05-11 20:28:45 +02:00
Guillem Arias
c018f95dfa perf+ux(goals): grouped state-count + friendly 404 for stale/cross-family deep links
- index: STATE_FILTERS count loop replaced with single Current.family.goals.group(:state).count + per-state lookup. 5 SQL queries -> 1.
- GoalsController + GoalContributionsController: rescue_from ActiveRecord::RecordNotFound -> redirect_to goals_path with a flash. Affects stale deep links AND cross-family access (previously bare 404 -> Chrome error page). Test for cross-family access updated to assert the redirect + flash key.
- New locale key goals.errors.not_found.
2026-05-11 20:19:00 +02:00
Guillem Arias
9b61e4a41b refactor: rename Savings Goals feature to Goals
User-facing rename + structural rename. Feature is now called just
"Goals" everywhere — page title, sidebar nav, modal headings, flash
messages, AI assistant tool. Code identifiers follow:

- Models: SavingsGoal → Goal, SavingsContribution → GoalContribution,
  SavingsGoalAccount → GoalAccount.
- Tables: savings_goals → goals, savings_contributions → goal_contributions,
  savings_goal_accounts → goal_accounts. FK columns savings_goal_id →
  goal_id. New migration db/migrate/20260511100003_rename_savings_to_goals.rb
  uses rename_table + rename_column; PG handles index renaming and FK
  redirection automatically.
- Controllers: SavingsGoalsController → GoalsController,
  SavingsContributionsController → GoalContributionsController.
- Routes: /savings_goals → /goals, nested /goals/:id/contributions
  (resource name shifts; old route name aliases dropped).
- ViewComponent namespace: Savings::* → Goals::*. Component class
  names drop their redundant "Goal" prefix where the namespace already
  carries it: Savings::GoalCardComponent → Goals::CardComponent,
  Savings::GoalAvatarComponent → Goals::AvatarComponent. Others keep
  their names (Goals::ProgressRingComponent, Goals::StatusPillComponent,
  Goals::AccountStackComponent, Goals::FundingAccountsBreakdownComponent).
- Stimulus controllers: savings_goal_* → goal_*, savings_goals_filter
  → goals_filter. Stimulus identifiers in data-controller / data-*
  attributes follow.
- Locale keys: savings_goals: → goals: (top level), savings_contributions:
  → goal_contributions: (top level). All t() callers updated.
- AI assistant tool: Assistant::Function::CreateSavingsGoal →
  Assistant::Function::CreateGoal, tool name "create_savings_goal" →
  "create_goal", description / response text updated.
- Sidebar nav label "Savings" → "Goals". Goals/show + index page title
  "Savings" → "Goals". Empty goals_section heading/subtitle dropped
  (duplicated the page title post-rename).

Original migrations create_savings_goals / create_savings_goal_accounts /
create_savings_contributions remain untouched so historical replay
still works; the rename migration runs on top.
2026-05-11 20:08:32 +02:00