Repository navigation
Notes Tech 3.1.0: reading mode, long press, colours, legal pages - #27
Merged
Merged
Conversation
A long press on a note card opens a sheet with its two actions, named by the note the way its card
names it ("Locked note" for a vault note, never its title). "Move to trash" says so in a message with
an Undo action (Long duration, so it does not stay over the list); "Delete permanently" asks the trash
screen's own question first, since it skips the trash. On the home screen and in its in-place search.
Vault notes. Trashing or erasing a note needs no key, so nothing structural stopped it: from the list,
whoever got past the app lock could have destroyed a closed vault's notes without its secret. The check
lives in HomeViewModel.executer, on a fresh read of the note at execution time — the card's copy can
be stale, and the automatic lock can fall while the sheet or the confirmation is open. A gesture on a
closed vault asks for its secret and runs once it is open; a long press on a closed vault's note opens
the unlock sheet first. Undo needs no secret: the note comes back sealed, as it left.
TalkBack: the long press is on the card's merged node with a label ("Note actions"), as the click is —
an action set on a child would not reach the node a screen reader focuses (§74).
No duplication: the menu entry of the folders' sheet and the trash's confirmation dialog moved to
ui/common and are shared; the card's displayed-title rule is one function used by the card and the
sheet. HomeRoute's new state lives in GestesDeNoteEnCours (detekt's complexity cap, not raised).
Tests (emulator API 34): GestesSurUneNoteTest, 5 cases on a real SQLCipher base and a real passphrase
vault — each refusal paired with the same gesture succeeding once the vault is open, the stale-card
case, undo on an ordinary and on a vault note; AccueilTest 16/16, with the long press by touch and by
its semantics action, label checked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tapping a note in a list now shows it to be READ: its title as a heading, its Markdown drawn (D-024), no field — so no cursor and no keyboard —, the links panel under it, the ⋮ menu unchanged, and an "Edit" button at the bottom right. Asked by Patrice (reference: StyncyNotes on F-Droid); no setting to get the old behaviour back, on his decision. - Decided once, when the note has loaded: an existing note opens to be read, a blank one (just created, created by a [[link]], or emptied) to be written — there is nothing to read. Saved across a rotation and the app lock. - "Edit" puts the cursor in the body. The ✓ tick, a writing tool with the microphone and the link button, now saves and shows the note to be read instead of leaving; leaving stays the arrow's and the system Back's. - The Edit / Preview switch is gone, and its string with it (5 languages): reading replaces Preview. - Room is kept under the links panel for the button, which would otherwise cover its last links. - A vault note reads on the same screen: the forced FLAG_SECURE, the session and the automatic lock are unchanged, and reading opens no keyboard, so a third-party keyboard sees nothing. The rendering stays non-selectable, as the preview was: no copy path around the vault's protected clipboard. Found by the editor's accessibility sweep, fixed before commit: the ExtendedFloatingActionButton overload with `text =` and `icon =` did not merge its label into the button's node — TalkBack would have announced an unnamed button. The content overload merges it. Tests (emulator API 34): EditeurTest 33/33 — the tick shows the reading and "Edit" focuses the body, reading keeps the menu and drops the writing tools, an untitled note reads "Untitled", "Edit" stays on screen at the end of a long note, the sweep on the reading side; ApercuDeBoutEnBoutTest 2/2 through the real route: an existing note opens to be read (no tap), the note a link creates opens to be written, and the links panel still hides while the keyboard is up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…by one
GPT-5.6 sol (0.41 $) and Claude Opus 5.5 (0.50 $) on the two previous commits. Every finding was
checked in the code before anything was changed.
Kept:
- The resume after the unlock was untested — both reviews. The instrumented tests unlocked the vault
and called the gesture again by hand; nothing ran GestesDeNoteEnCours. New JVM test
(GestesDeNoteEnCoursTest, 5 cases): the sheet opens once the vault is open, a refused gesture runs
once reopened and once only, giving up the secret gives up the gesture. The instrumented tests are
renamed for what they check: refused while closed, allowed once open.
- reprendreApres cleared everything before comparing (GPT): a gesture waiting for vault A was dropped
when vault B's sheet closed. Only what waits for the unlocked vault is taken off now. Proved by
sabotage: the old version fails "what waits for vault A survives the unlock of vault B".
- Nothing checked that the tick saves (both): the screen tests replace the Route's onTerminer. New
end-to-end test through the real route: the text is in the base 24 ms after the tick (measured),
read within 250 ms, under the 500 ms auto-save delay. Proved by sabotage: without saveNow() it fails
on that very assertion.
- That test found a real defect: "Edit" left the cursor at the START of the note (a loaded value
starts at 0), so typing went before the first word. "Edit" now puts the cursor at the end.
- The vault check is not atomic with the write (both): accepted and written down in the KDoc — an
automatic lock falling in the microseconds between the check and the DELETE completes a gesture
made while the vault WAS open, which the editor's trash has always done; a gesture on a vault
already closed is refused. Proved by sabotage: without the check, the three refusal tests fail.
Rejected, with the reason:
- "Pending gestures are lost on rotation" (both): MainActivity declares configChanges for
orientation and screen size (AndroidManifest.xml:67) — a rotation recreates neither the
composition nor the collectors.
- "Dictation survives the tick and flips the screen back to writing" (Claude): the dictation overlay
is a modal AlertDialog with onDismissRequest = {} for its whole duration; the tick cannot be
reached while it is up.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the editor's ⋮ menu, "Color": no colour or one of eight (yellow, orange, green, teal, blue, purple, pink, gray — no red, the border of a locked note). The card in the lists takes a pale background and a stronger border of the same hue, as Patrice asked; TalkBack names the colour, as the card says everything it shows. Home, search and trash draw it with one rule (couleurVisible). Vault notes — option A, Patrice, 2026-10-10. The colour is stored in clear in the database (encrypted by SQLCipher as a whole), outside the vault's envelope, like the tags. A sealed note's card says "Locked note" and hides its title, excerpt and tags; it shows its colour only while its vault is open — where it is the one way to tell a vault's notes apart — and turns neutral again when the vault closes, automatic lock included (the home list follows the sessions' flow). Search and trash pass no open vault. The privacy policy says it (next commit). What is stored is a palette id (NoteColor.id, 1-8), never a colour: the pair is chosen on screen for the surface really drawn (the threshold of SemanticColors), since a light pastel under the dark theme's light text, or the reverse, is unreadable. An id this version does not know reads as no colour. Contrast measured, not eyeballed: every background against the app's own text and icon colours, both themes — body 11.9-14.7:1, date/excerpt/tags 4.51-4.89:1 (AA 4.5), lock and star >= 4.2:1. The first palette, picked by eye, left the secondary text at 4.1-4.4:1: the light backgrounds are paler and the hue rides on the border. CouleursDeNoteTest recomputes it on the real schemes at every build (24 cases; sabotage: the first orange fails). Schema 9 -> 10 (D-028, "Oui, version 10"): ALTER TABLE notes ADD COLUMN color_id INTEGER. The way back to Flutter 2.0.9 the version 9 kept open was already closed — Android refuses 4071-4073 over 5001+, and uninstalling erases the notes. The colour is written by its own UPDATE, like pin and favourite: sealing, unsealing and moving a note never rewrite the row, so they keep it. Checks: - audits/verifier-schema-room-vs-flutter.py now checks schema 9 against the Flutter base AND the Flutter base with the migration replayed against schema 10 (negative control: migration removed, exit 1). - MigrationVersDixTest, on a real SQLCipher base: a Flutter base (no room_master_table) and a base adopted by 3.0.0 (Room identity of schema 9) both open at version 10, every note and sealed blob unchanged, and the column takes a colour through the FTS update trigger. 2/2 on the emulator. - NoteColorTest pins the stored ids; CouleurVisibleTest the vault rule (4 cases). - Unit tests 460/0; EditeurTest 33/33, AccueilTest 16/16 with the colour wired. Also: detekt refused `addMigrations(*array)` (SpreadOperator) — the migration is registered by name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Patrice read "Version 1.2.0" next to the app's v3.0.0 and took it for the app's version. It was the document's own number. The five languages now say "Last updated: 10 October 2026" (and its translations), as Agenda Tech and App Manager Tech do. Read against the code, the French — the version that prevails — and the English said too little or said it wrong, the same way in both: - The vault paragraph said the content of a locked note is encrypted, without saying what is not: its tags stay outside the vault's own encryption (in the SQLCipher database), which NoteCard has always documented. Said now, with the colour of 3.1.0, which is in the same case, and when the lists show either. - The export entry is "Settings → Export all my notes", not "Settings → Export" (French and English only; German, Italian and Spanish quoted the real label). - New with 3.1.0: a note moved to the trash can be restored for 30 days; one deleted permanently — from the trash, or with a long press in the list, after a confirmation — cannot. Added to the user's rights (privacy), to "Panic mode and data loss", and to the losses the publisher is not liable for (terms). Same changes in German, Italian and Spanish. PagesLegalesTest, which reads the files, passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
version.properties: 3.1.0, base 501, so 5011/5012/5013 per ABI and 5010 universal — above 3.0.0's 5000-5003. Not tagged: the release waits for Patrice. Changelogs 5011-5013 in five languages (276-353 characters, under F-Droid's 500): reading mode, long press, colours, legal texts, toolchain. Store descriptions: they still announced "an Edit / Preview switch", gone with this version — replaced by the reading mode, the long press and the colours, in the five languages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GPT-5.6 sol (0.47 $) and Claude Opus 5.5 (0.50 $) on the colour commit; each finding checked in the code. Kept: - A sealed note could flash its colour after its vault locked (both). HomeViewModel.state is shared with WhileSubscribed(5 s), which keeps its last value: back on the home screen after an automatic lock, the first frame still had the vault open. The open vaults now come straight from the sessions' hot flow (HomeViewModel.coffresOuverts), never through the cached state. - The colour sheet stayed open when the vault locked under it (GPT): it showed the sealed note's colour and let it change. It now closes as soon as the note stops being readable (vault locked, load error). - The tick of the chosen swatch was drawn in the border colour: 2.4:1 on yellow, 2.8:1 on orange, under the 3:1 of a non-text mark (both, computed). Drawn in onSurface now, which CouleursDeNoteTest holds at 4.5:1 and more on every colour. - The schema verifier checked the highest exported schema it found, not the code's (GPT): a missing export would have let it check schema 9 only and pass. It now reads NotesDatabase.VERSION and fails if that schema is not exported. - NoteEntity.colorId had a default (Claude): a row rebuilt without its colour would write NULL. No such path exists today — outside the insert, every write is a targeted UPDATE that does not touch color_id, so sealing, converting a folder into a vault and moving keep the colour — but without the default the compiler makes every constructor decide, and it found a third one in the tests that a text search had missed (NoteLinkTest). Checks: unit tests 460/0, lint, detekt, ktlint, schema verifier; instrumented on the emulator: home 16/16, links 5/5, repository 62/62, legacy base opening 15/15, vault gestures 5/5. A 3.0.0 debug build updated in place to 3.1.0 on a Galaxy S9 (Android 10) opens its migrated base. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the 3-axes pre-release audit of v3.0.0..HEAD (no Critical, no High; this one Medium, probable,
confirmed in the code). A "Delete permanently" confirmed on a note of vault A, refused because A locked
in the meantime, waits for A's secret. If A's unlock sheet was then replaced by vault B's without being
dismissed — `dossierAOuvrir?.let { UnlockVaultSheet(...) }` recomposes on the new folder and never
calls onDismiss — the confirmed deletion survived, and ran at the next unlock of A, made for another
reason, without a new confirmation.
What waits for a vault now waits for THAT sheet: when the sheet moves to another vault, or closes,
GestesDeNoteEnCours.garderSeulementPour drops what waited for any other one. Re-doing a gesture is
cheap; an irreversible one running out of its context is not. reprendreApres still consumes only what
waits for the vault just unlocked.
Tests: GestesDeNoteEnCoursTest 7/7 (+2: given up when the sheet moves to B, kept for the awaited
vault); sabotage — the rule removed, the new case fails. Unit tests 462/0, detekt, ktlint.
The audit also verified, without defect: the vault check, no colour leak from a closed vault in any
list, the 9 -> 10 migration, the reading mode, TalkBack, the 15 new strings in five languages, the
legal texts against the code, the changelogs, the wrapper's SHA-256. A data-room deep dive found
nothing on the migration, the FTS trigger, the paths that write a note, panic mode or an interrupted
migration. And on the emulator, the 3.1.0 R8 APK installed over the 3.0.0 R8 APK holding a note, a
passphrase vault and a sealed note: everything there, the note opens to be read, a colour is set and
announced, and a long press on the closed vault's note asks for its secret first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Notes Tech 3.1.0, asked by Patrice on 2026-10-10. Stacked on #26 (AGP 9): GitHub retargets it to
mainonce #26 is merged. Not tagged: the release waits for Patrice's test on his phone.What it does
Security
Schema 10 (D-028)
ALTER TABLE notes ADD COLUMN color_id INTEGER. The schema verifier now checks schema 9 against Flutter and the Flutter base migrated against schema 10 (negative control: exit 1).MigrationVersDixTestreplays the real migration on a Flutter base and on a base adopted by 3.0.0: both open, no note or sealed blob changes.Tests
Found by the tests, fixed before commit: the Edit button had no name for TalkBack (the
text =/icon =overload did not merge its label); Edit left the cursor at the start of the note.Reviews
GPT-5.6 sol and Claude Opus 5.5 on each lot (each finding checked in the code; kept and rejected ones listed in the commit messages). Rotation findings rejected:
MainActivitydeclaresconfigChanges.🤖 Generated with Claude Code