Suggests transaction categorization rules during onboarding.
After a sync or import, it groups the uncategorized transactions, asks
Gemini (via laravel/ai) to map the common merchants to categories, and
shows the results for review. The user edits or drops any and creates
the ones they want. During onboarding the accepted rules also categorize
existing transactions right away.
Off by default: it needs the `AiRuleSuggestions` Pennant flag and a
per-user AI consent. The model and thresholds are config-driven.
`ai:suggest-rules {user}` prints what a user would get.
The settings-page surface and monthly regeneration are a follow-up.
## Problem
A user reported automation rules not applying. The rule matched a
description **and** `Valor es igual a 21,99`, but never fired.
Root cause: the matching engine evaluates the **signed** transaction
amount (`amount / 100`), and expenses are stored negative. So an expense
of `-21,99` is compared as `-21.99 == 21.99` → false. The rule editor
showed a plain number input with no hint about the sign convention, so
entering a positive `21,99` to match an expense silently builds a rule
that can never match.
This is a common trap — likely affecting many users with amount-based
rules.
## Change
Add a small helper note under the numeric `amount`/`Valor` value input:
> Usa un valor negativo para gastos (ej. -21,99) y un valor positivo
para ingresos.
- Renders only for the numeric `amount` field; text conditions are
unaffected.
- No change to how rules are stored or evaluated — existing rules keep
working.
- Added the Spanish translation key (`es.json` is CI-enforced).
## Testing
- `bun run format` and `eslint` clean on the component.
- `LocalizationTest` passes (translation key resolves, no missing keys).
## Follow-up (not in this PR)
The hint guides new rules but doesn't retroactively fix rules already
saved wrong. A future enhancement could warn when saving a
positive-value amount condition, since most finance rules target
expenses.
## 🚪 Why?
### Problem
Untranslated strings were silently reaching production. Several UI
components rendered raw English literals without going through \`__()\`,
and 15+ strings used via \`__()\` had no entry in \`lang/es.json\`,
causing Spanish-locale users to see untranslated text. There was also no
automated safeguard to catch new regressions.
## 🔑 What?
### Changes
- Add \`tests/Feature/LocalizationTest.php\` with two test groups that
run in CI:
1. **PHP translation files** — verifies every key in \`lang/en/*.php\`
exists in the matching \`lang/es/*.php\` file
2. **Spanish JSON translations** — scans all \`.ts\`/\`.tsx\` files for
static \`__()\` calls and asserts each key exists in \`lang/es.json\`
- Wrap previously unwrapped literals in \`__()\` in `header.tsx`
(Github, Discord, Dashboard, Log in, Register), \`appearance.tsx\`
(Chart color scheme heading and color scheme labels), and
\`rule-builder.tsx\` (field config labels and operator labels at render
site)
- Add 15 missing Spanish translations to \`lang/es.json\`: Discord,
Chart color scheme, Choose the color palette for your charts, Blue,
Colorful, Neutral, Pink, Account Name, Bank Name, contains, equals,
greater than, is empty, is not empty, less than
## ✅ Verification
### Tests
- New test: `tests/Feature/LocalizationTest.php` — 2 tests, 4
assertions, all passing
- Runs automatically in CI via the existing `./vendor/bin/pest` job (no
CI config changes needed)
### Manual Verification
- `php artisan test --compact tests/Feature/LocalizationTest.php` →
`Tests: 2 passed (4 assertions)`
## Why
**Problem:** Bulk re-evaluation of transactions against smart rules was
broken because the code required an encryption key that no longer exists
in the app. Additionally, the rule builder UI had unnecessary complexity
(priority field, labels/tags, date/category/notes conditions) that added
cognitive load without value. Currency amounts were also always
formatted with US conventions regardless of the user's locale.
## What
**Automation rules – re-evaluation fix:**
- Remove encryption key requirement from `evaluateRules`,
`evaluateRulesForTransactions`, `evaluateRulesForNewTransaction`, and
`prepareTransactionData` (accept `CryptoKey | null`)
- Drop `isKeySet` guards from single-transaction and bulk re-evaluate
handlers in `transaction-list.tsx` and `transaction-actions-menu.tsx`
- Simplify `use-re-evaluate-all-transactions.tsx` to pass `null` as key
directly
**Automation rules – UI cleanup:**
- Hide priority field from create/edit dialogs (still sends `priority:
0` / `rule.priority` to satisfy backend validation)
- Remove labels/tags from create/edit dialogs, actions column, and table
display
- Remove `date`, `category`, and `notes` from rule condition field
options in `rule-builder-utils.ts`
- Add `opacity-30` to disabled X button when only one condition/group
remains
**Amount localization:**
- Replace hardcoded `'en-US'` with `useLocale()` in `AmountDisplay` so
symbol position and number format follow the user's locale (e.g.
`89.705,00 €` in Spanish)
## Verification
- 38/38 `AutomationRuleTest` + `AutomationRuleEvaluationTest` tests pass
- Re-evaluation works without an encryption key set
- Spanish locale users see `89.705,00 €` instead of `€89,705.00`