Skip to content

3.2.0: encrypted backup, import, find in note, incoming share, long notes - #41

Merged
gitubpatrice merged 17 commits into
mainfrom
feat/3.2.0
Oct 30, 2026
Merged

gitubpatrice merged 17 commits into
mainfrom
feat/3.2.0

Conversation

@gitubpatrice

Copy link
Copy Markdown
Owner

Notes Tech 3.2.0. Decisions and measurements: docs/13-VERSION-3.2.md (D-029 to D-034).

What it adds

  • Encrypted backup and restore (Settings → Encrypted backup): a .ntbackup file — Argon2id + AES-256-GCM in streaming, a 48-byte authenticated header, a generated code or a passphrase. Written before the system picker opens (every vault locks in the background), read back by SHA-256; restored in two passes, the writing one in a single transaction bound to the trial's bytes. Passphrase vaults travel sealed; an open PIN vault travels inside the backup's encryption and gets a new PIN. Reviewed twice by GPT-5.6 (design, code).
  • Import of Markdown or text files and ZIP archives (a Notes Tech export, an Obsidian vault): folders kept, frontmatter read, never into a vault; bounded (5 000 files, 100 MiB read, 30 million characters kept, 500 000 per note).
  • Find in note: highlighted matches, "3 / 12", while reading and writing; opened from a search result with its search.
  • Incoming share: text or files from other apps, bounded, after the app lock, never into a vault.
  • Long notes: the body is written in Android's own EditText — Compose draws every line of a text at each frame (100 000 characters: ~1 s per keystroke on a Galaxy S9; now ~20 ms at any length). The vault protections are re-applied, what an EditText opens is closed (toolbar allow-list, keyboard window, no autofill / content capture / spell checker / saved state), the field keeps its drag gesture and flings. Reading an unparsed long note goes in pieces.

