Two questions, and the code was only the second one.
WHERE THE FILE LANDS. The private directory is useless - no file manager
opens it and it leaves with the uninstall - and direct external storage has
been closed since Android 10. Of the three remaining doors, MediaStore ->
Downloads is the only one needing neither a permission nor an extra screen:
since API 29 an app may write what it inserted itself. The destination picker
would need an activity result, which FMX makes painful; sharing would need a
FileProvider in the manifest. Below API 29 it would take
WRITE_EXTERNAL_STORAGE - one of the ten permissions just removed - so it
refuses and says so.
WHERE THE CONTENT IS. TVault drops it on purpose: a single attachment took
the cache from 13 to 174 Ko. It is re-read from the raw snapshot, which has
been in memory ever since saving stopped fetching before publishing. The same
data now serves two needs that had nothing to do with each other.
Decoding and writing happen off the main thread, under the waiting veil: a
few megabytes would freeze the screen, and the veil would not even paint.
What leaves the application leaves its protection: the file lands in the
clear in Downloads. The confirmation names the place rather than saying
"saved".
Release configuration compiles too - checked, since that is what section 5
caught out.
The folder had no row of its own - it shared "Folder / tags" with the tags,
so neither could be touched. It is now CHOSEN from the known folders rather
than typed: a typo would file the entry under a folder nothing displays,
here or on the desktop, and it would look lost. Tags stay read-only because
they are an array in the snapshot, and writing one as a string would break
its shape for the desktop.
The 2FA secret gets its own masked row. The code has had a panel for a long
time, but it is computed and renewed every thirty seconds; the secret is what
one sets, changes and removes, and nothing allowed that from the phone.
User, Pass, Copy, Show and Edit become drawings. Four words on a list row left
little room for the entry's name and had to be re-read every time. Every icon
carries an aria-label: replacing a word with a drawing must not take the
information away from someone listening to a screen reader.
PatchEntryField already took a field name; what was missing was carrying it
from the row. It travels in the detail row itself rather than being guessed
from the label - a caption is prose, and the day it changes the page would
send a field name that does not exist, or quietly stop offering the edit.
Delphi does not take it on trust either: four names accepted, the rest
refused. The snapshot also carries tombstones, attachments and timestamps
that PatchEntryField would overwrite without understanding them.
An empty row now shows IF it can be filled - a missing username you cannot
add is a dead end. Emptying is allowed, except on a password, where it would
almost always be a slip; the refusal says so rather than doing nothing.
Show only appears on a masked field: on a site in clear it has nothing to
reveal, and would in fact hide it. A note keeps only Name and Content - a
site and a username mean nothing there.
Pull, patch, re-encrypt, publish - in that order, and against the SERVER's
snapshot rather than the local cache. That is what replaces the merge the plan
asked for: anything untouched is already everyone's newest version, so there is
nothing to reconcile.
The only conflict left is another device publishing between our read and our
write. Gitea refuses it on the sha with a 409, and the whole cycle repeats,
decryption included, since the content changed. Three attempts: past that the
other device is publishing in a loop and insisting would not help.
The editable field is marked in Delphi rather than recognised by its caption in
the page. A caption is prose; the day it changes, matching on it would silently
stop offering the edit.
The editor is inline rather than window.prompt: a JavaScript prompt needs a
WebChromeClient that FMX does not provide, and prompt() then returns null
without a word - the button would appear to do nothing. The value shows in
clear while typing, deliberately: you are entering a password you cannot read
back anywhere else, and masking it would mean confirming a keystroke blind.
This is the one command that carries a secret over the bridge, agreed
beforehand. It stays in the process, the iframe carrying it is destroyed, and
the handler logs the action only - never the query.
Only the password is editable. Creating or deleting an entry would need
tombstones and uuid generation, and changing a password covers the need
without either.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The FMX controls lost this one on merit. Every screen re-taught the same lessons: a template missing from the loaded style, an alignment order left to chance, a HitTest swallowing a tap, a native view painting over everything. The engine draws the same interface in forty lines of CSS and shapes Arabic on its own.
The bridge needs no local server, so no port is open on the device: evaluateJavascript carries the data down, an intercepted cmd:// navigation carries the actions up. Through a throwaway iframe - FMX always tells the engine it did not handle the navigation, which on an unknown scheme replaces the page with an error screen.
Secrets do not travel over that bridge. The list gets names, sites and usernames, which are already on screen; copying from a row sends an index and Delphi reads the vault itself. Only the open entry has its values in the JS heap, and PM.wipe clears them on lock.
Folders become a row of chips with counts instead of a full-screen panel: one tap, and the count says what to expect.
Also gone: about eight hundred lines of the screen this replaces.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>