- Where a test presets a harness slot by position (PriceFetchConsentToggle,
useOnboardingChecklist's mountWith, SystemsSettingsPage), a comment names
the dependency on the component's hook order: any hook added before shifts
the slots without a word (the `slot` helper of fakeReact).
- docs/architecture.md, Tests: "Limites connues" lists the slot presets, and
links to ADR 0021 for withoutFolders and for the catch-up measurement
instead of repeating them.
- ADR 0021 section 7: the relative-path limit is stated precisely (a run with
two separators or more is still cut to its last segment; only the words of
a folder holding a blank stay), and a second limit is written: a folder
name holding one of the stops (a backtick, `;`, `|`, a closing parenthesis
not followed by a separator...) ends the path there, and that name, or its
start, stays in the line.
- db.ts: the comment of withoutFolders says "a closing parenthesis not
followed by a separator", as the code does, and points to ADR 0021.
No behaviour change.
Resolves#437
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- withoutFolders no longer keeps the first word of a folder that holds
a blank (`C:\Users\Jean Dupont\...\profile_x.db` gave
`Jean profile_x.db`): a path starts at a recognised prefix (drive,
`/`, `~/`, `./`, UNC, `<profile folder>`) and runs to its last
separator, blanks included, until a line break, a double quote, a
backtick, `;`, `|`, a `,` or `:` before a blank, a closing parenthesis
not followed by a separator, or a blank before another path. When
nothing ends it, the text up to the last separator of the line is
dropped rather than a folder let through. A quote before the path is
not part of it, so `'C:\Users\Bob\x.db'` gives `'x.db'`. Relative runs
with no recognised prefix keep the previous fallback. No error line
contains a `/` nor a `\`, as before.
- ADR 0021 section 7 describes that rule and its limit.
- CHANGELOG.md, CHANGELOG.fr.md: no blank line between the two Fixed /
Corrigé bullets.
Refs #395
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- close() on a getDb() handle runs as a "connection close" section and
checks its generation: a handle kept from a previous profile rejects
with DbConnectionClosedError instead of closing the current profile.
- closeUnlocked never rejects: the state is cleared and the generation
bumped before close(), a failing close of the old pool is logged with
logWarn, connectToProfile still opens the next profile and closeDb()
resolves without a connection.
- The blocking alert names a getDb() call in a callback as a possible
cause only: a long restore also waits 15 s.
- The generation check of the transaction handle is documented as a
defence, not reachable today.
- Tests: the "behind the queue" alert (pins the reset of lockHolder),
close() on a stale and on the current handle, a failing close during a
switch and in closeDb().
Refs #408
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ProfilePreparation.catch_up_backup carries the file name of the copy as
soon as it exists, so the Journal error line of a catch-up that stops
afterwards (history_write_failed, pragma_failed, apply_pending) names it.
- Error lines and the JS fallback message keep only the last segment of a
path (withoutFolders): no error line contains a slash or a backslash.
- The copy is taken again at every catch-up, written as .tmp, synced and
renamed over a same-name copy; after a successful catch-up only, the
other copies of that profile are pruned (remove_backups keep argument).
- Pins: a pre-existing foreign key violation in the profile without
category 31 (RV4, RV7) and the Journal line that reports it (RT2).
- Ignored test measuring prepare_at on 100 000 transactions at floor 6:
306 ms in release, so STARTUP_TIMEOUT_MS stays 10 s (ADR 0021).
- Guide, docs.profiles.tips, ADR 0021, architecture and changelogs.
Resolves#395
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The global inTransaction flag let every execute/select bypass the FIFO
lock while a withTransaction callback ran, wherever it came from. The
callback now receives its own handle, which runs directly, refuses
close() and throws once the callback settled; any other call, getDb()
from a callback included, queues behind the transaction.
connectToProfile, initializeNewProfileDb and closeDb each run as one
exclusive section (close the old pool, prepare, load), and every open
or close bumps a connection generation: an operation queued for a
closed connection throws "connection closed by profile switch". An
operation waiting more than 15 s for the lock writes one Journal
warning naming the holder and since when.
findOrCreateSecurity now requires its executor: its getDb() fallback
would deadlock if called from a transaction callback.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tauri-plugin-sql migrates only the database registered with add_migrations,
the default profile's. A secondary profile kept the consolidated schema of
the version that created it and never received a migration afterwards.
prepare_profile_db runs before every Database.load:
- secondary profiles catch up with the same migrations() list (pinned
SHA-384), history kept in _sqlx_migrations; a database without history
gets a floor inferred from its schema (the seven released creation
states are test fixtures), so nothing at or below it is ever replayed;
- a VACUUM INTO copy is taken before the first write, synced, then renamed;
- each version runs in its own transaction with foreign keys on; one that
fails on a foreign key is retried once without them and its orphans are
set to NULL where the key declares ON DELETE SET NULL;
- every profile gets its TEXT manual-categorization flags repaired;
- failures are logged with file names only and the profile still opens.
New profiles are born with their full history (get_new_profile_init_sql).
The plugin keeps databases under app_config_dir(), not app_data_dir():
the same folder on Windows and macOS, not on Linux. repair_migrations,
delete_profile_db and compute_account_return looked in the wrong folder
there. profile_db_path resolves the plugin's folder and validates the
file name; deleting a profile now also removes its SQLite side files and
catch-up copies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Database lock (the reported 0.10.0 regression):
- tauri-plugin-sql loads SQLite through a default multi-connection sqlx pool
(Pool::connect => max_connections = 10) and exposes no JS transaction
primitive. BEGIN and COMMIT issued as separate db.execute calls could land on
different pooled connections, stranding an open write transaction on an idle
connection — a permanent write lock until app restart. Triggered when the
Bilan page's concurrent reads interleaved with an in-flight snapshot save.
- Fix: funnel every db operation through one FIFO lock in db.ts; withTransaction
holds it across the whole BEGIN..COMMIT so a transaction's statements never
span connections (and the saves become genuinely atomic). A reentrancy guard
lets nested getDb() calls run directly instead of deadlocking. Applied to all
5 transaction sites (saveSnapshotAtomic, upsertSnapshotLines,
proposeStarterAccounts, applyKeywordWithReassignment, applyMigration) via
in-place helper extraction. New db.test.ts covers serialization,
cross-transaction non-interleaving, reentrancy, and lock release on error.
Log console (Settings -> Systems -> Journaux):
- getLogs returned the same mutated array reference, so useSyncExternalStore
(identity comparison) never re-rendered on a new log -> the console never
updated live. getLogs now returns an immutable snapshot rebuilt on each
mutation (stable between mutations, new identity after each).
- Add logInfo/logWarn/logError app-logging API and instrument the snapshot save
(info on success, error on failure) so DB issues surface in the console.
tsc + 635 vitest + vite build green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add repair_migrations Tauri command that deletes stale migration 1
checksum from _sqlx_migrations before Database.load(). Migration 1
is idempotent (CREATE IF NOT EXISTS) so re-applying is safe.
Fixes "migration 1 was previously applied but has been modified".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each profile gets its own SQLite database file for complete data isolation.
Profile selection screen at launch, sidebar switcher for quick switching,
and optional 4-6 digit PIN for privacy. Existing database becomes the
default profile with seamless upgrade.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>