Commit Graph

2334 Commits

Author SHA1 Message Date
soky srm
79e8469102 Merge pull request #1055 from ChakibMoMi/feature/privacy-mode
Add privacy mode to blur financial data across the app
2026-03-22 11:06:03 +01:00
sokiee
b6b093c578 Extend privacy headers
Extend privacy mode coverage to remaining financial views

  Transfers, trades, valuations, and holdings detail views were missing
  the privacy-sensitive class, leaving amounts visible when privacy mode
  was enabled. Also adds blur to the summary card partial (used by credit
  cards, loans, etc.), account chart balances, and time series chart
  containers (dashboard net worth and per-account charts).
2026-03-22 10:57:56 +01:00
sokiee
71c0735824 Linter 2026-03-22 10:48:54 +01:00
sokiee
9693477761 Merge main: resolve grid layout conflicts, add missing privacy-sensitive coverage
Resolve merge conflicts in investment summary/performance views where main's
grid layout refactoring conflicted with privacy-sensitive class additions.
Also add privacy-sensitive to transaction list amounts, transaction detail
header, and sankey cashflow chart containers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 10:46:52 +01:00
juan
573745af78 Use edge-based insertion for symmetrical up/down drag reordering
Replace center-point distance algorithm with edge-crossing logic.
The swap threshold is now at the midpoint of the gap between adjacent
sections, so moving a section up or down requires equal distance
regardless of section height. Also handles 2-column grid layout by
filtering to same-column siblings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 13:08:15 -07:00
LPW
e43a8f295d Bump pipelock image from 0.3.2 to 1.5.0 (#1238)
* Bump pipelock image from 0.3.2 to 1.5.0

* Releasing via `alpha`

---------

Co-authored-by: Juan José Mata <jjmata@jjmata.com>
2026-03-21 15:32:07 +01:00
Yacine Kanzari
62a5255e02 Fix select is hidden inside dialog (#1196)
* Fix Select hidden inside Dialog

* Fix regression in Drawer
2026-03-21 15:14:11 +01:00
Dream
46494bc07f Fix rule condition merchant dropdown to show all merchants (#1240)
The "transaction merchant" condition filter used `family.assigned_merchants`
which only returned merchants already assigned to a transaction. This meant
newly created merchants wouldn't appear in the rule condition dropdown until
manually assigned to a transaction.

Changed to `family.available_merchants` which includes all family merchants
(even unassigned ones), provider merchants on transactions, and recently
unlinked merchants — consistent with the transaction form merchant selector.

Fixes #1197

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 10:48:11 +01:00
Juan José Mata
30ef253536 Fix pgvector migration to only run when explicitly configured (#1239)
The migration previously checked only if the pgvector extension was
available on the PostgreSQL server. In production Docker environments
(e.g., managed Postgres services), the extension may be present but the
database user lacks superuser privileges to enable it, causing the
migration to fail.

Now the migration requires VECTOR_STORE_PROVIDER=pgvector to be set
before attempting to enable the extension, matching the application's
own configuration pattern in VectorStore::Registry.

https://claude.ai/code/session_017YYBFXGwamXpGwwDZxe8Pv

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-21 09:24:11 +01:00
Juan José Mata
8bc4cae728 Add Plaid migration constant alias (#1235)
Restore the original migration constant name as an alias so old references still resolve during production boot and migration loading. Add a regression test covering the legacy alias.
2026-03-21 01:03:19 +01:00
Juan José Mata
445206a02f Fix NameError: rename migration file to match class name (#1234)
The migration file was named scope_plaid_account_uniqueness_to_item.rb
but the class inside was ScopePlaidItemUniqueness (renamed to avoid
secret-scanner false positive). Rails derives the expected constant from
the filename, causing a NameError in production.

Rename the file to scope_plaid_item_uniqueness.rb to match the class.

https://claude.ai/code/session_015AekNFxveK4W71nLxiqbX9

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-21 00:24:01 +01:00
soky srm
ae5b23fe67 Initial split transaction support (#1230)
* Initial split transaction support

* Add support to unsplit and edit split

* Update show.html.erb

* FIX address reviews

* Improve UX

* Update show.html.erb

* Reviews

* Update edit.html.erb

* Add parent category to dialog

* Update en.yml

* Add UI indication to totals

* FIX ui update

* Add category select like rest of app

---------

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-03-20 21:19:30 +01:00
Dream
6d22514c01 feat(vector-store): Implement pgvector adapter for self-hosted RAG (#1211)
* Add conditional migration for vector_store_chunks table

Creates the pgvector-backed chunks table when VECTOR_STORE_PROVIDER=pgvector.
Enables the vector extension, adds store_id/file_id indexes, and uses
vector(1024) column type for embeddings.

* Add VectorStore::Embeddable concern for text extraction and embedding

Shared concern providing extract_text (PDF via pdf-reader, plain-text as-is),
paragraph-boundary chunking (~2000 chars, ~200 overlap), and embed/embed_batch
via OpenAI-compatible /v1/embeddings endpoint using Faraday. Configurable via
EMBEDDING_MODEL, EMBEDDING_URI_BASE, with fallback to OPENAI_* env vars.

* Implement VectorStore::Pgvector adapter with raw SQL

Replaces the stub with a full implementation using
ActiveRecord::Base.connection with parameterized binds. Supports
create_store, delete_store, upload_file (extract+chunk+embed+insert),
remove_file, and cosine-similarity search via the <=> operator.

* Add registry test for pgvector adapter selection

* Configure pgvector in compose.example.ai.yml

Switch db image to pgvector/pgvector:pg16, add VECTOR_STORE_PROVIDER,
EMBEDDING_MODEL, and EMBEDDING_DIMENSIONS env vars, and include
nomic-embed-text in Ollama's pre-loaded models.

* Update pgvector docs from scaffolded to ready

Document env vars, embedding model setup, pgvector Docker image
requirement, and Ollama pull instructions.

* Address PR review feedback

- Migration: remove env guard, use pgvector_available? check so it runs
  on plain Postgres (CI) but creates the table on pgvector-capable servers.
  Add NOT NULL constraints on content/embedding/metadata, unique index on
  (store_id, file_id, chunk_index).
- Pgvector adapter: wrap chunk inserts in a DB transaction to prevent
  partial file writes. Override supported_extensions to match formats
  that extract_text can actually parse.
- Embeddable: add hard_split fallback for paragraphs exceeding CHUNK_SIZE
  to avoid overflowing embedding model token limits.

* Bump schema version to include vector_store_chunks migration

CI uses db:schema:load which checks the version — without this bump,
the migration is detected as pending and tests fail to start.

* Update 20260316120000_create_vector_store_chunks.rb

---------

Co-authored-by: sokiee <sokysrm@gmail.com>
2026-03-20 17:01:31 +01:00
soky srm
2cdddd28d7 FIX schema drift and snaptrade and mercury issues (#1232) 2026-03-20 14:52:09 +01:00
dependabot[bot]
8e19cee329 chore(deps): bump bcrypt from 3.1.20 to 3.1.22 (#1227)
Bumps [bcrypt](https://github.com/bcrypt-ruby/bcrypt-ruby) from 3.1.20 to 3.1.22.
- [Release notes](https://github.com/bcrypt-ruby/bcrypt-ruby/releases)
- [Changelog](https://github.com/bcrypt-ruby/bcrypt-ruby/blob/master/CHANGELOG)
- [Commits](https://github.com/bcrypt-ruby/bcrypt-ruby/compare/v3.1.20...v3.1.22)

---
updated-dependencies:
- dependency-name: bcrypt
  dependency-version: 3.1.22
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-20 14:20:03 +01:00
dependabot[bot]
e2cfc36ee9 chore(deps): bump json from 2.18.1 to 2.19.2 (#1225)
Bumps [json](https://github.com/ruby/json) from 2.18.1 to 2.19.2.
- [Release notes](https://github.com/ruby/json/releases)
- [Changelog](https://github.com/ruby/json/blob/master/CHANGES.md)
- [Commits](https://github.com/ruby/json/compare/v2.18.1...v2.19.2)

---
updated-dependencies:
- dependency-name: json
  dependency-version: 2.19.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-19 15:35:43 +01:00
Clayton
1191d9f7d8 feat: scope Mercury account uniqueness to mercury_item (#1032)
* feat: scope Mercury account uniqueness to mercury_item

* feat: extend to all other providers

* fix: add uniqueness test

* fix: lint

* fix: test

* fix: coderabbit comment

* fix: coderabbit comment

* fix: coderabbit comment

* fix: update

* fix: lint

* fix: update

* fix: update
2026-03-19 15:17:55 +01:00
juan
5336ec159f Increase touch hold delay to 1000ms for more reliable scroll behavior
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 20:22:53 -07:00
juan
528f5f32a6 Revert "Bind touch events to drag handle only, not entire section"
This reverts commit 55ab9aed6e.
2026-03-18 20:22:45 -07:00
juan
55ab9aed6e Bind touch events to drag handle only, not entire section
Touch events on the full section meant any touch on the header could
trigger hold-to-drag. Now touch events are scoped to the grip handle
button, which is made visible on mobile (was hidden lg:block before).
Desktop drag-and-drop via draggable attribute is unchanged.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 17:56:59 -07:00
juan
31a5ac156b Increase hold delay to 750ms and use Euclidean distance for cancellation
Use total distance (diagonal-aware) instead of per-axis thresholds
to better detect scrolling gestures that travel diagonally.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 16:20:48 -07:00
github-actions[bot]
b68c767b34 Bump version to next iteration after v0.6.9-alpha.5 release 2026-03-17 18:51:56 +00:00
Juan José Mata
53f478a77b Add scheduled DemoFamilyRefreshJob to rebuild demo data daily (#1217)
* Add scheduled demo family refresh job

Rebuild demo data daily at 5am UTC by anonymizing and enqueueing deletion of the existing demo family while immediately generating new sample data. Add super-admin email notifications with 24-hour session and signup metrics, plus tests for the new job and mailer.

* Delete demo monitoring key before family refresh

Ensure DemoFamilyRefreshJob removes ApiKey::DEMO_MONITORING_KEY from the old demo family before enqueueing async family destruction and generating replacement sample data. Adds a regression assertion that the key is gone before generator execution.
2026-03-17 19:41:26 +01:00
Milo
26aa260fb1 Respect manually selected account type in SimpleFIN liability logic (#1214)
* Fix: don't let inferred liability overrride linked account type

* Update file tests to match new code, fix Ruby linter warning.
2026-03-17 18:22:07 +01:00
Juan José Mata
9bc166cb2a Increase to 750ms 2026-03-17 17:52:18 +01:00
Jasper Abbink
872e455213 Fallback to transaction_date when booking_date and value_date are unavailable (#1215) 2026-03-17 15:26:24 +01:00
juan
9dcb74c46b Update hold delay comment to reflect press-and-hold behavior
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 16:04:23 -07:00
juan
970047d36c Fix dashboard mobile: require press-and-hold to reorder sections
On mobile, swiping through dashboard sections (investments, net worth,
balance sheets, etc.) accidentally triggers drag-to-reorder because
the hold delay is only 150ms with no movement cancellation.

- Increase hold delay from 150ms to 500ms
- Cancel drag activation if finger moves >10px before hold triggers,
  allowing normal scroll gestures to pass through unimpeded

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 15:46:39 -07:00
Juan José Mata
a377ed7552 Remove unused DeveloperMessage model (#1207)
DeveloperMessage was a debug-only STI subclass of Message that was never
created by any production code. Remove the model, view partial, test,
and fixtures, and simplify Chat#conversation_messages accordingly.

https://claude.ai/code/session_012pm5HKGKFs1tpAsvXMr4Tp

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-16 20:22:11 +01:00
Andrei Onel
a0b1029ba9 Documentation for review AI Assistant features, MCP and API additions (#1168)
* Create MCP server endpoint documentation

* Add Assistant Architecture section to AI documentation

* Add Users API documentation for account reset and delete endpoints

* Document Pipelock CI security scanning in contributing guide

* fix: correct scope and error codes in Users API documentation

* Exclude `docs/hosting/ai.md` from Pipelock scan

---------

Co-authored-by: askmanu[bot] <192355599+askmanu[bot]@users.noreply.github.com>
Co-authored-by: Juan José Mata <jjmata@jjmata.com>
2026-03-16 18:24:28 +01:00
Serge L
5a43f123c2 feat(balance): Incremental ForwardCalculator — only recalculate from changed date forward (#1151)
* feat(balance): incremental ForwardCalculator — only recalculate from changed date forward

When a Sync record carries a window_start_date, ForwardCalculator now
seeds its starting balances from the persisted DB balance for
window_start_date - 1, then iterates only from window_start_date to
calc_end_date.  This avoids recomputing every daily balance on a
long-lived account when a single transaction changes.

Key changes:
- Account::Syncer passes sync.window_start_date to Balance::Materializer
- Balance::Materializer accepts window_start_date and forwards it to
  ForwardCalculator; purge_stale_balances uses opening_anchor_date as the
  lower bound in incremental mode so pre-window balances are not deleted
- Balance::ForwardCalculator accepts window_start_date; resolve_starting_balances
  loads end_cash_balance/end_non_cash_balance from the prior DB record and
  falls back to full recalculation when no prior record exists
- Tests added for incremental correctness, fallback behaviour, and purge safety

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

# Conflicts:
#	app/models/balance/materializer.rb

* Enhance fallback logic on ForwardCalculator and Materializer

* fix(balance): address CodeRabbit review issues on incremental ForwardCalculator

- materializer.rb: handle empty sorted_balances in incremental mode by still
  purging stale tail balances beyond window_start_date - 1, preventing orphaned
  future rows when a transaction is deleted and the recalc window produces no rows

- materializer_test.rb: stub incremental? alongside calculate in the incremental
  sync test so the guard in ForwardCalculator#incremental? doesn't raise when
  @fell_back is nil (never set because calculate was stubbed out)

- materializer_test.rb: correct window_start_date in the fallback test from
  3.days.ago to 2.days.ago so window_start_date - 1 hits a date with no
  persisted balance, correctly triggering full recalculation instead of
  accidentally seeding from the stale wrong_pre_window balance

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(balance): multi-currency fallback to full recalculation and add corresponding tests

* address coderabbit comment about test

* Make the foreign-currency precondition explicit in the test setup.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 21:29:01 +01:00
Serge L
5aa808e668 Feat: Add default user account and consolidate account actions in menu (#1130)
* feat: Add default account for manual transaction entries (#1061)

Allow users to designate a default account that auto-selects
in the transaction creation form. Also consolidates account list
actions (edit, link/unlink, enable/disable) into a meatball menu.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* - handle context menu width on mobile
- restrict default account to depository types only
- added FR, ES and DE i18n files

* - Add credit card accounts can also be used as default
- Moved logic into controller

* Scope context menu max-width to accounts menu only
- decouples the width constraint from the shared DS::Menu component by introducing an optional max_width param

* fix ci test and address issues raised by coderabbit and codex

* Address CodeRabbit review feedback

- Use .present? for institution_name guards to avoid empty UI artifacts
- Align "Set default" menu visibility with actual preselection eligibility
  (active + unlinked + supports_default?) to prevent drift between UI and model
- Keep disabled star visible when account is already default but now ineligible

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add eligible_for_transaction_default? predicate to Account model

Consolidates active + unlinked + supports_default? checks into a single
shared predicate used by the controller, view, and user model guard,
preventing a direct PATCH from bypassing UI eligibility rules.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Added "Unset default" option
Added negative test for default account
Removed duplicated logic for account.eligible_for_transaction_default

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 21:26:26 +01:00
Renzo
581d3684b2 feat: Add duplicate button when a transaction is selected (#1123)
* feat: Add duplicate button when a transaction is selected

* feat: add merchant field

* feat: add duplicate transaction btn 2
2026-03-15 17:05:01 +01:00
Juan José Mata
3ac19bae2e Fix tests 2026-03-15 00:49:14 +01:00
Juan José Mata
cade5b22f7 Document admin-only reset auth in OpenAPI docs (#1198)
* Document admin-only reset auth in OpenAPI docs

The DELETE /api/v1/users/reset endpoint now requires admin role
(ensure_admin). Update the rswag spec to:
- Set default user role to admin so the 200 test passes
- Add a 403 response case for non-admin users with read_write scope
- Clarify the description notes admin requirement
- Add SuccessMessage schema and users paths to openapi.yaml

https://claude.ai/code/session_01Tj8ToLRmVg5HLmHwq9KKDY

* Consolidate duplicate 403 responses for reset endpoint

OpenAPI keys responses by status code, so two 403 blocks caused the
first (insufficient scope) to be silently overwritten by the second
(non-admin). Merge into a single 403 whose description covers both
causes: requires read_write scope and admin role. The test exercises
the read-only key path which hits 403 via scope check.

https://claude.ai/code/session_01Tj8ToLRmVg5HLmHwq9KKDY

* Em-dash out of messages.

* Fix tests

* Fix tests

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-15 00:23:38 +01:00
Ellion Blessan
98ae6782dc feat(transaction): add support for file attachments using Active Storage (#713)
* feat(transaction): add support for file attachments using Active Storage

* feat(attachments): implement transaction attachments with upload, show, and delete functionality

* feat(attachments): enhance attachment upload functionality to support multiple files and improved error handling

* feat(attachments): add attachment upload form and display functionality in transaction views

* feat(attachments): implement attachment validation for count, size, and content type; enhance upload form with validation hints

* fix(attachments): use correct UI components

* feat(attachments): Implement Turbo Stream responses for creating and deleting transaction attachments.

* fix(attachments): include auth in activestorage controller

* test(attachments): add test coverage for turbostream and auth

* feat(attachments): extract strings to i18n

* fix(attachments): ensure only newly added attachments are purged when transaction validation fails.

* fix(attachments): validate attachment params

* refactor(attachments): use stimulus declarative actions

* fix(attachments): add auth for other representations

* refactor(attachments): use Browse component for attachment uploads

* fix(attachments): reject empty values on attachment upload

* fix(attachments): hide the upload form if reached max uploads

* fix(attachments): correctly purge only newly added attachments on upload failure

* fix(attachments): ensure attachment count limit is respected within a transaction lock

* fix(attachments): update attachment parameter handling to avoid `ParameterMissing` errors.

* fix(components): adjust icon_only logic for buttonish

---------

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-03-14 23:56:27 +01:00
AdamWHY2K
3a869c760e feat: add pending transaction manual merging tool (#1088)
* refactor: use a map of providers that support pending transactions

* feat: add pending transaction manual merging tool

* fix(coderabbit): validate posted_entry_id against eligible posted candidates server-side

* fix(coderabbit): validate offset for negative numbers

* fix(coderabbit): check if pending_duplicate_candidates has_more in one transaction

* refactor: use list of radio buttons for better pagination

* chore: show current transaction range in paginated view

* chore: whitespace

chore: whitespace
2026-03-14 20:32:13 +01:00
Serge L
57199d6eb9 Feat: Add QIF (Quicken Interchange Format) import functionality (#1074)
* Feat: Add QIF (Quicken Interchange Format) import functionality
- Add the ability to import QIF files for users coming from Quicken
- Includes categories and tags
- Comprehensive tests for QifImport, including parsing, row generation, and import functionality.
- Ensure handling of hierarchical categories (ex "Home:Home Improvement" is imported as Parent:Child)

* Fix QIF import issues raised in code review

- Fix two-digit year windowing in QIF date parser (e.g. '99 → 1999, not 2099)
- Fix ArgumentError from invalid `undef: :raise` encoding option
- Nil-safe `leaf_category_name` with blank guard and `.to_s` coercion
- Memoize `qif_account_type` to avoid re-parsing the full QIF file
- Add strong parameters (`selection_params`) to QifCategorySelectionsController
- Wrap all mutations in DB transactions in uploads and category-selections controllers
- Skip unchanged tag rows (only write rows where tags actually differ)
- Replace hardcoded strings with i18n keys across QIF views and nav
- Fix potentially colliding checkbox/label IDs in category selection view
- Improve keyboard accessibility: use semantic `<label>` for file picker area

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix QIF import test count and Brakeman mass assignment warning

- Update ImportsControllerTest to expect 4 disabled import options (was 3),
  accounting for the new QIF import type added in this branch
- Remove :account_id from upload_params permit list; it was never accessed
  through strong params (always via params.dig with Current.family scope),
  so this resolves the Brakeman high-confidence mass assignment warning

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix: QIF import security, safety, and i18n issues raised in code review
- Added french, spanish and german translations for newly added i18n keys
- Replace params.dig(:import, :account_id) with a proper strong-params
  accessor (import_account_id) in UploadsController to satisfy Rails
  parameter filtering requirements
- Guard ImportsController#show against QIF imports reaching the publish
  screen before a file has been uploaded, preventing an unrescued error
  on publish
- Gate the QIF "Clean" nav step link on import.uploaded? to prevent
  routing to CleansController with an unconfigured import (which would
  raise "Unknown import type: QifImport" via ImportsHelper)
- Replace hard-coded "txn" pluralize calls in the category/tag selection
  view with t(".txn_count") and add pluralization keys to the locale file
- Localize all hard-coded strings in the QIF upload section of
  uploads/show.html.erb and add corresponding en.yml keys
- Convert the CSV upload drop zone from a clickable <div> (JS-only) to
  a semantic <label> element, making it keyboard-accessible without
  JavaScript

* Fix: missing translations keys

* Add icon mapping and random color assignment to new categories

* fix a lint issue

* Add a warning about splits and some plumbing for future support.
Updated locales.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 20:22:39 +01:00
Juan José Mata
5b0ddd06a4 Add post-trial inactive Family cleanup with data archival (#1199)
* Add post-trial inactive family cleanup with data archival

Families that expire their trial without subscribing now get cleaned up
daily. Empty families (no accounts) are destroyed immediately after a
14-day grace period. Families with meaningful data (12+ transactions,
some recent) get their data exported as NDJSON/ZIP to an ArchivedExport
record before deletion, downloadable via a token-based URL for 90 days.

- Add InactiveFamilyCleanerJob (scheduled daily at 4 AM, managed mode only)
- Add ArchivedExport model with token-based downloads
- Add inactive_trial_for_cleanup scope and requires_data_archive? to Family
- Extend DataCleanerJob to purge expired archived exports
- Add ArchivedExportsController for unauthenticated token downloads

https://claude.ai/code/session_01LR3Vo83R5s5SczYe6T33dQ

* Fix Brakeman redirect warning in ArchivedExportsController

Use rails_blob_path instead of redirecting directly to the ActiveStorage
attachment, which avoids the allow_other_host: true open redirect.

https://claude.ai/code/session_01LR3Vo83R5s5SczYe6T33dQ

* Update schema.rb with archived_exports table

Add the archived_exports table definition to schema.rb to match
the pending migration, unblocking CI tests.

https://claude.ai/code/session_01LR3Vo83R5s5SczYe6T33dQ

* Fix broken CI tests for ArchivedExports and InactiveFamilyCleaner

- ArchivedExportsController 404 test: use assert_response :not_found
  instead of assert_raises since Rails rescues RecordNotFound in
  integration tests and returns a 404 response.
- InactiveFamilyCleanerJob test: remove assert_no_difference on
  Family.count since the inactive_trial fixture gets cleaned up by
  the job. The test intent is to verify the active family survives,
  which is checked by assert Family.exists?.

https://claude.ai/code/session_01LR3Vo83R5s5SczYe6T33dQ

* Wrap ArchivedExport creation in a transaction

Ensure the ArchivedExport record and its file attachment succeed
atomically. If the attach fails, the transaction rolls back so no
orphaned record is left without an export file.

https://claude.ai/code/session_01LR3Vo83R5s5SczYe6T33dQ

* Store only a digest of the download token for ArchivedExport

Replace plaintext download_token column with download_token_digest
(SHA-256 hex). The raw token is generated via SecureRandom on create,
exposed transiently via attr_reader for use in emails/logs, and only
its digest is persisted. Lookup uses find_by_download_token! which
digests the incoming token before querying.

https://claude.ai/code/session_01LR3Vo83R5s5SczYe6T33dQ

* Remove raw download token from cleanup job logs

Log a truncated digest prefix instead of the raw token, which is the
sole credential for the unauthenticated download endpoint.

https://claude.ai/code/session_01LR3Vo83R5s5SczYe6T33dQ

* Fix empty assert_no_difference block in cleaner job test

Wrap the perform_now call with both assertions so the
ArchivedExport.count check actually exercises the job.

https://claude.ai/code/session_01LR3Vo83R5s5SczYe6T33dQ

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-14 20:14:18 +01:00
github-actions[bot]
f0902aa8e4 Bump version to next iteration after v0.6.9-alpha.4 release 2026-03-14 10:45:08 +00:00
Juan José Mata
02af8463f6 Administer invitations in /admin/users (#1185)
* Add invited users with delete button to admin users page

Shows pending invitations per family below active users in /admin/users/.
Each invitation row has a red Delete button aligned with the role column.
Alt/option-clicking any Delete button changes all invitation button labels
to "Delete All" and destroys all pending invitations for that family.

- Add admin routes: DELETE /admin/invitations/:id and DELETE /admin/families/:id/invitations
- Add Admin::InvitationsController with destroy and destroy_all actions
- Load pending invitations grouped by family in users controller index
- Render invitation rows in a dashed-border tbody below active user rows
- Add admin-invitation-delete Stimulus controller for alt-click behavior
- Add i18n strings for invitation UI and flash messages

https://claude.ai/code/session_01F8WaH5TmtdUWwhHnVoQ6Gm

* Fix destroy_all using params[:id] from member route

The member route /admin/families/:id/invitations sets params[:id],
not params[:family_id], so Family.find was always receiving nil.

https://claude.ai/code/session_01F8WaH5TmtdUWwhHnVoQ6Gm

* Fix translation key in destroy_all to match locale

t(".success_all") looked up a nonexistent key; the locale defines
admin.invitations.destroy_all.success, so t(".success") is correct.

https://claude.ai/code/session_01F8WaH5TmtdUWwhHnVoQ6Gm

* Scope bulk delete to pending invitations and allow re-inviting emails

- destroy_all now uses family.invitations.pending.destroy_all so accepted
  and expired invitation history is preserved
- Replace blanket email uniqueness validation with a custom check scoped
  to pending invitations only, so the same email can be invited again
  after an invitation is deleted or expires

https://claude.ai/code/session_01F8WaH5TmtdUWwhHnVoQ6Gm

* Drop unconditional unique DB index on invitations(email, family_id)

The model-level uniqueness check was already scoped to pending
invitations, but the blanket unique index on (email, family_id)
still caused ActiveRecord::RecordNotUnique when re-inviting an
email that had any historical invitation record in the same family
(e.g. after an accepted invite or after an account deletion).

Replace it with no DB-level unique constraint — the
no_duplicate_pending_invitation_in_family model validation is the
sole enforcer and correctly scopes uniqueness to pending rows only.

https://claude.ai/code/session_01F8WaH5TmtdUWwhHnVoQ6Gm

* Replace blanket unique index with partial unique index on pending invitations

Instead of dropping the DB-level uniqueness constraint entirely, replace
the unconditional unique index on (email, family_id) with a partial unique
index scoped to WHERE accepted_at IS NULL. This enforces the invariant at
the DB layer (no two non-accepted invitations for the same email in a
family) while allowing re-invites once a prior invitation has been accepted.

https://claude.ai/code/session_01F8WaH5TmtdUWwhHnVoQ6Gm

* Fix migration version and make remove_index reversible

- Change Migration[8.0] to Migration[7.2] to match the rest of the codebase
- Pass column names to remove_index so Rails can reconstruct the old index on rollback

https://claude.ai/code/session_01F8WaH5TmtdUWwhHnVoQ6Gm

---------

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-03-14 11:32:33 +01:00
Chase Martin
50f3a5c030 Fix Plaid link script loading and first-sync account linking (#1165)
* fix: Handle conditional loading of Plaid Link script

* fix: Plaid accounts not linking on first sync

* fix: Handle Plaid script loading edge cases

* fix: Use connection token for disconnect safety and retry failed script loads

* fix: Destroy Plaid Link handler on controller disconnect

* fix: Add timeout to Plaid CDN script loader to prevent deadlocks
2026-03-13 08:11:51 +01:00
Juan José Mata
3adc011df0 Require admin role for API family reset (#1189)
Prevent non-admin users with read_write API access from triggering family-wide reset jobs via /api/v1/users/reset.
2026-03-13 08:07:30 +01:00
Alessio Cappa
80026aeee4 Add "Transaction account" as rule condition filter (#1186)
* feat: Add transaction account as rule condition filter

* Update app/models/rule/condition_filter/transaction_account.rb

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Alessio Cappa <104093777+alessiocappa@users.noreply.github.com>

---------

Signed-off-by: Alessio Cappa <104093777+alessiocappa@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-03-13 07:59:45 +01:00
Alessio Cappa
9888b3071c feat: move account logo determination in dedicated method (#1190) 2026-03-13 07:56:29 +01:00
sentry[bot]
d7a3b0cacd Fix: Remove blank amount from transaction entry parameters (#1178)
Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com>
2026-03-11 17:45:42 +01:00
soky srm
e1ff6d46ee Make categories global (#1160)
* Make categories global

This solves us A LOT of cash flow and budgeting problems.

* Update schema.rb

* Update auto_categorizer.rb

* Update income_statement.rb

* FIX budget sub-categories

* FIX sub-categories and tests

* Add 2 step migration
2026-03-11 15:54:01 +01:00
Claude
ef4750c2c5 Skip reformatting blank amount fields on currency change
parseLocaleFloat returns 0 for empty strings, which caused blank amount
fields to be overwritten with "0.00" when the user changed currency.
Guard against this by checking for a non-empty value before parsing.

https://claude.ai/code/session_01ThfszjiCmbDDPyb4TZqk2X
2026-03-10 17:15:44 +00:00
Juan José Mata
7ae9077935 Add default family selection for invite-only onboarding mode (#1174)
* Add default family selection for invite-only onboarding mode

When onboarding is set to invite-only, admins can now choose a default
family that new users without an invitation are automatically placed into
as members, instead of creating a new family for each signup.

https://claude.ai/code/session_01U9KgikKjV6xbyBZ5wMYsYx

* Restrict invite codes and onboarding settings to super_admin only

The Invite Codes section on /settings/hosting was visible to any
authenticated user via the show action, leaking all family names/IDs
through the default-family dropdown. This tightens access:

- Hide the entire Invite Codes section in the view behind super_admin?
- Add before_action :ensure_super_admin to InviteCodesController for
  all actions (index, create, destroy), replacing the inline admin? check
- Add ensure_super_admin_for_onboarding filter on hostings#update that
  blocks non-super_admin users from changing onboarding_state or
  invite_only_default_family_id

https://claude.ai/code/session_01U9KgikKjV6xbyBZ5wMYsYx

* Fix tests for super_admin-only invite codes and onboarding settings

- Hostings controller test: sign in as sure_support_staff (super_admin)
  for the onboarding_state update test, since ensure_super_admin_for_onboarding
  now requires super_admin role
- Invite codes tests: use super_admin fixture for the success case and
  verify that a regular admin gets redirected instead of raising StandardError

https://claude.ai/code/session_01U9KgikKjV6xbyBZ5wMYsYx

* Fix system test to use super_admin for self-hosting settings

The invite codes section is now only visible to super_admin users,
so the system test needs to sign in as sure_support_staff to find
the onboarding_state select element.

https://claude.ai/code/session_01U9KgikKjV6xbyBZ5wMYsYx

* Skip invite code requirement when a default family is configured

When onboarding is invite-only but a default family is set, the
claim_invite_code before_action was blocking registration before
the create action could assign the user to the default family.
Now invite_code_required? returns false when
invite_only_default_family_id is present, allowing codeless
signups to land in the configured default family.

https://claude.ai/code/session_01U9KgikKjV6xbyBZ5wMYsYx

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-10 18:12:53 +01:00
Juan José Mata
674799a6e0 Enforce one pending invitation per email across all families (#1173)
* Enforce one pending invitation per email across all families

Users can only belong to one family, so allowing the same email to have
pending invitations from multiple families leads to ambiguous behavior.
Add a `no_other_pending_invitation` validation on create to prevent this.
Accepted and expired invitations from other families are not blocked.

Fixes #1172

https://claude.ai/code/session_016fGqgha18jP48dhznm6k4m

* Normalize email before validation and use case-insensitive lookup

When ActiveRecord encryption is not configured, the email column stores
raw values preserving original casing. The prior validation used a direct
equality match which would miss case variants (e.g. Case@Test.com vs
case@test.com), leaving a gap in the cross-family uniqueness guarantee.

Fix by:
1. Adding a normalize_email callback that downcases/strips email before
   validation, so all new records store lowercase consistently.
2. Using LOWER() in the SQL query for non-encrypted deployments to catch
   any pre-existing mixed-case records.

https://claude.ai/code/session_016fGqgha18jP48dhznm6k4m

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-10 13:44:53 +01:00