Skip to content

3.4.0: blind drop, notes with a lifetime, sealed notes, shortcuts, keyboard without learning - #53

Merged
gitubpatrice merged 11 commits into
mainfrom
feat/3.4.0
Nov 9, 2026
Merged

gitubpatrice merged 11 commits into
mainfrom
feat/3.4.0

Conversation

@gitubpatrice

Copy link
Copy Markdown
Owner

Notes Tech 3.4.0 (versionCode 507) — five features, all local, no new permission.

Features

  • D-040 Keyboard without learning — IME_FLAG_NO_PERSONALIZED_LEARNING on every field of the app, on by default (Settings → Security), always on secret fields. Suggestions stay; a keyboard may ignore the request.
  • D-041 Launcher shortcuts — New note, Dictate a note, Quick drop, through a non-exported activity; MainActivity still ignores intents.
  • D-042 Blind drop — a quick settings tile and a shortcut open a screen to write a note into the Inbox without unlocking the app and without being able to read anything. The phone is unlocked first (unlockAndRun); refused while a panic is pending; FLAG_SECURE.
  • D-043 Notes with a lifetime — 1 h, 24 h, 7 d, 30 d, or after reading. Erased by the same DELETE as "Delete permanently" (FTS index, secure-delete, links), at the app's opening and every minute on the list; left out of backups and exports. Room schema 11, additive migration 10 → 11 (expires_at, read_once), tested from a real 3.3.0 database.
  • D-044 Sealed notes — share one note as an encrypted .ntnote (the backup's container under its own magic: Argon2id + AES-256-GCM, a 120-bit code shown once); open one through "Open with" or the home menu, the code asked once the app is unlocked; it becomes one note of the Inbox, never a vault.

Reviews

  • External review (GPT): 3 defects fixed — the received note created in one insert; reading another app's file bounded (one at a time, own thread, stream closed past the delay, zero-byte reads refused, provider runtime errors caught).
  • Coherence audit (82/100), translations audit, 3-axes audit (0 critical, 0 high, 7 medium): applied, including FLAG_SECURE from the first frame on every dialog that shows or takes a code (the backup's three had the same defect).
  • Known limits, documented in docs/15-VERSION-3.4.md: Samsung My Files does not offer Notes Tech for .ntnote (empty MIME type; the home menu works everywhere); a shared file can be purged if the process restarts — via the tile — before a slow receiving app reads it.

Tests

  • Local gate green: build, JVM tests, lint, detekt, ktlint, instrumented sources.
  • Galaxy S9 (Android 10): full instrumented suite run, then the touched classes again after the fixes (106 cases, 0 failures); new NotesScelleesRecuesTest, LectureUniqueTest, MigrationVersDixTest, ClavierSansApprentissageTest.
  • End to end on the S9: shortcuts through the Samsung launcher, drop with the app lock on, a sealed note sealed, refused with a wrong code, opened with the right one typed in lowercase, erased after reading.
  • Not tried on a device: the tile on a locked phone; "Open with" from a messaging app.

AGP 9.5.0 / Gradle 9.8.2 came with #51; this is the first release built with them — F-Droid has not built one yet (SMS Tech 1.30.0 is still pending there).

🤖 Generated with Claude Code

gitubpatrice and others added 11 commits November 9, 2026 19:43
… notes

Decisions D-040 to D-044, written before the code, with the facts read in the
code that shape them. REPRISE points to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
D-040 — Keyboard without learning, on by default (Patrice). Every field asks
IME_FLAG_NO_PERSONALIZED_LEARNING while the setting is on; a secret's field
always does. The interceptor of SaisieDeCoffre is shared (ClavierSansApprentissage),
the note's native body reads a CompositionLocal. Setting in Security, 5 languages.

D-041 — Launcher shortcuts "New note" and "Dictation", published by the code
(the app has several application ids), in the app's language. They land on a
NON-exported activity that deposits the request in memory and opens the app,
which carries it out once unlocked: MainActivity keeps ignoring intents. The
in-memory deposit of the share receiver becomes a generic DepotEnMemoire.
Dictation opens the microphone as a tap on its button would. Checked on the
S9: the Samsung launcher starts the non-exported activity; New note opens a
blank note to be written; Dictation, without a model, the install screen.

D-042 — Blind drop: write a note without unlocking the app, read nothing.
DepotActivity (not exported, protected window, text actions of other apps and
classifier cut since it can be the first activity of its process, text never
in the saved state), its quick settings tile (unlockAndRun when the phone is
locked) and a third shortcut. The note goes to the Inbox, never a vault;
nothing is written while a panic is pending.

The language of an activity and the dark/light choice are shared helpers now
(avecLaLangueChoisie, estSombre) instead of living in MainActivity alone.

Tests: ClavierSansApprentissageTest (3), ChampDuCorpsTest +1, ReglagesHeritesTest +1
(instrumented, S9); RaccourcisTest (4), DepotViewModelTest (5) on the JVM.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two columns on notes, migration 10 -> 11, additive: expires_at (epoch ms,
NULL for never) and read_once (INTEGER NOT NULL DEFAULT 0). Every existing
note gets no lifetime.

- NotesRepository.setLifetime writes those two columns only, not updated_at:
  giving a note a lifetime is not editing it. A duration counts from now.
- purgeExpired: one DELETE, the one "Delete permanently" runs, so the FTS index
  (notes_ad, secure-delete), the links (foreign keys) and secure_delete treat an
  expired note as an erased one. The trash's notes too; a vault note needs no key.
- Bulk export (listAllAlive) and the encrypted backup (poidsDesNotes) leave
  such notes out (Patrice: a restore must not bring them back); a restored note
  has no lifetime.
- audits/verifier-schema-room-vs-flutter.py replays 10 -> 11: no divergence
  between the Flutter schema migrated to 11 and Room's 11.json.

Tests on the S9: MigrationVersDixTest (3, a new case for the base of 3.1 to
3.3 at schema 10, migrated by MIGRATION_10_11 alone; the purge erases that
note and that note only), LegacyDatabaseOpeningTest (15), NoteLinkTest (5),
NotesRepositoryTest (67), SauvegardeChiffreeTest (9) — 0 failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The editor's ⋮ menu has "Lifetime…": none, 1 hour, 24 hours, 7 days, 30 days,
  after reading. A tap applies it. The card shows what is left ("5 h", "after
  reading"), TalkBack says it, on a locked vault note too — not a secret.
- Notes whose lifetime is over are erased at the opening of the app
  (StartupViewModel) and every minute while the list is on screen.
- "After reading" erases the note in the editor ViewModel's onCleared, after the
  last save — not on the screen's disposal, which a rotation triggers. A choice
  made while the note is open counts from its next opening; the reader is told
  once the note is open.
- detekt's thresholds moved the lifetime out of NotesRepository (DureesDeVie)
  and grouped the card's two sheets (FeuillesDeLaFiche) on the editor screen.
- Texts in the five languages, plurals included.

Tests: ResteDeVieTest (5) and DureesDeVieTest (5) on the JVM; on the S9
LectureUniqueTest (3: erased at closing; control, chosen in the session;
witness without lifetime), EditeurTest (36), RechercheDansLaNoteEcranTest (5),
FermetureDuCoffreTest (9), LiensDansUnCoffreTest (3), GestesSurDesNotesTest (14),
AccueilTest (20) — 0 failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The encrypted backup's container — Argon2id, AES-256-GCM, the 48-byte header
as AAD — under its own magic, NTSEALED, with its own bound (4 MiB: a note of
500 000 characters may weigh 1.5 MB in UTF-8). FichierDeSauvegarde takes the
magic and the bound as parameters whose defaults are the backup's: a backup is
written and read exactly as before.

One note as JSON (title, content, colour, tags, readOnce), sealed by a
generated code of 24 signs (120 bits). Only a code opens it: a typed text that
is not one is refused before any derivation. The tag is checked before the
JSON is read; a wrong code and a damaged file look the same. The limits of a
note are checked on what is read.

Tests on the S9: FichierDeNoteScelleeTest (6: round trip however the code is
typed and nothing in clear in the file; another code; a damaged file; a text
that is not a code; a backup or any other file; past the bound), and the
backup unchanged: SauvegardeChiffreeTest (9), LectureDuContenuTest (4), its
JVM tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sending — the editor's ⋮ menu, "Share sealed…": the note as it is on screen,
sealed off the main thread into note-YYYYMMDD.ntnote (the name says nothing of
the note) in the exports directory (purged at start and by the panic), with an
option "read once by the recipient". The code is shown once, spelled for
TalkBack, can be copied through the sensitive clipboard, and is forgotten when
the sheet closes; a secured window while it is open. "Share the file" goes
through the exports' FileProvider, as the export does.

Receiving — "Open with" (an exported activity, VIEW on application/octet-stream,
content:// of another app only, bounded in size and time, magic checked: any
other file is said so and the app does not open) and the home screen's ⋮ menu,
"Open a sealed note" (the system picker). Both deposit the still-sealed bytes
in memory; once the app is unlocked, a dialog asks for the code (never in a
Bundle). The right code makes it a note of the Inbox — never a vault —, colour
and tags kept, "after reading" if asked, and opens it.

The code's display moves to ui/common (CodeAffiche), shared with the backup.
Texts in the five languages.

Tests: LectureScelleeTest (4, JVM: a sealed note read whole; another file
dropped at its first bytes, not read to its end; past the bound; empty).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A file manager gives an unknown extension no type, or a generic one. Two more VIEW filters on the .ntnote path, with no type and with any; the magic is checked in any case. Resolution checked with cmd package query-activities on the S9: no type, octet-stream, another type with a .ntnote path reach the activity; a .jpg does not. Samsung My Files still says no app can open it: with an empty MIME type it decides alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- version.properties: versionCode 507, versionName 3.4.0.
- fastlane: changelogs 5071-5073 in the five locales (405 to 428 characters,
  under 500 bytes too); four lines in each full description (lifetime, sealed
  note, quick drop, keyboard).
- Privacy policy, five languages (dated 9 November 2026): sealed notes (the
  file, its code, where it goes; a received one), quick drop, notes with a
  lifetime (left out of backup and export), the keyboard's learning.
- README (EN, FR): what is new in 3.4.0. SECURITY (EN, FR): v3.4.0, the new
  doors and how narrow each is kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
External review (GPT):
- a received sealed note is created in ONE insert, colour and "after reading" included;
- reading a sealed file: one at a time, on a thread of its own, the stream CLOSED past the delay, a
  zero-byte read refused, any runtime error of another app's provider caught.

Coherence audit: the sealing sheet forgets its code whenever it leaves; the keyboard setting's callback
has no default; the picker's reading in its own ViewModel; one guarded purge (`purgerSansEchec`); the
purge loop runs only while the list is started; KDoc put back in place; no ellipsis on the two new menu
entries; `ClavierSansApprentissageTest` on the v2 Compose rule.

Translations: Italian and German wording, German changelogs completed.

3-axes audit:
- M4: a second sealed file deposited while the first is being opened waits for it — it no longer zeroes
  the bytes under decryption, nor is it erased by the first one's end;
- M5: FLAG_SECURE from the first frame on the dialogs where a code is shown or typed — the two sealed
  ones and the three of the backup, which had the same defect (`ProprietesDeDialogueSecret`);
- M7: a hand-made sealed file's tags are split, trimmed and deduplicated as the import does, its title
  put on one line without bidirectional overrides;
- M1, M2: the lifetime sheet and SECURITY say the time follows the phone's clock, and when "after
  reading" really erases.

Tests: `NotesScelleesRecuesTest` (wrong then right code, one note with what was sealed, no duplicate,
the second file waiting), a hand-made file in `FichierDeNoteScelleeTest`, the settings tests with the
keyboard row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
)
} else {
@Suppress("DEPRECATION")
startActivityAndCollapse(intention)
@gitubpatrice
gitubpatrice merged commit d523f1c into main Nov 9, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants