Releases
Everything that has shipped.
Generated from the commit log when each version is tagged, so this page cannot disagree with what actually went out.
5 releases739 logged changesv2.0.0 latest
Nothing matches those filters.
- v2.0.0Latest
Breaking changes1
- potsCategory-linked pots are retired, and their balances are released on upgrade (#399) (breaking) (
f8ccaf6)
Features353
- GREEN — CalendarQuery service wired into CalendarPage (
b575bc4) - CalendarPage month-nav, accounts popover, persistence (
022d540) - Full calendar UI — grid, toolbar, day panel, CSS layer (
ade2513) - Sidebar + palette test — CalendarPaletteAndSidebarTest (
63e415b) - Tax module scaffold, provider, routes, migrations, service stubs (
a9371dc) - DTOs, TransactionTagged event, TagTransaction + UntagTransaction actions (
d89562a) - Implement TaxYearQuery — grouped per-year read with COALESCE override + totals (
2fa8d24) - Implement TaxTagQuery + NavCountsService tax_tagged key (
70c7e46) - Tax corpus YAML (6 countries) + TaxCorpusLoader (
4e0dd6b) - TaxCategoryWriter — provenance-safe seed + CRUD actions (
f0d9ab5) - TaxSettingsSection Livewire component + settings-page integration (
87206f7) - Implement TaxCsvExporter (D-15 audit-extra CSV) (
e00d5a9) - Implement TaxPdfRenderer + CSS-2.1 table PDF template (D-14) (
55b6a25) - TaxPage cockpit — year switcher, grouped sections, exports, empty states + CSS (
708914b) - Dashboard tax card, sidebar entry, ⌘K palette, real cross-user probe (
8d771b7) - Shared tax-badge + picker + HandlesTaxTagging trait (
796c517) - Integrate tax badge on all four surfaces + TaxBadgeSurfacesTest (
9060fe3) - Expose Tax public country-setup surface for cross-module wizard use (
cef4960) - Add skippable tax-country step to the setup wizard (
177b502) - Scaffold Search module manifest, service provider, and boundary arch rule (
b6bce16) - FTS5 migration, public DTOs and contracts for Search module (
5f374c1) - Implement SearchIndexWriter and IndexTransactionOnImport listener (
caa604b) - Tax note re-index hook + pint fix on SearchIndexWriter (
7b9f906) - Add reindex command, FTS health check, and doctor wiring (
abf6baa) - Implement QueryParser and DidYouMeanSuggester (
2a24a10) - Implement SearchQuery::search() — FTS MATCH + filters + highlight + summary + cursor (
74aeb62) - Implement SearchQuery::palette() + EntityNameSearch + SearchResultsProviderImpl (
ff16c18) - Add URL-bound search/filter to TransactionsList + search UI (
7d28808) - PaletteSearchEndpoint component, view, route, CommandPaletteModal injection (
b93d664) - Palette.js server fetch + merge, palette blade sections, token autocomplete, CSS (
fec71ab) - Sidebar search affordance + mobile magnifier (D-25, D-29, UI-SPEC #8/#9) (
fa8c7a9) - Scaffold Anomaly module + four migrations (
76ad968) - Anomaly models + sole-mutator state machine + factories (
24efee6) - Robust statistics + large-vs-typical detector (
4a91255) - First-time + duplicate detectors + recurring membership (
e06badb) - AnomalyEvaluator orchestration + suppression + dispatch (
04162f8) - AnomalyAlertQuery + DTO/mapper + suppression-rule query (
7468b48) - Acknowledge/snooze/dismiss Public Actions + cross-user 404 + lifecycle events (
31830eb) - Dismiss-as-expected (±15% suppression) + remove/undo suppression rules (
3bef60c) - Reactive DetectAnomaliesJob + TransactionImported listener (
6df1ceb) - Chunked idempotent BackfillAnomaliesJob with anomaly_backfilled_at guard (
3e3c84e) - Hourly anomaly snooze-revival + safety-net sweep + scheduler wiring (
7bed409) - /drift type switch + anomaly alert row consuming AnomalyAlertQuery (
0a13153) - Dashboard anomaly tile + amber sidebar nav badge (D-03) (
96ea638) - Settings 'Anomaly detection' section + first-activation backfill (D-11/D-18) (
bf2db7e) - Scaffold Modules/Sync spike module and register in Pest harness (
e8d9aa7) - Define Internal contracts — HLC, OpType, OpLogEntry, DeviceKeySigner, OpLogReplayer skeleton (
38f833e) - Complete OpLogReplayer LWW merge algorithm — D-05..D-08 GREEN (
8a6ac1b) - Add TriggerAwareRebuildTest + signature gate (3 probes + forged-entry proof) (
d331be4) - Add CrossUserScopeTest — security proof for T-10-02 (elevation of privilege) (
8b685bc) - Create three Sync persistence migrations (
72f1eca) - Add transactions.note column with mandatory trigger re-install (
7e065e9) - Implement MergeRulesRegistry + 4 CRDT merge strategies (
8c5192f) - Implement OpLogWriter — persist signed entries + HLC persistence (
def18cb) - Productionize OpLogReplayer + wire SyncServiceProvider as single owner (
acdb0c2) - Add FTS5 freshness hook (SearchIndexWriterContract) to OpLogReplayer (
ffa33a3) - OpLogRebuilder — trigger-safe deterministic full-rebuild from op-log (
58425af) - TransactionMutated event + SyncCaptureListener + capture at category_id + type edit paths (
b679bf0) - Note editor + counterparty reassignment + delete on TransactionDetail (
1a5aa1a) - SyncHealthPage Livewire + view + route + user-scope test (D-07) (
8d6bf59) - Add device_registry and pairing_tokens migrations (
265e3f8) - Bundle BIP39 wordlist + immutable DeviceIdentityDto (
5a4a6b0) - Device identity service + loader + name detector (
cef4388) - Safety-number deriver + DeviceRegistryService facade (
bfb4b28) - Wire identity + registry services into SyncServiceProvider (
a7400a4) - Single-use pairing token service + state machine (
568061e) - Word-code encoder + QR payload builder (
bf5ee79) - Complete both-confirm trust transition in confirm() (
9545c7f) - Devices & Sync settings section + settings-page mount (
cf0b5e0) - PairingFlowModal bidirectional flow + both-screen safety-number gate (
d6e7000) - Install amphp stack + deviceX25519Keys() + sync transport migrations (
eeb2516) - Pre-wire SyncServiceProvider with Phase 13 class_exists guards (
8178a2c) - NoiseCipherState + NoiseSymmetricState (AEAD + HKDF over BLAKE2b) (
dcf4280) - NoiseHandshakeState (IK+XX) + NoiseSession + TransportFramer; Wave-2 tests GREEN (
8590c9f) - Implement RelayMailbox — ZK ciphertext store/drain/confirm (
cc564c5) - Implement RelayConfig + RelayClient; turn Wave 4 guard GREEN (
18fc1b3) - SyncSession — Noise auth gate + additive Ed25519 op verify (
9e4da2b) - Add PeerCatchUpExchanger, SyncWebSocketHandler; turn Wave-3 tests GREEN (
064c0dd) - Add mDNS discovery (MdnsAdvertiser, MdnsBrowser, DiscoveredPeer) (
1cca0c0) - SyncServeCommand + RelayServeCommand + provider registration + config/sync.php (
205f699) - NativePHP ChildProcess host + launchd plist for self-hosters (D-03/D-04) (
56d0f90) - SyncStatusService + SyncStatusSection + status-section blade + test (
bbcd395) - Relay-URL setting + sync-status-section wired into Devices & Sync (
d3efab9) - SaveTransactionSplit action — sum invariant, type gate, PK-preserving edit, unsplit (
8201c8e) - Add SpendByCategoryQuery shared read model over legs ∪ unsplit-parents (
414e89a) - Redirect BudgetProgressQuery/TopCategoriesByPeriodQuery/CategorySpendTrendQuery to SpendByCategoryQuery (
ff37e4c) - Wire transaction_splits op-log capture (Task 1) (
3d09b8f) - Leg-aware TaxYearQuery amount resolution + TaxTagQuery leg batch-load (
fa89caf) - TransactionDetail split editor actions + Livewire feature test (
1f7db96) - Split section Blade markup + new app.css component classes (
4a3d6d6) - Split-row expand/collapse on the transactions list (
96f2cdb) - Add the three envelope Public sync-event contracts (
e24ca18) - Envelope tables + genesis anchor migrations (Pitfall 4 nullability fork) (
04ba90d) - EnvelopeAssignment/EnvelopeSetting models + EnvelopeRow/EnvelopeMoveRow DTOs (
1b2e7e6) - BudgetsServiceProvider single-owns envelope bindings (class_exists-guarded) (
ecc4786) - Extract incomeForPeriod() as the one canonical income sum (
d8c45c5) - CarryoverQuery genesis-to-target fold (Req 2/4/7/8/9) (
0291e46) - EnvelopeBalanceQuery net_moved + recent-moves loader (
a4e09c1) - Add envelope_assignments/moves/settings MergeRulesRegistry entries (
ac8e88f) - Add SyncCaptureListener handlers for the three envelope tables (
886eea9) - Wire envelope events + register envelope_moves in UserIdColumnArchTest (
d08421c) - EnvelopeActivationService idempotent per-user cutover (
4332686) - One-shot cutover migration invoking EnvelopeActivationService (
249c9ca) - EnvelopeWriter setAssigned/setOverspendMode/copyFromPeriod (Req 1/4/6) (
40e1608) - EnvelopeWriter move/undoMove -- paired-row ledger, no balance guard (Req 5/8) (
0ddf693) - Rebuild /budgets as the zero-based envelope grid (Task 1) (
7357392) - Move-money modal + per-envelope recent-moves undo (Task 2) (
877e7a7) - EnvelopeGlanceCard dashboard glance card (Req 12) (
3899fa8) - Repoint onboarding BudgetsStep onto EnvelopeWriter (Req 12) (
3dd1090) - Activate + seed envelope budgets in demo:seed (
94b63bb) - Add Transaction::STATUSES allow-list + D-04 re-import regression test (
7fef025) - Source-format-aware import default + clearedBalance query (
484fbb4) - Register status as an LWW-synced field (D-10) (
7b874a8) - HandlesClearedStatus trait + TransactionDetail::toggleCleared() (
7657a9e) - Wire cleared badge into transactions list + detail page (
b1121d5) - ReconciliationWriter::completeReconcile() + unreconcile() (
33d0eff) - Enforce reconciled lock on TransactionDetail mutators (
9d3ce4c) - Enforce reconciled lock on SaveTransactionSplit::save/unsplit (
958ac0e) - ReconcilePage — account picker, target pre-fill, difference, confirm (
80f6bdb) - Wire /reconcile into the sidebar (calm-slate, no new CSS needed) (
ee86ca7) - Redesign categorization_rules + add rule_conditions/rule_actions/field_provenance (
6227ad9) - Forward-backfill legacy rules + RuleCondition/RuleAction models (
2698d94) - Update MergeRulesRegistry + seeder for the new rules schema; add RuleSchemaMigrationTest (
7bbc014) - Add RuleEngine multi-condition matching core (
78ecdfb) - Nested rule DTOs + CategorizationRuleQuery join/mapping (
85ad2ba) - Multi-condition/multi-action rule authoring with op x value_type matrix + payload ownership validation (
d704f75) - Referential-integrity listener deactivates rules on category/counterparty delete (
00c13c7) - Add shared FieldProvenanceWriter Ledger Public service (
80a51eb) - Promote counterparty-reassign/note writes to Ledger Public actions (
7818b2c) - Stamp field_provenance='manual' from all four manual write paths (
97d235f) - RuleApplier import-mode action folding (D-06) (
497e070) - RuleApplier re-apply mode delegating to Ledger guarded actions (
0d167dd) - Rewire import categorisation to RuleEngine + RuleApplier (
212d29a) - Add ReapplyRulesJob — chunked, reconciled+split-guarded re-apply (
e7e6119) - RuleFormModal multi-condition/multi-action builder (
eebec49) - RulesPage priority-ordered table + re-apply-to-history trigger (
4134cad) - Scaffold Migration module + test reachability (
2fa4cfa) - Add migration_runs + staging + source_map + baseline schema (
0c718b1) - Register Migration sync entries + registry-columns contract tests (
f42f9ca) - Migration IR DTOs + parser contract (Task 1) (
2cb155c) - Shared YNAB parser Support helpers (Task 1) (
075d329) - Ynab4Parser + NynabParser turn the Plan 02 RED stubs GREEN (Task 2) (
6726135) - ActualSqliteReader read-only Pdo\Sqlite wrapper over the extracted Actual export (
7eeade6) - ActualParser + ActualGoalDefInterpreter turn the RED ActualParserTest GREEN (
18696c7) - StagingWriter lands parsed batch into migration_staging_* in bounded chunks (
3b69764) - StartMigrationRun action + reject-not-partial cleanup (
f5453bd) - PreviewSummaryBuilder read model for the wizard preview step (
dfa3838) - SourceMapWriter + goals staging + migration_runs summary counts (
222a6c2) - PromoteStagingToDomain — fan-out staged IR to the six public writers (
95ab5db) - ConfirmMigration + DiscardMigrationRun actions (
c352b51) - ThreeWayMergeResolver — source/baseline/current merge comparator (
47cad0e) - CheckForUpdates reconciliation entry point (Req 10 GREEN) (
7095161) - Migration wizard routes + index/upload components (
c796b1d) - Preview + results wizard components (stat grid, conflicts, confirm/discard) (
60284f6) - Scaffold Reports nwidart module + phpunit testsuites + boundary rule (
94a02b3) - Saved_reports migration + SavedReport model + MergeRulesRegistry entry (
7f1e2f6) - SavedReportMutated event + SyncCaptureListener::handleSavedReport + provider wiring (
bd4b8a6) - Implement counterparty filter dimension in Search (
42af99a) - Wire counterparty URL filter into TransactionsList (
fbdfb82) - Add Reports public DTOs (ReportDefinition/ReportResultRow/ReportResultDto) (
12fc433) - PeriodPresetResolver + TimeBucketGenerator (Req 6/7) (
893d89e) - CategorySpendQuery — canonical type-based, split-leg aware (Req 2/3) (
d60b766) - CounterpartySpendQuery + AccountSpendQuery + TimeBucketSpendQuery (Req 2/3/6/7) (
6e04861) - Implement NetWorthSeriesQuery sampled base-currency series (
4b48fb8) - ReportAggregator dispatch + CurrencyModeApplier (metrics, dimensions, currency, FX exclusion) (
9854b61) - PeriodComparison — current vs previous-period deltas (Req 13) (
ffb1f64) - Saved-report write actions (save/update/delete/pin) (
d990ffa) - Pin cap + ReportCsvExporter + reports.export route (
a429347) - DrilldownUrlBuilder — single tested Period→transactions.index URL helper (
1480002) - Live /reports builder — control rail, result panel, bar/line/donut charts, drill-down (
499cfa5) - Saved-report library index (SavedReportsQuery + ReportsIndex Cards|List CRUD) (
758a43c) - Reports sidebar nav item + cross-user isolation guard coverage (
4ae8e61) - Dashboard pinned-reports mini-card row (
61839a2) - Wire the pinned-reports row into the dashboard additively (
c14befa) - Add gdk_epoch op-log column + device-local sync_encryption_state table (
f03f126) - Add GdkEpoch DTO + SensitiveFieldRegistry (pure crypto foundation) (
9eae3e2) - OpLogFieldCrypto — per-field AEAD with epoch-bound associated data (
4c9a63b) - GdkKeyring + GdkKeyringService — append-only GDK epoch keyring (
c6756d1) - SensitiveColumnCodec — rotation-safe projection-column codec (
1fb8b4f) - OpLogEntry.gdkEpoch + OpLogWriter encrypt-on-write hook (
c7eed50) - OpLogReplayer decrypt-before-strategy hook + gdk_decrypt_failed quarantine (
35f6eab) - Encrypt RecordTransactions direct-write import path (CRYPT-01) (
758ae92) - Encrypt Counterparties creation write + decrypt read sites (CRYPT-01) (
f3313a1) - Decrypt Ledger transaction detail/list read sites (CRYPT-01) (
a4b27bd) - SearchIndexWriter decrypts before tokenizing (CRYPT-01 / BLOCKER-2) (
55b82ef) - Decrypt Tax cockpit/export read sites (CRYPT-01) (
5574fa4) - Decrypt SearchQuery row-level counterparty_name (CRYPT-01) (
df91a27) - D-10 GDK rewrap contract/service + event + Sync listener (
43eeeda) - Wire AppLockProvisioner.changePin to D-10 GDK re-wrap (
bb6ecee) - GdkRotationService.rotateAndRevoke + sealed-box wrap-per-device (
0ecf01c) - EncryptionMigrationService — D-09 backup-first atomic migration (
997ce8c) - Add GdkEpochControlHandler — validate, open, append inbound GDK_EPOCH_WRAP (
bf174d7) - Wire GDK_EPOCH_WRAP delivery into the live Noise catch-up exchange (
6f9abff) - Enable-encryption flow (D-07 mandatory) + device remove/revoke UI (
143a7c7) - Encrypt tax_transaction_tags.note in TagTransaction (CR-01) (
8bf6679) - Encrypt transaction_splits.note in SaveTransactionSplit (CR-02) (
faeac20) - Encrypt transactions.note in SetTransactionNote + decrypt-before-compare (D-07 gap) (
e0457e8) - Backfill note-table sweep (D-08) + docblock correction (
5091fd1) - Flip request-context dispatch to dispatchSync for CRYPT-01 (
e8bdecc) - Decrypt-then-substring SearchQuery LIKE fallback (Task 1) (
6ef0dbb) - Decrypt-then-match EntityNameSearch + DidYouMeanSuggester (Task 2) (
328f2b3) - Decrypt-for-display in RuleFormModal + CategorizationRuleQuery (
8d7302f) - Decrypt-for-display in ReportBuilder counterparty filter (
40b8203) - Decrypt-for-display in Chains + triage D-06 fold-in sites (
aec58f7) - Decrypt-then-json_decode raw_payload Eloquent cast (D-04/D-05) (
9d87aac) - Decrypt raw_payload + decrypt-then-match iban in PaypalFundingResolver (CR-05) (
2bc61ff) - TransferPairer decrypt-then-match counterparty_iban (CR-03/D-02) (
66e1bf1) - IcsSettlementResolver + RetypeByAliasResolver decrypt-then-match counterparty_iban (CR-03/D-06) (
fbe62be) - Decrypt counterparty_name/description before ReapplyRulesJob rule matching (CR-04) (
7f47a53) - Decrypt counterparty_iban before income-series clustering (
c5ab929) - Skip iban-dependent income detection with a logged warning when KEK-less (
0daeabd) - Encrypt display_name/merchant_name before save() in CounterpartyTriage (CRYPT-01, Task 1) (
ea2b19d) - Decrypt candidate description before MerchantNameResolver::resolve() in CounterpartyTriageQueue (CRYPT-01, Task 2) (
fc66d05) - KEK-aware, encryption-safe CounterpartyGarbageCollectorJob orphan prune (
0feddc3) - Decrypt-for-display in PreviewSummaryBuilder::transactionLabel() (
2b4bb27) - Scaffold Mobile module with single-owner provider/routes/phpstan wiring (
462c028) - Add mobile_sync_progress durable-cursor table + green wiring test (
8b31e1a) - Add throwaway mobile topology spike probe commands (
e845529) - Scaffold mobile-app sibling root sharing Modules/ (Option 1) (
fd0d34c) - UserDataPathService NATIVEPHP_PLATFORM accessor + mobile-branch regression coverage (
b1f6442) - Reconcile live sqlite path to the single authority + on-device migrate-on-launch (
cc63548) - NetworkPolicyResolver — D-09/D-10 mobile network policy (
f969e39) - LanSyncClient — outbound amphp/websocket-client LAN dial-out (
c7ec2c7) - MobileSyncTriggerService — KEK-gated sync burst orchestrator (
345a134) - InitialSyncPuller — idempotent resumable full-history pull (
c125bb9) - SetupProgressScreen — blocking setup screen resuming at true percent (
fe30f76) - SyncScreen — full-page /sync status surface composing sync-status-section + Sync-now + cellular toggle (
ebc05f7) - Drawer entry for /sync (D-05) (
b33fdb4) - Install premium NativePHP mobile plugins into mobile-app/ (
28736d3) - BiometricUnlockBridge — bool-only native bridge (mobile-biometrics) (
d727601) - MobileLockScreen — biometric-primary unlock reusing the Auth chain (
5b31172) - QrScanBridge — hand decoded QR token into the existing pairing validation boundary (
e46e1f3) - MobilePairingScan — camera-first pairing entry with word-code fallback (
a477045) - Sync:mobile-pull bounded background pull command (D-07) (
eab2642) - Add mobile-app/-rooted CI job; fix Desktop-route 500 under the mobile shell (
ffebc8f) - Mobile first-run gate + welcome screen (create-account / import CTAs) (
239d3a1) - Add fresh-device import bootstrap Public seams (Task 1) (
b65e367) - Desktop epoch fan-out on pair-ADD (Task 3 service half, B1) (
9e9bd6f) - Cross-device pairing token seeding + mutual admission (Task 2, G1/G2) (
ef28a2e) - Desktop pairing UI fans out epochs on both-confirm (Task 3 UI half) (
a096f76) - Phone-side epoch receive + history re-projection (Task 4, G3/O1) (
28fe34c) - MobilePairingScan import mode — self-mint deferral (Task 2 UI, B2) (
a60880a) - Fresh-device import onboarding screen (Task 5) (
aed4cf5) - 15-crossdevice-pairingQR carries the relay endpoint for cross-device bootstrap (C1) (
6b299bd) - 15-crossdevice-pairingCross-device apply methods close the both-confirm gap (HIGH-01 trust core) (
9dbc6fa) - 15-crossdevice-pairingPairingRelayCourier carries pairing frames over the ZK relay (HIGH-01 transport) (
24cebd7) - 15-crossdevice-pairingWire the desktop pairing surface to the relay courier (HIGH-01 C4) (
1039d7e) - 15-crossdevice-pairingWire the phone pairing surface to the relay courier (HIGH-01 C5) — full happy path to epoch delivery (
d59756d) - Wire real iOS/Android release signing + close bundled-.env keystore leak (
fc1cd3e) - desktopReal Developer ID signing + notarization for the macOS build (
ef3ab50) - Scaffold Modules/Notifications and register its test harness (
b97c256) - Create notifications table with deterministic string PK + state CHECK triggers (
8dc1ef9) - DeterministicKeyDeriver + NotificationStateMachine with unit tests (
0dd4fa5) - Add per-budget threshold_percent + expose on Public seam (
436ecd3) - Redirect D-20 notify threshold to the live envelope model (
ba43efa) - Expose localDeviceId/otherDeviceNames on Sync's Public seam (
9253cad) - Notification_preferences table, model, DTO and Public query (
234df89) - SuppressionEvaluator — the single delivery-suppression site (
d797e8f) - Register notifications tables + D-20 envelope threshold sync (
e25fb7c) - NotificationMutated event + SyncCaptureListener handlers (
3c385a1) - NotificationCopy — single copy authority for 8 titles + type chips (
2a89ef7) - NotificationWriter + lifecycle actions + 8 guarded listener slots (
68fbc2c) - NotificationQuery inbox read model with compound cursor pagination (
447c68d) - PaymentReminderDue/PaymentSettled events + EmitPaymentRemindersJob (
3a470f1) - PersistPaymentReminder + ResolveSettledReminder listeners (
b24aae2) - Add BudgetThresholdCrossed event + EmitBudgetNudgesJob (
d3a7f18) - Add PersistBudgetNudge listener + Req 6 feature test (
a1f8fca) - Add SavingsPromptDue event + EmitSavingsPromptsJob (Req 7) (
e16cbf1) - Scaffold Modules/Position with composed PositionQuery + DTO (
9feff1f) - PositionDigestDue event + cadence-aware EmitPositionDigestJob (
f854fa5) - Add batch-altitude TransactionBatchImported event (Req 10 / D-22) (
c987105) - Persist ONE coalesced import notification per batch (Req 10) (
1af8359) - Persist drift-alert + forecast-shortfall reactive notifications (
5c36605) - Collapse DispatchOsNotification onto the single NotificationDeliverable event (
100707a) - /notifications route, NotificationsPage, and DeepLinkResolver (
d43533b) - Notifications-page/row Blade, .type-chip CSS, sidebar nav item (
544b8c4) - Notifications settings section anchored next to Anomaly (D-36) (
475d8d7) - Wire five notifications schedule entries in routes/console.php (
427f59a) - Add PruneNotificationsJob — 365-day, KEK-less retention sweep (
be11f2a) - DemoNotificationsSeeder — all 8 types, real events, delivery suppressed (
7550ff7) - Swap dashboard position composition onto PositionQuery (
5467d94) - Install nativephp/mobile-local-notifications v0.0.2 (
2079766) - Mobile delivery adapter mirroring Desktop's OS notification listener (
c2aca6d) - Scaffold OpenBanking module, close registration landmines (
bb06e45) - Add RemoteSourceAdapter contract, credential/window DTOs, consent-failed event (
edd9eaa) - Add RunsImports::runFromRemoteFetch() with deterministic ImportRun idempotency (
20c2895) - OpenBankingSecretsRepository — atomic chmod-600 SecretShield-wrapped credential store (
0550944) - EnableBankingJwtSigner + EnableBankingAccessScope (
523a090) - EnableBankingHttpClient with dynamic SSRF allow-list (
581bb92) - Promote LoopbackRedirectUri to Public + OB metadata migration + state repo (
4d3c8b6) - OpenBanking consent/SCA loopback dance (connect + callback) (
751dd64) - OpenBanking onboarding wizard (local keypair + institution pick) (
8c70dd4) - Reconsent-alert listener + wizard/listener provider wiring (
e9c6fe7) - Typed Enable Banking transaction response DTO (
59c1e10) - Enable Banking source adapter with CAMT-parity field mapping (
6727b67) - OpenBankingFetchService + SyncOpenBankingAccountJob (two-timestamp accounting) (
b5e4717) - OB settings page shell, toggle + B2 loud-warning gate (Task 1) (
9a927ac) - Transparency panel + shared disconnect action (Task 2) (
962c7d4) - Consent-expiry banner + re-link flow (Task 3) (
23acd09) - Open-banking.daily-sync scheduler entry (D-13/Req 9) (
094cb49) - Manual "Sync now" action + result states (Surface B6) (
820d6ee) - Guided ICS file-import affordance (Surface B7, Req 13) (
3d2b47d) - Metadata-only ICS statement-ready detector + tunable pattern + scheduler (
cfcd7b7) - Notifications nudge listener + 9th trigger constant + guarded registration (
6e0af28) - HTTPS-loopback TLS listener for Enable Banking consent dance (
bd87413) - authCold-start biometric unlock + SecretShield at-rest keychain (
e1460fa) - Sweep Modules/Forecasting M1-M3+M5 comment shape/token conformance (
d572e14) - demoSeed the surfaces demo:seed left empty (
e2105b6) - notificationsGive the digest cadence a type and a constrained column (#71) (
f4f3aa2) - recurringGive the series cadence a type and a constrained column (#72) (
15d8116) - Multi-language UI support (English + Dutch) (#204) (
50b3b3d) - i18nLocalise the operator and cadence enum labels (#214) (
f83a70c) - i18nLocalise the Reports and Recurring page titles (#213) (
1eb8452) - i18nRender persisted notification copy in the recipient's locale (#216) (
1fb6a2b) - i18nRender dates and counts in the active locale (#217) (
1ee0745) - mobileNative chrome, the fixes that made it usable, and the dependency majors (#223) (
900968a) - Twenty-six languages, a corpus you can act on, and a mobile app that survives its own updates (#226) (
4a05620) - goalsA goal counts the transactions you attributed to it, not every credit on an account (#239) (
72bb11d) - i18nLaravel's own messages ship in every locale the product does (#254) (
4a49115) - coreYour country is a preference like your language, not a tax setting (
b1f4311) - syncThe column we matched merchants on was the plaintext we had encrypted (
33d0618) - coreA shipped deployment shape the address gate refused, and the record that opens it (#362) (
63b8a1b) - syncAn op a new phone can never verify, and the device that can vouch for it (#363) (
3c0f716) - mobileA permission prompt the reader can read (#398) (
b5b9e3b) - open-bankingA store that could hold one bank and one person, keyed to both (#403) (
3b52585) - coreThe zone the machine is in, not the one the packager was in (#431) (
f0820a5) - shellSample data on a surface a store build carries (#432) (
faa6385) - coreA channel a reader can choose, and a preview manifest to choose it for (#438) (
732b8d6) - coreA feed a prerelease can actually live on (#443) (
0b339b9) - syncThree facts the sync layer had and never used (#447) (
94d425b) - sync,mobileThe typed code had one road to the peer, and iOS never has it (#449) (
32e467e) - release,mobileThe shape a store takes, and reading the artifact instead of the list (#450) (
f81433f) - desktopRead a bundle for what the Mac App Store refuses, and measure what the interpreter needs (#453) (
de929b3) - releaseThe App Store lane gets its own target, beside the Developer ID one (#454) (
8e6d7a8) - releaseA store lane that can be run, and refuses to produce what nobody can submit (#455) (
93edfe8) - doctorSay out loud when no machine could name the zone (#472) (
1026f6f) - Eleven answers a reader could not reach, across every screen (#504) (
9eda816) - searchFind a transaction by the note the reader wrote on it (#512) (
1be69bf) - ledgerThe doctor says whether a digest still describes its row (#571) (
240407f) - ledgerThe doctor sees a split its transaction outgrew (#577) (
cdb8d0e) - coreA probe that asks whether any row names another reader's row (#595) (
d8ae471) - importA receipt total a cent from the statement is the same transaction (#623) (
6c0a690) - A converted figure names the rate that made it (#701) (
c345e43) - import,ledgerA statement that did not add up now says so (#710) (
9b14592) - The two figures the rate could not reach (#708) (
a581a11) - importA row that restates one already stored (#711) (
fb671f8) - ledgerA reader outside the euro can choose their own reporting currency (#709) (
bcb5ca8) - importA restatement the bank booked on another day (#717) (
5dca705) - coreAn older backup is brought forward, not refused (#719) (
7fd5e91) - toolingOne command that makes a worktree able to run the suite (#729) (
0609603) - toolingA rule for the update that moves a column the digest is over (#856) (
8f25f2d) - releaseLaunch the macOS and Windows bundles too (#888) (
2e867ae) - releaseSay which build produced each published artefact (#891) (
2212fad)
Fixes348
- Cover calendar.index in cross-user guard + refresh sidebar snapshot (
83d53e6) - Resolve duplicate Pest TestCase binding for Calendar tests (
fd4ee91) - CR-01 materialize account-popover defaults into explicit id lists (
c2a4cb4) - CR-02 FX-convert per-currency forecast buckets in the balance line (
057d34f) - WR-01 batch counterparty/account metadata resolution in buildEntryMap (
ec48089) - WR-02 clamp the paid-match window to half the cadence interval (
81c95e6) - WR-03 bound backward projection at the series' inception (
9cf1258) - WR-04 step occurrences by index from the anchor, not chained (
cf1c24b) - WR-05 enforce the 12-month ceiling at render time for tampered URLs (
6bd81bd) - WR-06 validate selectDay with checkdate before Carbon parse (
86197e5) - WR-07 sanitize client-controlled pref arrays before persisting (
8b96508) - WR-08 honest start-of-day balance — null sentinel instead of fake 0 (
1a4a224) - WR-09 implement past-day actual balances (D-07 contract) (
46ea8b0) - WR-10 announce negative projected balances in the cell aria-label (
5918cf5) - WR-11 route every 'now' in CalendarPage through the Clock contract (
cadc58e) - IN-03 update horizon docblocks to reference HORIZON_DAYS (
8dd05d0) - IN-04 assert the clamped month label in the URL-clamp tests (
7c8e905) - IN-05 exclude lead-in/lead-out grid days from the risk summary (
453e5a1) - IN-06 document whole-euro cell rounding as a density trade-off (
fc2ef8a) - IN-07 remove the unreachable .cal-entry--paid .cal-day-num rule (
f470218) - IN-08 only persist prefs on Escape when the popover is open (
77f429e) - Resolve post-merge wave 2 defects — counterparties.display_name + RefreshDatabase on unit suite + single-arg COALESCE (
dd4598f) - Release drawer-container inline 100dvh on desktop so the sticky sidebar tracks page scroll (
13b8b3a) - Desktop sidebar joins the page scroll — no separate nav scrollbar (
4397407) - Batch Tag-all inherits the trigger tag's category + note (D-03) (
66f1012) - 07-rev-cr-01Replace bogus @return blade directive with @elseif so first-visit /tax masks the cockpit body (
8e77cf9) - 07-rev-cr-02Populate pickerBookedYear so the D-10 year-override picker row is reachable (
2e81a7a) - 07-rev-cr-03Preserve tax state on accumulated phone rows + make bare re-tag non-destructive (
419063b) - 07-rev-cr-04Make the untagged Tag button reveal on all four desktop surfaces (
adf014c) - 07-rev-wr-01Skip corpus entries whose name collides with a user-created category (
de87ecc) - 07-rev-wr-02Regression test — created_at survives re-tag (write path split in CR-03 commit) (
c497469) - 07-rev-wr-03Use array_key_exists for batch-suggestion snapshot keys (
2c8f9ac) - 07-rev-wr-04Reset picker note/category/year-override when the picker opens for a new row (
24791f2) - 07-rev-wr-05Key the batch suggestion to the trigger transaction's booked year (
f9d66e0) - 07-rev-wr-06Invalidate the nav-counts cache on tag/untag via an InvalidateNavCounts listener (
093ca67) - 07-rev-wr-07Make the Public TaxCategoryWriter a real delegating facade; trait no longer leaks the Internal type (
0f9c4e1) - 07-rev-wr-08Mitigate spreadsheet formula injection in the tax CSV export (
215f6de) - 07-rev-wr-09Freeze the Clock in the batch-suggestion tests (
1e4ff58) - 07-rev-wr-10Remove double-escaping of the category name in the PDF export (
696c844) - 07-rev-wr-11Wire a rename affordance, guard rename errors, and make archiving reversible (
3e6e14e) - 07-rev-in-01Drop the copy-pasted amber @class from the booked-year button (
d79f33b) - 07-rev-in-02Remove dead Alpine x-data and inert Livewire-v2 .defer modifiers in the popover body (
11d980b) - 07-rev-in-03Bring all corpus short_name values within the documented 12-char badge budget (
cc10f86) - 07-rev-in-04Seed sort_order continues after the user's existing categories (
6ab2ff6) - 07-rev-in-05Render all available tax years in the year switcher (
0b8dce1) - 07-rev-in-06Route TagTransaction time through the injected Clock; tolerate the upsert race (
540ceb9) - 07-rev-in-07Align empty-year copy with the actual badge — it reads "Tag", not ⌾ (
f51fb54) - 07-rev-in-08Dashboard summary total shows deductions only, matching the /tax headline KPI (
6abd986) - Add missing Modules/Search/tests/Unit directory referenced by phpunit.xml (
d190403) - Resolve post-merge conflicts from wave 2 (
2a26e65) - Resolve UAT findings — XSS in highlights, palette 500, missing category chip (
34e744c) - CR-02/CR-03 verify actor + atomic FTS upsert in SearchIndexWriter (
c04620d) - Correctness fixes in SearchQuery read path (WR-01/02/03/04/07/08, IN-02, CR-01) (
8b70961) - WR-05 search:reindex fails loudly on a partial run (
f6099bf) - WR-06 deterministic did-you-mean tie-break by corpus frequency (
1b3a55d) - IN-03 correct stale phpstan-type aliases in SearchResultsProvider (
248223e) - IN-03 promote AnomalyAlertQuery page-size 26 to a named constant (
9520c7d) - WR-05 compute madFloorFor in float, cast int at the denominator (
c0b8aca) - WR-06 compare snooze idempotency via toDateTimeString round-trip (
458a5dc) - WR-03 chunk snooze-revival candidate scan with lazyById(500) (
b30cc85) - WR-01 claim anomaly_backfilled_at before the history walk (
cb9d511) - WR-02 make duplicate-charge window backward-only (
d370973) - WR-04 make the large p95 boundary tie-inclusive and pin it (
87bf704) - CR-01 fall back to the charge settled amount for non-large dismissals (
b68dbf4) - CR-02 don't suppress first-time's synthetic large with a merchant band (
8e3c41a) - WR-07 make suppression-rule insert idempotent on the natural key (
f7a7c38) - Type SyncServiceProvider closure param as Application (PHPStan L10) (
b45c1a9) - Add cross-user isolation probe for dev.sync-health route (
0aa467b) - CR-01/WR-01 composite-key hlc_clock_state + real monotonic-guard proof (
7c59684) - CR-02/WR-02/IN-04 encode non-scalar merge results before column write (
dfe1f18) - CR-03/CR-04/IN-01 durable cascade op + correct, tested rebuild (
04636a9) - WR-08 persist only verified entries to the authoritative op-log (
a64645e) - WR-03 apply the 7-day window to the sync-health table query (
887a50c) - WR-04/WR-07 quarantine index DESC + transactions schema-integrity test (
ab9f99e) - WR-09/IN-02/IN-03 memoize merge rules, inline indirection, container Clock (
c31fc80) - WR-05/WR-06 document timestamp-format and emit-after-commit invariants (
63f55f2) - Surface Sync Health in Dev Console nav + command palette (
e94d973) - Open Devices & Sync pairing modal + live-refresh app-lock gate (
c1e06d6) - coreStop Alpine init crash from raw if in bottom-sheet x-on (
6d8f90d) - desktopBounce set-up devices off /welcome to the dashboard (
6b8eedd) - coreRestore dashboard top-section spacing lost in 04-06 (
6c148af) - CR-01 bind pairing confirm side to caller device identity (
3615d8c) - CR-02 never shorten pairing TTL on early accept (
b6fb1e2) - WR-01 validate pairing public-key hex at the trust boundary (
b9496d7) - WR-02 wire pairing-confirmed listener to refresh device list (
af14cfd) - WR-03 stop cancelPairing expiring a confirmed token on success (
48d621c) - WR-04 prune stale pairing_tokens + expire abandoned codes (
34743ad) - WR-05 guard admit against self-row device_id collision (
9dec89a) - WR-06 stop leaking the OS hostname as the default device name (
6e529f0) - IN-02 reject wrong-length word-codes with a clear error (
8594872) - IN-03 document QR raw-echo safety invariant (
4f33d52) - IN-01 document the UTC-lexical expires_at comparison invariant (
888918c) - IN-04 make pairing confirm() the single source of truth (
3fbff33) - CR-01/CR-02/CR-03 align RelayClient↔RelayServeCommand wire contract (
65f78ac) - CR-04 bind relay drain/confirm auth to the requested mailbox (
745672a) - CR-05/CR-06 + WR-06 bound catch-up frames and add receive timeouts (
6ee85a3) - WR-01 gate ephemeral injection; WR-02 audit dropped sync entries (
5cf2c07) - WR-03 enforce Zulu timestamp form at RelayMailbox write sites (
8756908) - WR-04 read deviceKeys once as a documented connect-time snapshot (
a2a864d) - WR-05 fail loudly when the relay token chmod 600 fails (
585df55) - WR-06 clamp catch-up watermark fields to non-negative (
50e6c64) - WR-07 give the live-sync replayer the merge registry + FTS writer (
7c09091) - WR-08 initialize and annotate parse_str target in handleDrain (
8c8af5e) - WR-09 drop unresolved mDNS peers; document intentional ws:// (
7bf159e) - IN-01..IN-05 docblock + robustness cleanups (
7217ca8) - Clean up relay token/config files in RelayRoundTripTest teardown (
a4caa3c) - Dirty-field diff on split leg edit + concurrent-edit convergence proof (Task 3) (
dcae68f) - IN-04 extract SPLITTABLE_TYPES into shared Ledger Public source of truth (
b76ff13) - WR-03 fail safe to parent category when legs do not sum to parent (
fa6d21c) - IN-01 correct tax_transaction_tags merge rules and add leg scope (
360462e) - WR-04 + IN-02 canonicalise note to null; datetime strings in op-log (
aa3a07c) - CR-01 + WR-01 + WR-02 coordinate reclassify/delete/back-out with split legs (
24c3bc4) - Retire pot category-linking (D-15) and record breaking change (
71f3979) - CR-01 surface non-EUR spend instead of dropping it (
54f3090) - WR-01 correct false updateOrCreate docblock on envelope events (
17d95ce) - WR-02 make cutover retryable via unclaim-on-failure (
6f5f462) - WR-03 add wire:key to move-history sub-row (
c61668b) - WR-04 correlate move pairs by move_group_id not created_at (
a0e879b) - WR-05 make EnvelopeAssignment store bare Y-m-d period_start (
14dce97) - WR-06 catch RuntimeException in moveMoney handler (
1d98e2e) - IN-01 batch recent-moves into one query per render (
b7a1852) - IN-02 log when the fold walk cap trips before the target (
f5a80d7) - IN-03 gate overspend-mode dispatch on an actual change (
ebf3ec3) - IN-04 guard copyFromPeriod against overwriting existing target (
6028eaa) - testsRepair 4 pre-existing suite failures (arch boundaries + time-bomb) (
0c0f45e) - Repoint Budgets nav badge to envelope_assignments (
e396855) - Update sidebar HTML snapshot for the new Reconcile nav row (
aeabc90) - CR-01 date-bound reconcile match to statement date (
49cef96) - WR-01 close reconciled-lock gaps in reclassifyCategory + toggleLegTax (
f5c8288) - WR-02 dispatch reconciled events only for transitioned rows (
bbc9047) - WR-03 replace brittle positional STATUSES indexing with literals (
9a8a3af) - WR-04 honest reconcile toast reflecting rows actually locked (
a59ad88) - IN-02 bump updated_at in toggleClearedStatus for consistency (
c283a59) - U-1 guard tax-tag writes against reconciled lock (
8916132) - U-2 align demo ICS account kind to ics_card (
00405b2) - communityBind NoOpShell outside the NativePHP runtime (
b0d4ce1) - ledgerDerive reconcile transitioned set from captured ids, not timestamp re-select (
2aee9e3) - categorizationEnforce D-08 reconciled lock on the inline category picker path (
f06fd7a) - syncEnforce table allow-list and authoritative user_id in op-log replay (
3cd5214) - syncBound the open relay deliver/drain paths against resource exhaustion (
382df8a) - taxScope forTransactionIds to whole-transaction tags so untag isn't a silent no-op (
d719448) - taxRestore whole-transaction-tag uniqueness via partial unique index (
0f229b2) - coreExclude superseded whole-tx tags from the sidebar tax_tagged count (
598541d) - syncStage device identity plaintext in the 0700 dir at 0600, not world-readable /tmp (
ebc8b96) - coreStage encrypted-backup plaintext snapshot in a private 0700 dir, not /tmp (
8ab6597) - Rewire CorrectionDivergenceToast/CategorizationProvenancePanel to the nested rule schema (
450d182) - Rewire RenameCounterpartyPopover to the new CreateCategorizationRule signature (
a659278) - 13.4-reviewCR-01 scale amount rule-condition values to minor units (
6d0fd99) - 13.4-reviewCR-02 preserve tax-tag note on rule-driven re-apply (
ec94141) - 13.4-reviewCR-03 make migration 000005 down() a safe no-op (
8b9ae22) - 13.4-reviewWR-01 wrap hits_count bump loop in a DB transaction (
3c9145f) - 13.4-reviewWR-02 add id tiebreak to RuleEngine::actionsFor() (
c223623) - 13.4-reviewWR-03 fail-open per row in ReapplyRulesJob (
348fa08) - 13.4-reviewWR-04 catch InvalidArgumentException in rename popover (
f604fd4) - 13.4-reviewIN-01 remove dead RuleEvaluator::evaluate() wrapper (
b2d3452) - 13.4-reviewIN-02 document field_provenance's actual two-state contract (
7c3e550) - 13.4-uatDispatch modal-show so the rule-builder actually opens (
7d47615) - 13.4-uatCoerce action <select> ids to int so rule save doesn't 500 (
8aa67d8) - Add the locked nav entry for the migration wizard (
9dd5095) - 13.5-reviewCR-01 re-derive conflicted budget-assignment skip-list on every Confirm (
0a54500) - 13.5-reviewCR-02 seed distinct bookedAt so migrated same-day transactions don't collide (
e757f22) - 13.5-reviewWR-01 explicitly user_id-scope 4 unscoped writes/reads in PromoteStagingToDomain (
96443d7) - 13.5-reviewWR-02 user_id-scope ThreeWayMergeResolver::baselineValue() (
b6df77f) - 13.5-reviewWR-03 materialize category-group hierarchy as real parent categories (
6e6a07e) - 13.5-reviewWR-04 reject archives containing symlink entries (
9f03f04) - 13.5-reviewWR-05 require opposite-sign amounts before pairing a transfer (
49bcb3d) - 13.5-reviewWR-06 use migration_runs.status, not an all-zero-counts heuristic, to gate PreviewSummaryBuilder (
895720b) - 13.5-reviewWR-07 preserve a negative Budgeted value instead of silently coercing to zero (
dfd87d9) - 13.5-gapfixReconcile transaction amount in 3-way-merge (Req 10) (
022f9d9) - 13.5-review-deepCR-01 disjoint cat:/grp: key tags so category-group parents never collide (
7b32e77) - 13.5-review-deepWR-02 guard against confirming an already-discarded run (
c47cba6) - 13.5-review-deepWR-03 only treat the real fingerprint-unique violation as a collision (
de97c21) - 13.5-review-deepWR-01 clean up temp dir on partial extraction; IN-01 sniff upload mime (
698e7ec) - 13.5-uatfixMake migration conflict toggle real; format currency + human labels (
9a3ccda) - 999.6-review-aCategorySpendQuery — WR-01 capture unsplit $0 rows, WR-05 rename shadowed filter var, wire amount filter (
f88ae02) - 999.6-review-aAccountSpendQuery — WR-05 rename shadowed filter var, wire amount filter (
8573098) - 999.6-review-aCounterpartySpendQuery — WR-05 rename shadowed filter var, wire amount filter (
0a3b549) - 999.6-review-aTimeBucketSpendQuery — wire amount filter (
c514501) - 999.6-review-aPeriodPresetResolver — WR-02 reject inverted custom range, INFO-03 strict Y-m-d parsing (
c852674) - 999.6-review-aTimeBucketGenerator — WR-03 cap weekly bucket count via widen-to-monthly (
b4e3b91) - 999.6-review-aCurrencyModeApplier — CR-02 filtered currency discovery + post-hoc primary-currency selection (
9d708ae) - 999.6-review-aPeriodComparison — CR-01 calendar-month previousPeriod, INFO-02 drop redundant round(), CR-03 currency-aware compare() keys, WR-04 return previous-period FX-exclusion metadata (
b2915ea) - 999.6-review-aReportAggregator — wire CR-02 currency filters, WR-04 previous-period FX-exclusion metadata, and the amount filter into every dimension query (
fac841c) - 999.6-review-deepWR-01 close TogglePin's pin-cap TOCTOU; WR-02 compact pin_order on delete (
4c090c6) - 999.6-review-deepWR-03 normalize filter-array order before UpdateReport's dirty-check (
5ff7e96) - 999.6-review-deepWR-04 validate category ownership in SearchQuery, honoring global categories (
b02c0e4) - 999.6-review-deepIN-02 dedupe decodeDefinition into shared DefinitionJsonDecoder (
611aee7) - 999.6-review-deepIN-01 format CSV Amount column via integer arithmetic, not float division (
ca68ef3) - 999.6-review-deepCR-01 edit updates the same saved report; CR-03 charts refresh live; WR-02 export/recompute loading feedback (
4f602bc) - 999.6-review-deepWR-01 debounce Amount filter inputs; IN-01 keyboard-operable filter-chip clear controls (
13490c9) - 999.6-review-deepWR-03 delete confirm names the report; IN-02 tabular-nums on saved-reports index counts (
a2ce2b6) - Donut report-updated destroys+recreates chart instead of updateOptions (
df4b76b) - Keyring-file atomicity vs SQL transaction (D-10 / WR-01) (
5107563) - GdkRotationService per-method Session (D-11) + CounterpartyIndexQuery ORDER BY (D-12) (
f25c726) - Decrypt-before-compare in FingerprintStage conflict detection (
4ba0829) - Encrypt-incoming-before-update on both receipt-conflict write paths (
0249ed5) - Decrypt-before-compare in Migration 3-way-merge resolver (
76cb73d) - Encrypt-before-update in Migration EntityChangeApplier (
9273631) - Extend BoundaryArchTest Auth\Internal exemption to EnablesEncryptionForUser (
3a2aa59) - CR-04 decrypt tax note before re-tag to prevent double-encryption (
3959b3c) - CR-05 decrypt transaction description in triage recent-transactions card (
0da2095) - CR-01/WR-08 preserve staged keyring on finalize failure, randomize staging path (
81c3ca5) - CR-01/WR-01/WR-02/WR-09 migration finalize-failure atomicity, keyring reconcile, snapshot cleanup (
79ce989) - CR-02/WR-06/WR-07 rotate-revoke atomicity, is_self guard, seal-auth precondition (
b862e43) - WR-07 document authenticated-sender precondition on GDK epoch wrap receive path (
3ffa6d9) - WR-05/WR-06/IN-01 log+surface encryption-activation failure, server-side self-removal guard, document sync progress limitation (
111c826) - CR-03/WR-19 encrypt pending_enrichment_conflicts at rest, decrypt round-trip, preserve undecodable resolution (
d745789) - WR-03/WR-04 encrypt raw_payload on Model::save path, log decrypt-failure instead of silent null (
df1d89f) - WR-16 preserve counterparty when merchant_name fails to decrypt, never wrongful-prune (
b7e3eee) - WR-14 only warn on missing KEK when encryption is actually enabled (
3afcb76) - WR-18 skip undecryptable rows in transfer-pairing and search decrypt-then-match sites (
3d89a71) - WR-13 tag baseline amount leg with current currency to stop spurious conflicts (
b0fd1a6) - WR-15 report true IBAN-matched count as ambiguous_candidates (
75fb34a) - IN-06 require both cursor components so pagination matches posted_at ordering (
27b1217) - WR-12/IN-03 decrypt counterparty_name in cash-book list, document note property (
ed82485) - WR-17 document recurring_series cluster key as reviewed plaintext-derivative exception (
fd61d86) - WR-10 document migration baseline as reviewed plaintext-sink exception (
d4a3b3a) - WR-11 document preview cache as reviewed transient plaintext-staging exception (
ffdc8fe) - IN-04/IN-07 document per-attempt confirm semantics and tax-tag full-payload contract (
5adcd05) - CR-05 guard null user_id in triage recent-transactions decrypt (Larastan L10) (
5bd7154) - SyncScreen — use manual $view->extends() instead of #[Layout] attribute (
d39204d) - Target the NativePHP persisted store for the on-device DB (no bundle-DB data leak, survives updates) (
0310e84) - Reconcile on-device storage (db-driven session/cache, view-compiled path) + kill the cold-boot Blade cache-path race (
f75a8f4) - Register NativePHP mobile plugins in NativeServiceProvider (biometrics/scanner/background-tasks/network) + wiring test (
5e445ba) - Resolve the on-device DB path per-request via structural mobile detection (
a2da1a1) - Keep the dev database.sqlite out of the mobile bundle (
ec00e20) - Pre-create the on-device DB file before providers boot (kills the cold-boot WAL error) (
7a9da58) - RetryProvisioning() uses server-side stashed credentials, not emptied properties (HIGH-02) (
21d7988) - Derive mobile self-mint-skip from durable state, not ?mode=import (MEDIUM-01) (
d68d3f0) - Surface the silent GDK epoch-1 collision on device-ADD fan-out (MEDIUM-02) (
e28b35b) - Gate import completion on installed keys + guard reproject crash-loop (LOW-01, LOW-02) (
3d137c4) - Harden cross-device pairing per security review (MED-01, MED-02, LOW-01, LOW-03) (
3ad2773) - Rename EnableBankingHttpClient::auth() to initiateAuth() (
da8ee1b) - Guard EB adapter against silent wall-clock date fallback (
8dad6e4) - Guard OpenBankingFetchService against cross-institution session mismatch (
9cee47e) - Harden the Req 4 warning-gate against a stale session ack (
67d6d1a) - Disconnect() disables ALL of a user's OB connections (
ce71275) - CR-01 disable Guzzle redirect following on EB HTTP boundary (
358d269) - WR-01 reject non-public SCA host before allow-listing (
4f66647) - WR-02 assert https + host-match before consent redirect (
20980b1) - WR-03 skip malformed-money rows instead of aborting fetch (
ec50ef2) - WR-04 restore prior consent/account_uid on re-link secrets failure (
8484ff6) - WR-05 lock server-authoritative Livewire props (
c572876) - WR-09 disable other rows when enabling a connection (
90bf274) - IN-03 align ICS PDF upload limit with copy + add mimetypes (
f056924) - WR-11 self-guard enabled + consent in buildFetch() (
e98a312) - WR-10 same-tab consent redirect + institution_id href fallback (
e181ae6) - IN-04 move redirectUri to data-* attr, out of inline JS (
a700d57) - WR-13 skip unparseable internal_date instead of aborting sweep (
223072a) - WR-14 handle unique-violation race in import create paths (
7570bed) - WR-15 degrade gracefully when settings.open-banking is absent (
0568f36) - WR-06 add server-side warningShown gate to confirmWarning (
6b7f2ea) - WR-07 raise ack TTL to 2h and add re-confirm fallback CTA (
b8d3b0b) - WR-08 document single-user v1 secrets store and add multi-user log guard (
d65ef9a) - WR-12 key ICS nudge occurrence on statement-arrival day not month (
a6c5cb0) - ingestionDeny all external entities in CAMT.053 parser (XXE hardening) (
4272757) - syncAlert on GDK keyring rewrap failure after passphrase change (
89aaf65) - syncCorrect phantom columns in merge registry to real schema (
6339bd2) - authNever lock a session that has no lock to unlock with (
3b3dfb4) - buildRepair migration against a fresh database (
a3af0b6) - onboardingDerive wizard step numbers and untangle the Dutch copy (
6f83a9b) - releaseKeep the generated notes within the API's size limit (
189ebaa) - securityClear the two SonarCloud vulnerabilities (#57) (
e0de927) - a11yRepair the controls the accessibility rules were right about (#58) (
3c662d3) - a11yLet the filter chips hold two controls instead of nesting them (#59) (
aff54df) - a11yAssociate the orphan labels and drop the roles that add nothing (#60) (
b9b8bfb) - a11yState the live region instead of overriding the element's role (#61) (
ceae457) - a11yLet the announced name start with the words on the button (#65) (
cc0450b) - a11yEnforce label-in-name with a check that can read Blade (#68) (
cfee32b) - setupReport the server the reachability probe actually reached (#70) (
3068556) - a11yLet the landmark elements carry their own roles (#81) (
f03cac4) - ledgerAnchor the demo dates to the window instead of the clock (#86) (
778cfba) - ciLet the scorecard result publish, and colour the coverage badge (#89) (
0058d80) - coreMake the backup sidecar failure actually raise its alert (#92) (
f880831) - syncMake the secret-staging guard actually fire, and cover the keyring moves (#95) (
ec79ea6) - coreMake the filesystem guards that never fired actually decide (#101) (
aa10087) - syncTranslate the recipient-key conversion in the add-device fan-out (#110) (
87e6d39) - coreName the update-alert kind, so a writer can't spell it two ways (#185) (
4959499) - mobileMaterialize Laravel runtime dirs into the Bifrost tree (#219) (
457fc2e) - dev-modeApply the argument, redaction and raw-echo guards at every entry point (#220) (
5280587) - Restore a green main — triage test rot and six commonmark advisories (#222) (
bc2f42d) - mobileDetect the on-device runtime so native capabilities are not silently disabled (#221) (
af7b9b5) - testsCap paratest workers so composer test finishes (#224) (
67e5239) - coreSend baseline security headers on every web response (#225) (
d8e9e90) - communityShared brands return to their countries, and the transfer-pair rule leaves the applier (#227) (
ffca543) - mobileThe six defects behind an unusable onboarding — locale, double encryption, slow navigation, and a lock that came late (#228) (
1bcb5bc) - mobileA signed-up account could not open Settings, and iOS wore the framework's icon (#229) (
257b983) - syncLocalise the sync timestamp, and write down the mobile release path (#235) (
73d0693) - updaterFetch the running platform's manifest and wire the release feed URL (#238) (
1bfe409) - buildMake Windows signing possible, and stop the env template defeating its own defaults (#236) (
a8159e5) - authCentralize the app-lock 6-digit PIN floor in the provisioner (#245) (
ae73a70) - syncBind the X25519 sealing key into the PAIR_CONFIRM signature (#248) (
372e6c5) - mobileAndroid uploads never landed either, and the gate said they did (#249) (
6984729) - mobileThe upload signal reached two layouts out of four (#250) (
f9ba8c3) - mobileAsk whether multipart works per request, not once at startup (#251) (
2e84a46) - mobileBe the outermost XHR wrapper, because the shell wraps too (#252) (
fb87f35) - importA statement that failed to parse is not a ready one with no rows (#253) (
2984d9e) - mobileSay when a notification was not delivered, instead of nothing (#255) (
0fe81a5) - coreBottom sheets never closed on save, because nothing sends the event they listen for (#256) (
45863ab) - goalsA goal that has been drawn on reported -4% progress into an aria-valuemin of 0 (#257) (
b0cf884) - mobileA phone could not pair at all, and told you the desktop's QR was the problem (#259) (
d253881) - syncEdits to most tables never left the device that made them, and four surfaces lied about why (#260) (
a1b4803) - coreThe whole Android app 404'd, because the host gate treated "no Host" as an attacker (#261) (
144649d) - The defects two device passes and a night on real hardware found (#262) (
a9e606d) - Four defects a clean install on real hardware found (#263) (
2c2c022) - A budget that could not see its own income, and two conventions made one (#264) (
457de4c) - The round that came out of the last device pass (#265) (
4239cc0) - The gate that let twenty issues through, and the twenty issues (#266) (
03efcb8) - What two phones found, and the bindings that let it happen (#267) (
8a5fa09) - authA blank username on the first screen returned a stack trace, not a message (
2dc39fe) - authTwo app-lock writes that succeeded without saying so (
3404664) - coreEvery route 404'd on iPhone, because the loopback gate judged a SAPI that has no socket (
160bb78) - syncTwo phones on one wifi could not pair, because the handshake had only one road (
9475350) - syncThe road home, and three flaws a security review found in the road out (
1ac9da2) - mobileA picker that greyed out every statement, and a screen that waited on a message it could not show (
de32693) - mobileEvery setting reverted on the next screen, because the runtime never let go of the user (
e9c8c97) - taxThe country list was ordered by a code the reader cannot see (
db9ce00) - communityA corpus token matched inside any word, so a phone bill became a DIY chain (
bc5d174) - syncA confirmation now names the keys it was given for, and both sides keep asking (
31c6eb8) - budgetsA month of real spending reported as nothing spent (
d452db9) - counterpartiesThe triage screen showed the ciphertext of the IBAN it exists to identify (
9d5bd27) - communityThe page that invites you to publish a merchant name was holding ciphertext (
5349640) - notificationsA notification was frozen in whatever language it was written in (
594552a) - Four screens that showed the wrong thing, one of them forever (
16b5912) - cashbook"greater than zero" was the wrong complaint about an amount that could not be read (
78203e8) - counterpartiesA Dutch health insurer was classified as a Belgian agency (
8c0de29) - syncOpening the pairing screen killed the handshake it was opened to finish (
bc9688f) - Two more places that read an encrypted column without decrypting it (
d23ac96) - authThe signup form contradicted itself, and a saved file went where nobody could reach it (
04f07da) - mobileA locked phone could spend a one-shot pairing code before being told it could not (
6720ab8) - ledgerMoney was formatted by its currency, so a German reader got Dutch (
f33a59f) - mobileThe iPhone could not speak mDNS at all, so it could never find a peer to pair with (
2a05c7e) - goalsThe phone got a percentage where the desktop got a goal (
22fe60f) - syncA killed listener left a child holding the port it was killed to free (
8d43873) - sync"type or scan the code" led to a screen with no scanner (
361581d) - Four defects the code and security review found in this branch's own work (
ba266e9) - importThe tier consulted first still matched a token inside any word (
e6db201) - ledgerA category kept the language it was created in, for ever (
9fbfc04) - authThe signup form trusted a value it could not see the shape of (
754fa5e) - mobileFive full-screen views padded by zero, on Android only (
2459495) - syncThe pull that anyone on the wifi could make, and the timestamp guard that went missing (
7deaf5d) - The seven a reconciliation found still open, and the one a phone disagreed about (
5905b76) - syncA marker doing two jobs silently handed the ledger's key away (
9cb3516) - Six ways the app promised one thing about language and country and did another (
78bebd8) - searchA Dutch reader could not find a category by the name on their screen (
78e095b) - authThe language select on signup reintroduced the shape that shipped two false ticks (
535130e) - importA file that half-parsed imported half a statement and called it done (
cd9e17c) - importThe results screen could only ever show the unhelpful half of what it knew (
b0f422b)
View this release on GitHub →10 installers attached
- potsCategory-linked pots are retired, and their balances are released on upgrade (#399) (breaking) (
- v1.3.0
"Local & in sync" — the largest release to date. Three tracks shipped:
goals & motivation, take-it-with-you, and insight & records. The fourth track
(local-first end-to-end-encrypted device sync) is in active development for a
follow-up v1.4 release.
Added9
- Base-currency FX conversion — non-EUR account balances now roll into a single reporting currency you choose, using a pluggable, offline-capable rate source. Closes the gap where non-EUR accounts were silently excluded from the net-worth headline.
- Savings goals — set a target amount and date, track contributions, and see a forecast-driven projected finish date for each goal. The dashboard surfaces your nearest-finishing goals at a glance.
- Savings pots / envelopes — carve virtual sub-balances ("pots") out of a real account and assign money to them without moving it between banks; pots reconcile against the real account balance so the numbers always tie out.
- Responsive, installable PWA — every screen is now phone-legible, and the app installs to your home screen with an offline app-shell. Financial pages are never cached offline (privacy by construction); the transactions list gets phone infinite-scroll. Charts upgraded (ApexCharts 5).
- PIN / biometric app-lock — protect the app with a PIN (Argon2id + libsodium key-wrap) and/or device biometrics (WebAuthn / Touch ID), with a server-authoritative idle re-lock, a privacy veil, and cross-tab lock sync. Also establishes the at-rest key-unlock gate that the upcoming sync feature will build on.
- Bills / cash-flow calendar — upcoming fixed payments laid out on a calendar with a running projected balance, so you can see what's due and what you'll have left.
- Tax-deductible tagging + per-year export — tag tax-relevant transactions (with an optional deduction category and note) from the transactions list, detail view, counterparty profile, or cash book; review a whole year on the
/taxcockpit; and export the year's set as CSV or PDF for your records. Ships a six-country deduction-category corpus and a setup-wizard step. - Full-text search over your entire history — a fast FTS5 index over merchant names, descriptions, and tax notes powers a ⌘K command palette and a search-and-filter surface on
/transactions, with date / account / amount / category filters and typedaccount:/category:/amount:/after:/before:tokens. - Unusual-charge / anomaly alerts — beatrax now proactively flags charges that look out of the ordinary: much larger than your typical spend for that merchant or category (robust median/MAD statistics), a large charge at a brand-new merchant, or an apparent duplicate within a short window (excluding known recurring series). Alerts appear through the existing alerts surface via a "Subscription drift ↔ Unusual charges" switch, with reason chips, a dashboard tile, and a nav badge. Each alert is acknowledge-/snooze-/dismiss- able; "mark as expected" creates a narrow, server-computed suppression rule you can see and remove in Settings (with one-tap undo). Detection runs on the queue (never slowing imports), with a one-time full-history backfill on first activation. Tunable sensitivity and a minimum-amount floor in Settings.
Security1
- The anomaly feature shipped with a full STRIDE threat model verified against the implementation (22/22 threats closed): every background-job query is explicitly user-scoped, suppression bands are computed server-side (never client-trusted), alert state changes go through a single audited state machine, and cross-user access returns 404.
View this release on GitHub →7 installers attached
- v1.2.0
Added12
CHANGELOG.mdas the source of truth for release notes, automatically published into each GitHub Release body by the release workflow.- Cash book — a new
/cashpage to hand-enter cash and other off-bank spending (or income) so it lands in the same ledger as your imports: manual entries categorise, recur-detect, and count toward your month exactly like imported ones, because they flow through the canonical recording pipeline (a synthetic per-user Cash account + a "manual" source). Manual rows are user-owned and deletable (unlike immutable imported rows). The amount field accepts plain and Dutch-grouped formats; the chosen category is validated as your own; and two genuinely-distinct identical same-day entries both record (the recorder bumps the booking time on a fingerprint collision rather than dropping the second). - Net-worth roll-up — the dashboard leads with one figure across all accounts (assets minus liabilities), with an expandable per-account breakdown. Each account's balance is the same anchor the forecast uses as "today's balance", already sign-correct (a credit card counts as what you owe). Sums EUR-denominated accounts into the headline figure and flags when a non-EUR account is left out (no balance FX conversion yet); the internal
paypal_fundingrouting account is excluded. Backed by a newNetWorthQueryPublic service over the previously-internal balance resolver. - Month-over-month spending — the dashboard shows a "This month vs last" card: the current period's total spend with the signed change against the previous period, and the categories that moved the most (each with its delta). Spending more reads in rose, less in emerald. Uses the same EUR-settled outflow definition as the rest of the ledger, so the figures reconcile; renders nothing until there is a prior period to compare.
- Sidebar count badges — the nav items (Transactions, Recurring, Counterparties, Drift alerts, Budgets, Subscriptions, Imports, Receipts) now show how many items each holds. All counts come from a single per-user cached payload (NavCountsService, short TTL, invalidated on demand) so the sidebar — which renders on every page — never fans out a pile of COUNT queries per render. Large counts are compact-formatted (e.g. "3.1k"), and a count of zero hides the badge to keep the rail calm.
- "You could save here" insights — the dashboard shows a "Ways to save" card that pairs your recurring subscriptions with the most relevant official link from the support-resource corpus: a cheaper / student / retention plan where one exists, a cancellation page when a subscription's price has drifted up, or a gentle review nudge for an ongoing charge. Each suggestion is dismissible (and stays dismissed), and the whole set is cached per user so the dashboard never re-runs the resolution on every render. Purely informational — beatrax surfaces the official link, it never cancels or switches anything.
- Recurring ↔ counterparty navigation — a counterparty profile's Recurring card now lists that merchant's recurring series (each linking to the series detail), and the recurring series detail page links back to the counterparty profile. Resolved through the shared
transactions.counterparty_id, so it is exact rather than name-matched. - Settings redesign — the Settings page is reorganised from a narrow flat list into wider, titled cards (Appearance · Preferences · Forecasting · Importing · Shared merchant list · Data & backup · Help · Developer), keeping every existing control and behaviour, with proper padding from the nav.
- Encrypted backups — Settings → Data & backup can now download a passphrase-encrypted snapshot of your whole database (a
VACUUM INTOcopy, encrypted in place and streamed as a.sqlite.encfile), safe to keep on an external drive or in cloud storage because it is unreadable without the passphrase. Encryption is quantum-safe by construction: a purely symmetric scheme (no public-key step, so no Shor exposure) using Argon2id key derivation and XChaCha20-Poly1305 with a 256-bit key. The plaintext snapshot is locked to 0600 and deleted as soon as it is encrypted; the passphrase never persists. Settings → Data & backup can also restore from an encrypted backup: upload the.enc, enter the passphrase, and type the confirmation phrase. The file is decrypted and integrity-checked first — a wrong passphrase or corrupt backup throws before anything changes — then a pre-restore snapshot of the current database is saved and the verified backup is swapped in (reload to see the restored data). Available on the SQLite (desktop) build; on a server, usephp artisan db:restore(which gates on maintenance mode) instead. - Support-resource profiles — a counterparty profile now shows a "Support & cancelling" card (merchants) or "Getting help" card (government) with the official cancel / help / cheaper-plan links, a
tel:helpline, and — where a service genuinely supports it — a one-click pre-filled cancellationmailto:. Backed by a new bundledresources/corpus/support/<country>.yamlcorpus (researched for ~25 common subscriptions and the main NL agencies; the cancellation method, e.g. online form / phone / registered letter, is shown in a note since none of these document email cancellation). Looked up by word-level brand matching that tolerates legal-entity suffixes ("Netflix International BV" → Netflix) without false-matching ("Apple" never matches "Applebee's"; a plain "Albert Heijn" grocery charge never inherits the "Albert Heijn Premium" cancel card). Link schemes are restricted to http(s) and the mailto address is injection-guarded. - Subscription Drift Watch — a new
/drift/watchpage ("Subscriptions" in the sidebar) that ranks your approved subscriptions by how much their price has crept up since the first charge, each with a baseline → latest figure, a signed €/% delta, an amount-history sparkline, and an "open alert" badge that deep-links to the drift alert. The subscription-centric companion to the alert-centric/driftpage; reuses the recurring occurrence history rather than adding a new store, and reads the full history so the baseline is the true first charge. - Category Budgets — a new
/budgetspage (Budgets module) to set a monthly spending ceiling per expense category and track the current period's spend against it, with a status-coloured progress bar (under / near / over), remaining amount, an inline editor, and a period total. Budget writes are validated against the user's own + global expense categories (a client-supplied category id can never attach a budget to another user's category), and the amount field accepts both plain and Dutch grouped formats. An optional Budgets step is added to the first-run setup wizard so new users can set a few category budgets during onboarding (or skip it); a step added after a user already finished onboarding is seeded as skipped, so finished users are not dropped back into the wizard.
Fixed3
- The receipt-conflict resolver ("Use receipt") no longer 500s on a malformed stored value: a non-JSON
incoming_valueis skipped (not applied) while the pending row is still cleared, matching the read side's tolerance. The demo seeder now JSON-encodes the conflict values like the production producer. - Server-deployment support alongside the NativePHP desktop build.
confignow defines Postgres, MySQL, and MariaDB connections (SQLite stays the default, so the desktop build is unchanged), selectable viaDB_CONNECTION+ theDB_*env vars. A new interactivephp artisan beatrax:setupcommand walks a self-hoster through writing.env(app URL, application key, database), verifies the connection, and hands off tobeatrax:installin a fresh process. Adeploy/server/recipe ships a single-container FrankenPHP image (code + assets baked in) with Postgres, a queue worker, and the scheduler, and.docs/deployment.mddocuments both the Docker and the bare-metal (clone-without-Docker) paths, including logging to stderr, the database queue (no Redis/Horizon on a server), and keeping the Dev Console off in production. - Import other European banks' CSV exports via a preset-driven generic CSV importer. Bundled presets cover N26, Revolut, and ING (Netherlands), each selectable under a new "Other bank" source in the import wizard. The engine handles the cross-bank differences — signed vs separate debit/credit columns vs an "Af/Bij"-style direction indicator, comma-or-dot decimals with thousands separators, per-row or fixed currency, and varied date formats — matches columns by a normalised header name (tolerant of minor spelling differences), skips pending/reverted rows (e.g. Revolut
State) instead of aborting, and rounds sub-cent amounts rather than truncating. Adding another bank is a data-only change (a new entry inCsvPresetRegistry).
Added3
- Government-agency and bank-fee classification now lives in a regex-capable, per-country YAML corpus (
resources/corpus/<type>/<country>.yaml) instead of hardcoded Dutch keyword constants. Patterns may be literal substrings or aregex:body, and the bundled set covers tax / social-security / broadcast-fee agencies across all 27 EU member states plus the UK, US, Canada, and Ukraine (e.g. Finanzamt, DGFiP/URSSAF, HMRC, IRS, Canada Revenue Agency, the German Rundfunkbeitrag). Patterns are collision-safe: risky short acronyms useregex:\b…\bword boundaries or the agency's full name, and agencies whose statement descriptors carry only a payment reference (not the agency name) are deliberately omitted to avoid false positives. - The internal bank-statement parsers are no longer branded around a single bank: the generic CAMT.053 and MT940 adapters, their helpers, and the shared amount parser moved from
Internal/Adapters/Asnto a neutralInternal/Adapters/Bankingnamespace (Camt053Adapter,Mt940Adapter,BankAmountParser, …), and the matching Import-module payment-type hinters toInternal/Parsers/Banking. ASN's own proprietary CSV adapter keeps its name (it is one specific bank's format, like the new N26/Revolut/ING presets). No behaviour change; format keys are unaffected. - The bundled merchant corpus is reorganised into per-country files (
resources/corpus/merchants/<country>.yaml, region inferred from filename) and expanded to ~600 merchants across all 27 EU member states plus the UK, US, Canada, and Ukraine — supermarkets, fuel/energy, telecom, streaming, transport, retail, insurance, and food delivery, with pan-European subscriptions and payment-facilitator prefixes (PAYPAL *,GOOGLE *,AMZN MKTP) inmerchants/eu.yaml. Merchant patterns support the sameregex:prefix as the rest of the corpus, used to give collision-prone short brand tokens (DIA, ICA, NOS, TIM, …) word boundaries so they no longer match inside ordinary words.
Changed4
- User-facing copy and the README now describe beatrax as a generic European personal-finance tool (any bank that exports CAMT.053, MT940, or CSV, plus cards and PayPal) rather than an ASN-specific one. Format-specific names and the genuine ASN-format parsers are unchanged.
- Updated dependencies to clear outstanding Dependabot updates: guzzlehttp/guzzle, google/apiclient-services, nativephp/desktop, larastan, and Pest on the PHP side; fuse.js, vite, and concurrently on the front-end side.
- Renamed the generic statement-format identifiers
asn-camt053→camt053andasn-mt940→mt940, since CAMT.053 (ISO 20022) and MT940 (SWIFT) are pan-European standards rather than ASN-specific formats. Existing imports are migrated automatically; the ASN-specific CSV layout (asn-csv) keeps its name. - The personal-transfer counterparty heuristic now recognises any valid SEPA IBAN, not just Dutch ones, using proper mod-97 + country-length validation (via
jschaedl/iban-validation). A German, French, or Belgian personal transfer is now classified as a person rather than falling through to "unknown". All test/demo fixtures use real, checksum-valid IBANs.
Fixed2
- Resolve seven static-analysis findings surfaced by the larastan/phpstan upgrade (the OAuth secrets store's per-inbox map is now typed with the array-key it actually uses), and cap phpstan's worker count so the quality gate runs deterministically in the Docker toolchain.
- The Docker dev toolchain now points the Pest/PHPUnit suite at the isolated
sqlite_testingconnection instead of the WAL-configuredsqliteconnection, so the full test suite runs cleanly in the container instead of failing thousands of tests on aRefreshDatabaseisolation clash.
View this release on GitHub →7 installers attached
- v1.1.1
What's Changed3
- fix: Windows install time (decrease composer dependency files)
- fix: loopback 403 flood
- chore: standardise on Docker toolchain
View this release on GitHub →7 installers attached
- v1.1.0
This release was published without notes.
View this release on GitHub →7 installers attached