Skip to content

Commit 904aff4

Browse files
Patclaude
andcommitted
release: v1.1.5 — Corbeille, contraste erreur WCAG, i18n erreurs & correctifs
Audit 4-agents (secu/perf-qualite/cablage/coherence-i18n) sur ~24.8k LOC. Aucun CRITICAL/HIGH. Corrections + features anti-perte de donnees. Securite / robustesse: - Purge des sidecars WAL/SHM/journal EN CLAIR a la migration DB plain->chiffre (fuite plaintext sur device roote). Symetrique au wipe du .plain.bak. - Resilience cache embeddings (listByModel tolerant par ligne). - Gardes mounted (ai_chat apres isolate MiniLM, search post-frame). Features (au top): - Corbeille: ecran restaurer/supprimer definitivement/vider + entree drawer + retention 30j (comble le trou: note supprimee irrecuperable via l'UI). - Contraste erreur WCAG: helpers showErrorSnack/showSuccessSnack sur ScaffoldMessengerState + migration de tous les sites d'erreur (dont le helper central _showError de l'editeur). Contraste enfin effectif. - i18n erreurs: EmbedderCoordinator -> enum de codes + cles l10n ; erreur hash Gemma -> cle localisee (fini le FR brut en locale EN). - Banner brouillons vault perdus: lu au boot + dismissible (pref F11 etait ecrite jamais lue). i18n/UX: langue transcription vocale = locale, titre privacy -> cle l10n, depreciation cacheExtent->scrollCacheExtent. Nettoyage: code mort retire (recent, toggleArchive, saveAll+upsertBatch). Verif: flutter analyze 0 issue, 84 tests, release signe. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 4fdf6ab commit 904aff4

28 files changed

Lines changed: 1048 additions & 140 deletions

SECURITY.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,37 @@
11
# Security policy — Notes Tech
22

3-
**Version current : v1.1.0 — Mai 2026.**
3+
**Version current : v1.1.5 — Juillet 2026.**
4+
5+
## v1.1.5 — Audit expert post-v1.1.4 (2026-07-07)
6+
7+
Audit 4-agents (sécu / perf-qualité / câblage / cohérence-i18n) sur ~24,8k LOC.
8+
Aucune faille CRITICAL/HIGH. Corrections sécu/robustesse + features anti-perte
9+
de données. `flutter analyze` 0 issue, 84 tests verts.
10+
11+
### Sécurité / robustesse
12+
13+
- **Purge des sidecars WAL/SHM/journal EN CLAIR** lors de la migration DB
14+
héritée plain → chiffré (`database.dart._migratePlainToEncrypted`). Le mode
15+
`journal_mode = WAL` laissait `notes_tech.db-wal`/`-shm` contenant des pages
16+
de notes en clair, survivant à la migration (fuite sur device rooté /
17+
extraction physique). Désormais purgés après `sqlcipher_export` + checkpoint,
18+
symétriquement au wipe du `.plain.bak`.
19+
- **Résilience du cache d'embeddings** (`embeddings_dao.listByModel`) : une
20+
ligne empoisonnée (blob de taille incohérente) ne condamne plus tout le
21+
chargement du cache sémantique — décodage tolérant par ligne.
22+
- **Gardes `mounted`** ajoutées (`ai_chat` après l'isolate MiniLM, `search`
23+
post-frame) : plus de `setState` après `dispose`.
24+
- **Signalement anti-perte vault** : les notes de coffre dont la dernière
25+
modification a été perdue (coffre verrouillé pendant la sauvegarde) sont
26+
désormais signalées par un banner à l'ouverture — la pref `vault_lost_drafts`
27+
(F11 v1.1.0) était écrite mais jamais relue.
28+
29+
### Point ouvert
30+
31+
- `NoteActions.cancelAndClear()` (vidage immédiat du presse-papiers) n'est PAS
32+
câblé dans `PanicService` : après un partage/copie de note, le presse-papiers
33+
repose sur l'auto-clear 60 s. À câbler dans l'orchestrateur panique pour un
34+
effacement immédiat.
435

536
## v1.1.0 — Audit expert post-v1.0.9 (2026-05-14)
637

android/gradle.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,7 @@ android.enableR8.fullMode=true
1111
# `builtInKotlinServices$delegate`). Cf. android-security-patterns.md §11.
1212
org.gradle.configuration-cache=false
1313

14+
# This builtInKotlin flag was added automatically by Flutter migrator
15+
android.builtInKotlin=false
16+
# This newDsl flag was added automatically by Flutter migrator
17+
android.newDsl=false
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
v1.1.5 — Trash, contrast & fixes
2+
3+
New: a Trash (restore / delete permanently / empty) for your deleted notes, with 30-day retention.
4+
5+
Fixes: better-contrasted error messages (WCAG) that are finally translated, voice dictation in the app language, alerts for vault notes whose edits were lost, purge of plaintext traces when encrypting the database.
6+
7+
100% local, zero Internet.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
v1.1.5 — Corbeille, contraste & correctifs
2+
3+
Nouveau : une Corbeille (restaurer / supprimer définitivement / vider) pour vos notes supprimées, avec rétention 30 jours.
4+
5+
Corrections : messages d'erreur mieux contrastés (WCAG) et enfin traduits, dictée vocale dans la langue de l'app, alerte pour les notes de coffre dont une modification s'est perdue, purge des traces en clair lors du chiffrement de la base.
6+
7+
100 % local, zéro Internet.

lib/core/constants.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class AppConstants {
55
AppConstants._();
66

77
static const String appName = 'Notes Tech';
8-
static const String appVersion = '1.1.4';
8+
static const String appVersion = '1.1.5';
99
// NB : la clé Kotlin équivalente côté `MainActivity.kt` est
1010
// `flutter.secure_window_enabled` (préfixe `flutter.` ajouté
1111
// automatiquement par `shared_preferences` au moment de la persistance).

lib/data/db/database.dart

718 Bytes
Binary file not shown.

lib/data/db/embeddings_dao.dart

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
/// Accès direct à la table `note_embeddings`.
22
library;
33

4-
import 'dart:typed_data';
5-
4+
import 'package:flutter/foundation.dart';
65
import 'package:sqflite_sqlcipher/sqflite.dart' hide DatabaseException;
76

87
import '../../core/exceptions.dart';
@@ -37,7 +36,21 @@ class EmbeddingsDao {
3736
where: 'model_id = ?',
3837
whereArgs: [modelId],
3938
);
40-
return rows.map(_fromRow).toList(growable: false);
39+
// Décodage tolérant : une seule ligne empoisonnée (blob de taille
40+
// incohérente / dim invalide → ArgumentError de decodeBlob) ne doit pas
41+
// condamner tout le cache sémantique. On la saute ; la note sera
42+
// ré-indexée au prochain passage d'indexation.
43+
final out = <NoteEmbedding>[];
44+
for (final row in rows) {
45+
try {
46+
out.add(_fromRow(row));
47+
} catch (e) {
48+
if (kDebugMode) {
49+
debugPrint('[EmbeddingsDao.listByModel] ligne ignorée: $e');
50+
}
51+
}
52+
}
53+
return List.unmodifiable(out);
4154
} catch (e) {
4255
throw DatabaseException('emb.listByModel échoué', cause: e);
4356
}
@@ -77,28 +90,6 @@ class EmbeddingsDao {
7790
}
7891
}
7992

80-
/// Insertion en lot dans une transaction unique.
81-
Future<void> upsertBatch(Iterable<NoteEmbedding> items) async {
82-
if (items.isEmpty) return;
83-
try {
84-
await _db.transaction((txn) async {
85-
final batch = txn.batch();
86-
for (final e in items) {
87-
batch.insert('note_embeddings', {
88-
'note_id': e.noteId,
89-
'vector': VectorMath.encodeBlob(e.vector),
90-
'dim': e.dim,
91-
'model_id': e.modelId,
92-
'source_hash': e.sourceHash,
93-
'updated_at': e.updatedAt.millisecondsSinceEpoch,
94-
}, conflictAlgorithm: ConflictAlgorithm.replace);
95-
}
96-
await batch.commit(noResult: true);
97-
});
98-
} catch (err) {
99-
throw DatabaseException('emb.upsertBatch échoué', cause: err);
100-
}
101-
}
10293

10394
Future<int> deleteByNoteId(String noteId) async {
10495
try {

lib/data/repositories/embeddings_repository.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ class EmbeddingsRepository {
1414
Future<Map<String, int>> sourceHashes(String modelId) =>
1515
_dao.listSourceHashes(modelId);
1616
Future<void> save(NoteEmbedding e) => _dao.upsert(e);
17-
Future<void> saveAll(Iterable<NoteEmbedding> items) =>
18-
_dao.upsertBatch(items);
1917
Future<void> remove(String noteId) => _dao.deleteByNoteId(noteId);
2018
Future<int> deleteOrphans(Set<String> aliveIds) =>
2119
_dao.deleteOrphans(aliveIds);

lib/data/repositories/notes_repository.dart

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,6 @@ class NotesRepository {
4646
}) =>
4747
_dao.listByFolder(folderId, sort: sort, includeArchived: includeArchived);
4848

49-
Future<List<Note>> recent() =>
50-
_dao.listRecent(limit: AppConstants.recentNotesLimit);
51-
5249
/// Toutes les notes vivantes (hors corbeille). Utilisé par l'indexation
5350
/// d'embeddings — pas par l'UI.
5451
Future<List<Note>> listAllAlive() => _dao.listAllAlive();
@@ -126,9 +123,6 @@ class NotesRepository {
126123
Future<Note> toggleFavorite(Note note) =>
127124
_toggleFlag(note, note.copyWith(favorite: !note.favorite));
128125

129-
Future<Note> toggleArchive(Note note) =>
130-
_toggleFlag(note, note.copyWith(archived: !note.archived));
131-
132126
Future<Note> _toggleFlag(Note original, Note candidate) async {
133127
final updated = candidate.copyWith(updatedAt: DateTime.now());
134128
await _dao.update(updated);

lib/l10n/app_en.arb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,26 @@
6666
"homeUnarchive": "Unarchive",
6767
"homeMoveTo": "Move to…",
6868
"homeTrash": "Move to trash",
69+
"homeVaultLostBanner": "{count, plural, =1{1 vault note lost its latest changes} other{{count} vault notes lost their latest changes}} (vault locked during save).",
70+
"@homeVaultLostBanner": {
71+
"placeholders": { "count": { "type": "int" } }
72+
},
73+
"drawerTrash": "Trash",
74+
"trashTitle": "Trash",
75+
"trashEmptyTitle": "Trash is empty",
76+
"trashEmptySubtitle": "Deleted notes appear here before permanent removal.",
77+
"trashRetentionNotice": "Notes in the trash are automatically deleted after {days} days.",
78+
"@trashRetentionNotice": {
79+
"placeholders": { "days": { "type": "int" } }
80+
},
81+
"commonRestore": "Restore",
82+
"trashRestored": "Note restored",
83+
"trashDeleteForever": "Delete permanently",
84+
"trashDeleteForeverTitle": "Delete permanently?",
85+
"trashDeleteForeverBody": "This note will be permanently erased. This cannot be undone.",
86+
"trashDeletedForever": "Note permanently deleted",
87+
"trashEmptyAll": "Empty trash",
88+
"trashEmptyAllConfirm": "Permanently delete all notes in the trash? This cannot be undone.",
6989
"homeRestore": "Restore",
7090
"homeNoteDeleted": "Note deleted",
7191
"homeUndo": "Undo",
@@ -126,6 +146,9 @@
126146
"settingsThemeDark": "Dark",
127147
"settingsSemanticSearch": "Advanced semantic search (MiniLM)",
128148
"settingsSemanticSearchSubtitle": "More relevant, slow first indexing. Can be disabled at any time.",
149+
"embedderErrorModelAbsent": "MiniLM model missing from the app.",
150+
"embedderErrorLoadFailed": "Failed to load the semantic model.",
151+
"embedderErrorDowngradeFailed": "Failed to switch back to lightweight mode.",
129152
"settingsSecureWindow": "Hide in recent apps",
130153
"settingsSecureWindowSubtitle": "Prevents screenshots and hides the app preview in the Android task switcher.",
131154
"settingsVaultAutoLock": "Vault auto-lock",
@@ -159,6 +182,7 @@
159182
"@aboutVersion": { "placeholders": { "version": { "type": "String" } } },
160183
"aboutTagline": "Your notes stay in your pocket. The AI too.",
161184
"aboutSectionPrivacy": "Privacy",
185+
"aboutPrivacyCardTitle": "100% private — zero surveillance",
162186
"aboutPrivacy1": "No network connection — verifiable in the manifest",
163187
"aboutPrivacy2": "No account, no sign-up",
164188
"aboutPrivacy3": "No tracker, no advertising",

0 commit comments

Comments
 (0)