Checks

  • Pre-release audit on three axes (no Critical / High; the Medium and four Lows fixed), i18n check (five languages; register, terms and Inbox names fixed), GPT-5.6 reviews of the backup and of the body field.
  • Every new defence seen failing when sabotaged.
  • Local: ktlint, detekt, lint (55 warnings, unchanged), 541 JVM tests; instrumented on an API 34 emulator — the whole suite (544: the 12 not passing are the emulator's software Keystore, no biometric, no Whisper model, as in 3.1.1), then the editor (99), settings (15), secure (23), share (3), about (6) packages on the final code.
  • By hand on a Galaxy S9 (Android 10) and S24 (Android 16): long notes typed and scrolled, keyboard closing, the selection toolbar, backup and restore.

Version 3.2.0 (503): changelogs 5031-5033 in five languages, store listing, privacy policy (imported and shared notes), README and SECURITY.

🤖 Generated with Claude Code

gitubpatrice and others added 17 commits October 29, 2026 16:16
First brick of 3.2.0 (docs/13-VERSION-3.2.md, reviewed by GPT 5.6 sol: 16
findings, all checked, the kept ones folded into the backup design).

LecteurMarkdown reads back the export's own frontmatter exactly - quoted
strings with its escapes, local dates, inline tags - and the common forms
of other tools (plain values, block tag lists, dates with an offset). A
frontmatter that never closes is not one; a byte-order mark and CRLF are
handled; a title is cut at the repository's 200 without splitting a
surrogate pair; a tag holding a comma is split, since tags are stored
comma-joined. Tested through the real NoteMarkdown.render; two sabotages
(an escape, the CRLF conversion) each caught by the test meant for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/13-VERSION-3.2.md section 4. A dedicated exported activity with no
screen reads what another app sends (SEND / SEND_MULTIPLE, text types),
bounds it and hands it over in memory, then opens the app. MainActivity is
not touched: it keeps dropping every extra of its intent (audit E1).

- LectureDUnPartage (pure, JVM-tested): files win over the text sent with
  them and are read like imported files (LecteurMarkdown); strict UTF-8,
  a NUL means not text; 1 MiB of text, 5 MiB per file, 50 files; what is
  refused is counted, never cut.
- The activity: content:// only, never one of this app's providers; each
  file read up to the bound plus one byte; a malformed extra is counted.
- PartagesRecus, composed beside the NavHost INSIDE the app lock's content:
  a share becomes notes only once the app is unlocked. One note opens; else
  a summary. NotesRepository.importer: one transaction, new ids, the file's
  dates and flags, links indexed; refuses a vault (or an unknown folder).
- Strings in the five languages, with many forms for fr/es/it.

Tested: 6 + 8 JVM tests, a sabotage (the NUL rule) caught; full gate green;
on the emulator a text share became the note, opened, no crash. Found on the
way: a slash-star in a KDoc opens a NESTED comment in Kotlin and had turned
the rest of the activity into a comment (detekt saw every import unused).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A security deep-dive of "Share -> Notes Tech" (0 critical, 0 high, E1 holds)
found two medium and three low issues; each checked against the code, all fixed:

- M1: a frontmatter block list was appended to the string read so far -
  quadratic, minutes of CPU for a 5 MiB file of "- x" lines. Block lists now
  grow a list; the closing fence must come within 16 KiB; at most 100 tags of
  100 characters. Reading a share is bounded to 20 s, the app opens without it.
- M2: 50 files of 5 MiB were held at once (250 MiB), and an OutOfMemoryError,
  which no catch here sees, took the app down. Files are now read one by one
  under a 16 MiB budget, 20 files at most; past the budget nothing is opened.
- L1: content://0@<this app>.exports/... passed the startsWith on the raw
  authority while the resolver strips the 0@. Any user-info or @ is refused,
  and the host is compared, case-insensitively (estDUneAutreAppli).
- L2: file dates are believed only between 1970 and now.
- L3: a waiting share expires after 2 minutes (monotonic clock), is not
  taken while the panic mode runs, and nothing is written before the user
  confirms - any app can start the share activity by name.

Tests: LecteurMarkdown 10, LectureDUnPartage 6 (budget: nothing read once
spent), PartagesEnAttente 4 (expiry, taken once), FournisseurDUnPartageTest 3
on real Uri parsing; the L1 fix sabotaged -> 2 of 3 fail. Full gate green.
On the emulator: Cancel writes nothing, Add creates and opens the note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n option

Patrice, 2026-10-29: "do what is best". The backup file leaves the phone and
can be attacked offline without a limit on attempts: only a secret drawn at
random is sure to hold. 24 characters, six groups of four, a 32-sign alphabet
without ambiguous signs (120 bits), shown once and retyped before the file is
written. A chosen phrase stays possible: 12 characters, NFC, obvious ones
refused. PIN vaults: backed up only while open, a new PIN chosen at restore.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…earch result

hcarrega's idea (issue #10), D-033 of docs/13-VERSION-3.2.md. The editor's
menu opens a bar in place of the top bar: the occurrences highlighted in
the note, "3 / 12", previous and next. A note opened from a search result
(the search screen, or the home list filtered by its field: the same
full-text search) opens with the bar filled and its first occurrence on
screen.

- What is searched is what is shown: while writing, the title and the
  Markdown as typed; while reading, the title and the Markdown as drawn
  (a link's address is not on screen there, so not found there).
- Insensitive to case and accents, word by word, inside words too, and
  the typographic apostrophes fold to "'" (FTS5 treats the apostrophe as a
  separator: a note found by "l'été" shows its "l’été"). Positions in the
  text as written; nothing is copied, a note having no size limit.
- At most 1000 occurrences ("1000+"), computed off the main thread and
  cancelled by a newer text or search; an answer is never drawn on
  another text than the one it was computed on.
- The bar moves the cursor or the list only when asked (new search,
  previous, next, arrival): typing in the body with the bar open does not
  pull the cursor back at every keystroke. While reading, the list goes to
  the item, then its text brings the very line into view, once.
- Back closes the bar before leaving the note; the bar closes if the
  vault locks underneath. The bar's field keeps its name with text in it.
- Nothing is indexed or written; strings in the five languages.

Tests: RechercheDansLeTexteTest (8) and RechercheDansLaNoteTest (3) on the
JVM, the latter through the real Markdown reader over every kind of
block; RechercheDansLaNoteEcranTest (5, the far occurrence measured on the
pixels while writing) and RechercheDeBoutEnBoutTest (2, the real app).
Each seen failing under a sabotage: no fold of the apostrophe, no accent
kept, no merge; a move on every answer, no scroll to the item, no Back
handler; no search in the route. Gate: 498 JVM tests, detekt, ktlint,
lint clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
D-032 of docs/13-VERSION-3.2.md. Settings, "Export and import" (the
section had borne its only row's label until now), "Import notes": the
system picker through the app's seam, several documents at once, then a
summary: notes imported, folders created, notes sent to the Inbox,
files not imported, and whether a bound stopped it.

- A file alone goes to the Inbox. In a ZIP, the first level of the path
  names an ordinary folder, found by name or created; `inbox/`, the
  export's own name, is the Inbox, whatever language the export was
  written in (its frontmatter carries the translated name, so the path
  decides). An export of Notes Tech comes back in its folders, its
  README recognised by its heading, now shared with the export.
- Never into a vault: a name that is only a vault's sends the note to
  the Inbox, and the summary says so; an ordinary folder of the same
  name wins. Folders and notes in ONE transaction, all or nothing.
- Bounded on what is really read: 5,000 files, 5 MiB per file, 100 MiB
  in all. A ZIP read as a stream inflates the rest of an entry to reach
  the next one, so a skipped entry counts too: one entry of zeros, a few
  hundred kilobytes compressed, would otherwise keep the import
  inflating for hours. Past a bound, the import keeps what it read.
- Strict UTF-8 (the share's decoder, now shared), not a NUL; hidden
  entries (`__MACOSX/`, `.DS_Store`, `.obsidian/`) are neither read nor
  counted. Nothing is copied to the app's storage.

Tests: LectureDUnImportTest (7: the real export read back, an archive
bomb under a second, the file bound, a download cut short), three on
real SQLCipher in NotesRepositoryTest; each seen failing under a
sabotage (skipped bytes uncounted, no hidden filter, no README rule, no
vault rule). Tried on the emulator through the real picker. Gate: 505
JVM tests, detekt, ktlint, lint clean; NotesRepositoryTest 65/65,
ReglagesTest 15/15 on the emulator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pure layer of the encrypted backup (D-029 of docs/13-VERSION-3.2.md),
not yet used by the app.

FichierDeSauvegarde: the 48-byte header (magic, version 1, Argon2id with
exactly t = 3, m = 64 MiB, p = 1 — the passphrase vaults' parameters,
derived by VaultCrypto), authenticated as the AAD of AES-256-GCM, streamed
both ways by Bouncy Castle. The header is refused before any derivation
when it is not what version 1 writes (R-10): a forged file cannot make
the phone allocate more, or compute longer, than a real backup. The
content is not authenticated until its stream ends, where a wrong secret
and a damaged file fail alike. 64 MiB at most, counted on what is really
read (R-9). The header is read by a loop, not readNBytes (Android 13+).

SecretDeSauvegarde: the code drawn by default, 24 signs of Crockford's
base 32 (120 bits; removing 0 O 1 I L leaves only 31 signs, so the
look-alikes are folded when typed instead), read back whatever its case,
spaces and dashes; or a chosen phrase, in NFC, at least 12 characters,
not a repeated pattern, a run or one of the commonest. One rule for
creating and restoring: what has the shape of a code is a code.

Tests: FichierDeSauvegardeTest (5) and SecretDeSauvegardeTest (4), the
R-10 check and the NFC rule seen failing under a sabotage. The NFC test
first used a literal its editor had precomposed, and proved nothing: it
is written with escapes now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The data layer of the encrypted backup (D-029 to D-031 of
docs/13-VERSION-3.2.md); no screen yet.

- ContenuDeSauvegarde: the content as streamed JSON (android.util
  JsonWriter / JsonReader, never a tree): folders, then notes. A
  passphrase vault travels as its columns, a PIN vault saved open as its
  folder key, a sealed note as its blob. Strict on reading: fields typed,
  duplicates refused, numbers never through a double. 🔴 It reads its
  stream to the very end and refuses any byte after the JSON: GCM checks
  its tag only there, and when the content ends on a 16-byte boundary its
  last block can come out before — a reader stopping at the last brace
  would leave the tag unchecked (LectureDuContenuTest, red under that
  sabotage; the round trip passes either way, which is why it exists).
- SauvegardeChiffree: every folder and note, the trash included, in one
  read transaction, notes a page at a time (SauvegardeDao). A closed PIN
  vault is named first (apercu) and left out with its notes; an open one
  goes with a copy of its key, wiped after. Success only after the file is
  read back whole; a failed file is deleted where the provider allows.
- PlanDeRestauration (pure, JVM): ONE place deciding for both passes.
  Folders reused by id or created parents first, a missing parent, a
  cycle or depth past 32 leaving them at the top (R-6). Each note checked
  against the folder it really goes to (R-4): in clear into an ordinary
  folder, sealed into the backup's own vault — same key by its verifier.
  Same id: identical left, different in clear added beside under a new
  id, different sealed ignored (R-3). A forged file repeating an id is
  read by its first description everywhere; a phone vault without a
  verifier takes no note at all (read as ordinary, it would have taken
  notes in clear past the repository's sealing).
- RestaurationDeSauvegarde: a trial pass that writes nothing, then the
  writing pass in one transaction rolled back when the tag fails at the
  end. A PIN vault comes back with a PIN chosen then; its Keystore key is
  deleted if the transaction does not commit (R-11).
- FolderVaultService: the PIN sealing shared by createPinVault and the
  restore (materielDUnCoffreACode), no second copy of it.
- KeystoreEnMemoire: one shared test Keystore instead of two copies.

Tests: PlanDeRestaurationTest (6, JVM); SauvegardeChiffreeTest (4, real
SQLCipher and Argon2id: what was lost comes back, the PIN vault opens
with its new PIN; a wrong secret or a changed last byte restores nothing
and leaves no Keystore key; restoring twice adds nothing; a closed PIN
vault named then left out); LectureDuContenuTest (2). Sabotages seen
red: the key cleanup removed, the drain to the end removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The screens of the encrypted backup (D-029 to D-031 of
docs/13-VERSION-3.2.md), in a section of their own after "Export and
import", and what testing them on a S9 and an emulator turned up.

- 🔴 Written BEFORE the system picker opens. The picker sends the app to
  the background, where every vault locks (NotesTechApplication, the
  main protection, left as it is). Written on the way back, the backup
  left out the open PIN vault the preview had just named: 294 bytes,
  "0 notes" (seen on a S9). SauvegardeChiffree.preparer now encrypts and
  reads the backup back into cache/sauvegarde/ while the vaults are as
  the preview saw them; deposer copies it into the chosen document and
  reads that back against it (same SHA-256). While the picker is open
  only that encrypted file waits: neither the secret nor a vault key is
  kept. It is deleted on every way out, and otherwise by the startup
  purge and the panic (ClairDuCache, the exports' definition). A process
  killed while the picker is open leaves no empty document behind.
- Restore: the file is examined before any secret (size given by the
  provider, header, version): not a backup, too large or too new is
  refused at once. A wrong code is asked again in place, the file kept.
- Every dialog bears the title of the row that opened it, waiting and
  failure included; the summary is in the future before, in the past
  after; closed and open PIN vaults are counted plurals; what a backup
  left out is said again at the end. The code shows as two lines of
  three groups, typed back case-, dash- and O/0-insensitive.
- AppResultLauncher.launch returns false instead of letting
  ActivityNotFoundException crash the app when no documents app exists;
  the four pickers say so (both backup flows, the import, the voice
  model).
- French texts say "passphrase", as the rest of the app does.
- Privacy policy and terms (5 languages): the backup file, its cache
  copy, that panic mode does not reach it, and a forgotten backup code.

Tests: SauvegardeChiffreeTest 4 -> 7 (a PIN vault locked between
preparer and deposer keeps its notes: red when locked before, as the
old code did; everything lost, a passphrase vault included, comes back;
the examination); ClairDuCacheTest +1. On a S9: PIN vault closed (named,
then said left out), open (1 note saved), deleted, restored with a new
PIN and reopened with it; wrong code asked again. On an emulator: code
and passphrase paths, rotation, Cancel, Back in both pickers, process
killed during the picker, restore on fresh data, a note modified since
(copy beside), truncated / newer / 65 MiB / ZIP files, no documents app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
15 findings, each checked in the code before any change: 12 fixed, one
refuted (sessionKey already returns a copy taken under its lock), one
set aside (the whole app reads a vault as salt != null, the backup too),
one answered in the KDoc (a process death forgets the restore flow).

Creating
- A PIN vault the preview said open and that locked before the writing
  (the user left the app to note the code) stops the backup: nothing is
  written, the screen names the vault. Seen on a S9.
- Notes are read in lots of at most 200 notes and 1 Mi characters,
  through ids and weights first (SauvegardeDao.poidsDesNotes): a page of
  200 imported notes of up to 5 MiB each could exceed the heap. The
  preview counts notes from that light query, no longer from their text.
- The waiting file is deleted on every exit: the derivation failing, or
  the deposit cancelled before its IO block ever ran.

Restoring
- The writing pass is held to the trial's bytes (SHA-256 of the raw
  file, compared inside the transaction): another valid backup in their
  place commits nothing and says so.
- A failed pass deletes only the Keystore keys whose vault is NOT in the
  database, one by one: a cancellation can arrive after the commit, and
  deleting those keys would have locked the restored vaults for good. A
  key is listed before it is created, so a sealing that throws after the
  creation leaves nothing behind.
- The root of the JSON takes each field once: a second "folders" would
  have the trial and the writing decide on two trees.
- JsonReader's IllegalStateException and NumberFormatException become an
  unreadable content: noise from a wrong secret begins with "[" or a
  digit about once in twenty, and the screen said "failed" instead of
  asking for the code again. Never around the database callbacks.
- A PIN vault's key must be 32 bytes (passphrase vaults keep the lengths
  the phone has: a vault migrated from 2.x does not have 3.x's).
- The examination before the secret needs the tag after the header.
- Secured window on the restore dialogs, as on the creation's.

Tests: SauvegardeChiffreeTest 7 -> 9 (vault locked before the writing;
file changed between the passes), LectureDuContenuTest 2 -> 4 (another
shape of JSON, a root field twice). The four new refusals sabotaged at
once: exactly those four tests red, the nine others green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A long note was unusable to write in. Compose draws every line of a
text each time it records it (ui-text 1.12.1: TextAndroidCanvas
reports a clip of infinite height), and a text field records at every
keystroke and every blink of its cursor. Measured on a Galaxy S9
(Android 10), release build, time per frame while typing (median):
2 000 characters 30 ms, 100 000 950 ms, 200 000 3.35 s, the keyboard
up after 9 s. Compose's TextFieldState field did no better (1.2 s at
100 000). The body is now an EditText (ChampDuCorps): 23 ms per frame
at 2 000 as at 200 000 characters.

The note's state keeps its TextFieldValue; the field reports text and
selection to it and applies its changes (a link, a dictation, the
search's cursor) as the smallest replacement. Values the field sent
are recognised by identity when they come back late, so a late
recomposition cannot undo a keystroke.

What an EditText opens and Compose's field did not is closed:
- the selection toolbar keeps cut, copy, paste and select all: no
  Share, no Autofill, no other app's PROCESS_TEXT action (on Android
  10 and below every one is listed, "Translate" among them);
- Copy and Cut go through SaisieDeCoffre's clipboard (protected for a
  vault note), and a Cut removes its text only once written and only
  if nothing changed meanwhile; a refused copy is said
  (note_editor_copy_failed outside a vault);
- a vault note asks the keyboard not to learn, and its selection
  cannot be dragged to another app; a drop and a paste are plain text;
- no classifier, no autofill, no content capture (Android 11+), no
  spell checker, nothing in the saved state, no fullscreen keyboard.

The keyboard reads a window of 5 000 characters around the cursor,
never the note: the Samsung Keyboard asks for the extracted text after
every keystroke, and at 450 000 characters the 900 KB answer failed
the Binder and stalled each keystroke 2 s (S9). A selection longer
than the window is handed over by none of its reads.

The keyboard leaves with the field (the tick, the app bar's arrow, a
note opened from a link): an EditText taken out of the window left it
open over the list.

Reviewed by GPT-5.6: 8 findings, 5 fixed (a long selection in the
extracted text and in takeSnapshot, a slow Cut and an edit during it,
an echo seen twice), 1 refuted (search keys are compared by identity),
1 kept as it is safe (a Cut cancelled with the screen stays a Copy),
1 answered in the KDoc (content capture's view flag is Android 11+;
a vault note's window is FLAG_SECURE before).

Tests: ReglesDuChampDuCorpsTest 9 (JVM), ChampDuCorpsTest 13, the
editor's screen tests moved to the native field, and the keyboard
closing end to end. Every new defence sabotaged: its test red.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A note past 500 000 characters is not parsed and was shown as written
in ONE Compose text, which Compose lays out and draws whole: a 5 MiB
note, the import's bound until now, froze the app on opening with its
heap full (emulator, debug and release).

It is now cut into pieces of at most 2 000 characters, at a line break
in the second half of a piece where there is one, never inside a
surrogate pair — one lazy item each, the pieces touching and the block
rounded at its ends only. The in-note search finds what the pieces
draw.

Tests: AplatissementDeLApercuTest 3 -> 7, sabotaged: 3 red.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The import took files of 5 MiB and a share texts of a million
characters: notes the app could neither show nor edit. A note brought
in by the import, a shared file or a shared text is now at most
500 000 characters (LongueurDUneNote), the Markdown preview's own
bound — every note brought in is shown drawn — and measured fluid to
write in on the S9 with the native body field. A file is read up to
three bytes per character, then counted on its text. A longer one is
counted as ignored, as before. A note typed or pasted has no bound.

docs/13-VERSION-3.2.md: D-034, the long notes, with the measurements.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tried on a Galaxy S24 and a S9, the native body field scrolled badly
in a long note, keyboard up or down, while each frame took 12 ms.
Two defects:

- The editor's own scrolling column took every vertical drag on the
  field: logged on the S24, the field received ACTION_CANCEL 40 to
  90 px into each gesture, so the text stopped mid-gesture. The field
  now keeps the gesture while its text can scroll
  (requestDisallowInterceptTouchEvent); a short text leaves it to the
  column, as before.
- An EditText scrolls by the finger's distance only, with no fling.
  It now flings (VelocityTracker + Scroller), never after a long
  press, and stops at a touch or a change of the text.

Measured, 8 flings on a 450 000-character note: S24 24 -> 862 frames,
5 ms median, 10 ms at worst, keyboard up or down; S9 ~540 frames,
21 ms at worst. The ~1 s stalls seen on the S9 with its keyboard up
came from the cut gestures and are gone.

Tests: EditeurTest scrolls the body through Compose's pointer input,
as a finger (red without the fix: 135 px instead of more than 904);
ChampDuCorpsTest checks the fling itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pre-release audit of v3.1.1..HEAD on three axes: no Critical, no High,
one Medium (probable), six Low; each checked in the code first.

- Import: the notes read wait in memory for the one transaction that
  writes them all, and 100 MiB of text — 200 MiB as UTF-16 — could
  exceed a modest phone's heap with an OutOfMemoryError nothing
  catches (the share had the same defect, fixed with a budget). The
  text kept is now bounded at 30 million characters; past it the
  import stops and says so, as at its other bounds.
- Import: a folder's name taken from an archive loses its control and
  bidirectional formatting characters (a right-to-left override could
  disguise it in the folders' drawer) and is cut at 100 characters.
- Find in note: a vault note's query is no longer kept in the
  activity's saved state — it is a word of its plaintext.
- Body field: a value the state already gave back, seen again after
  the next keystroke, could undo that keystroke (the acknowledgement
  emptied the echo queue). The last known value now stays known.
- Backup: TalkBack spells the backup code instead of reading it as
  words.
- Kept as they are: a backup past 64 MiB is refused (documented), the
  backup dialogs' fields empty on rotation (no secret in saved state),
  a 12-character passphrase (D-029; the generated code is the default).

i18n check (five languages; parity, placeholders, plurals, escaping
all clean): the new 3.2.0 strings used the familiar form in Italian
and Spanish where the app uses the formal one throughout (Lei, usted);
German read "Ihr"/"Sie" (your/you) where it meant their/they; German
and Italian named the Inbox "Posteingang" and "Posta in arrivo", the
app's folder being "Eingang" and "In arrivo"; Spanish capitalised the
Inbox mid-sentence and said "borradas" for "eliminadas".

Tests: import 2 new (bound, folder name), search saver 1, spelled code
1, the echo seen again 1 (red with the old queue: "xa" for "xab").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- version.properties: 3.2.0, base 503 (5031/5032/5033 per ABI, 5030
  universal).
- Changelogs 5031-5033 in the five store languages, under F-Droid's
  500 characters: encrypted backup, Markdown/ZIP import, find in note,
  incoming share, long notes fluid.
- Store descriptions: the new features in the Editing block.
- Privacy policy (five languages, dated 30 October 2026): what happens
  to imported and shared notes — read once on the phone, ordinary
  notes never in a vault, nothing kept beside them, 500 000 characters
  at most per note.
- README (EN, FR): what's new in 3.2.0, the features list, the APK
  name. SECURITY (EN, FR): the 3.2.0 section — backup format and
  restore, import and share bounds, the body field's protections.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gitubpatrice
gitubpatrice merged commit 8488e7d into main Oct 30, 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.

1 participant