Commit Graph

59 Commits

Author SHA1 Message Date
r-zakarya 060611f101 fix(android): drop the labels, put a clear cross in each field
The labels went in this afternoon to tell the token and the sync password
apart, both being masked and stacked. They are removed on request; the
placeholders carry that job again, which they do while the fields are empty -
and that is when it matters, since a filled field is one you just typed.

Each field gets a cross instead. Clearing a forty-character token with the
backspace key takes forty presses and leaves a character behind easily, which
shows up as a 401 whose cause is invisible.

The cross is a SIBLING of the input, laid over it - never a child. A child of
an input does not receive the tap; that cost a build earlier today when the
search field was given its own cross.

It also pushes the configuration immediately: the field keeps the focus, so the
blur that normally carries the value to Delphi never happens.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 19:18:39 +01:00
r-zakarya 683655415f fix(android): locking the vault no longer discards the typed settings
Reported as "the token field saves nothing", and it was exact: LockVault
called PushConfig, which pushes the STORED server settings back into the page.
Every lock therefore overwrote a freshly typed token with the old one -
including the three-minute auto-lock, so it happened with no gesture from the
user at all, and the field looked like it forgot on its own.

The user's own sequence isolated it: deleting one character gave a 401, which
proves the typed value does reach the wire; then open, lock, sync gave a 200
again, the lock having restored the old token in between.

PushConfig now runs at startup only. It was pointless in the other four places
anyway: PM.wipe clears the list and the detail, never the configuration fields.

Along the way, and part of the same confusion: the three settings fields had no
labels, only placeholders that vanish once filled - and since the token was
masked this morning it became indistinguishable from the sync password sitting
right below it. They are labelled now, and the log reports both fingerprints so
a value landing in the neighbouring field shows up immediately.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 18:52:45 +01:00
r-zakarya 90e8fabf58 feat(android): the write transport, and an error that says why
TSyncClient.Put publishes a container to the Gitea contents API: token header,
JSON envelope with the file base64 inside, sha omitted rather than empty when
creating, branch omitted so the repo default is used. 409 becomes its own
status, ssConflict, because it means re-read and re-apply rather than retry.

The first PUT is where a mistake costs data, so the probe writes to
write-test.json rather than the vault. Same repository, same token, same
envelope, same concurrency check - and the vault is never at stake. Reachable
from a debug-only button; the page asks Delphi whether to show it.

Two mistakes of mine on the way, both about replacing information with a guess:

- the probe did not push the config first, unlike Sync, so a token that had
  just been pasted had not reached Delphi and the 403 blamed the wrong thing.
- 401 and 403 were rewritten as "token lacks write access", discarding Gitea's
  body. That is an interpretation, not an observation: the same code covers a
  read-only repository, a protected branch, a token from another account. The
  body says which - here, plainly, "token scope=read:repository".

Also recorded: the sha comparison now short-circuits (304 where it used to
decrypt), so the difference was case or whitespace, absorbed by SameText on
trimmed values. The offending character itself was never seen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 17:47:55 +01:00
r-zakarya c73ac66313 fix(android): the token is stored, not displayed
Kept in settings.bin, because the silent refresh needs it and so does the
first download on a fresh device - there is no vault to hide it in yet. But it
was the only secret on that screen shown in clear, right beside a masked
password field, and it is also the least protected of the three: settings.bin
is sealed by the no-auth Keystore key, readable without authentication, unlike
the vault.

Masked now. It is typed once, and reading it back has no use - a wrong token
shows up as a 401 in the log, not by squinting at the field.

What it opens stays worth knowing: read access to the account's repositories,
whose only content is ciphertext.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 16:49:06 +01:00
r-zakarya aa87580443 fix(android): no disc behind an entry icon
The translucent circle exists to back the initials of an entry without an
icon. A favicon brings its own transparency, so the circle showed through and
around it - a saucer under the drawing, plainly visible on the Gitea cup.

The circle is dropped as soon as the row carries an image, through a class on
the avatar rather than :has() so it does not depend on how new the engine is.
object-fit also goes from cover to contain: an icon is not a photograph and
has no business being cropped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 15:28:48 +01:00
r-zakarya c215a5755d fix(android): a network call that never returns no longer kills the interface
Reported symptom, with the local server stopped: Sync leaves the app inert -
neither Open vault nor Sync responds, and nothing is displayed. Log and
Server settings still work, because they are local to the page.

The log proves where it stops: the GET line is there, the "HTTP ... in ... ms"
line that immediately follows the call is not. So the request never returned,
SetBusy(False) never ran, and the two actions stayed disabled - a disabled
button fires no click, hence the silence. THTTPClient is given 15 s timeouts
and the Android request does inherit them, so this should not happen; rather
than keep chasing why it did, the situation is now impossible.

A deadline timer, armed while a request is in flight and longer than the HTTP
timeouts so the precise network error still wins when it works. On expiry the
interface comes back and says so. The thread is abandoned, not cancelled: it
dies when its socket fails, and if it somehow succeeds its result still shows.

The page's bridge queue had the same shape of bug: a throw while removing the
iframe left "sending" true forever, and every later command was dropped in
silence. The removal is now in a try/finally.

Also fixed while in there: SetBusy hardcoded "Sync" as the button label, so a
vault-less device lost "Download vault" on every busy transition.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 13:11:21 +01:00
r-zakarya 77809b09c8 refactor(android): the unlock screen moves to HTML, Skia and the style go
The caret in an Android TEdit does not land where you touch, which makes a long bearer token impossible to correct - the reason the last FMX screen had to go. A field in the engine inherits the system's text handling (caret, selection, handles, paste) for free.

With no FMX control left, two things became dead weight and are gone: the vendored Dark.style, which had nothing left to style, and Skia - 25 MB carried solely so FMX labels would shape Arabic. The engine does its own shaping; checked on the device, an Arabic filename still reads correctly. The APK drops from 22.0 to 12.7 MB.

Delphi stays the owner of the server settings; the page only edits them and hands them back when a field loses focus, one navigation per changed field rather than one per keystroke.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 10:40:06 +01:00
r-zakarya d1d5d29879 fix(android): the page stops sliding, and locking comes back
A swipe with nothing to scroll dragged the whole document, showing the body gradient sliding under the screen gradient - two gradients stacked and no content at all. overflow:hidden does not prevent that in an Android WebView; position:fixed does.

The lock button went missing when the list moved to HTML: it lived in the old FMX bottom bar and had no counterpart in the page. It returns as a padlock next to the search field, no label - it is the one icon universal enough to stand alone.

Icons on the unlock screen too, as code points since this source is read as ANSI. All four are basic-plane and common, so a font without them would show an empty box rather than a wrong glyph; checked on the device, they render.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 00:18:33 +01:00
r-zakarya 8c1cabb5bd feat(android): the list and the detail move to HTML
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>
2026-08-16 23:57:46 +01:00