feat: restructure settings into identity, server, backup and advanced tabs

Drops the per-server pin list down to the one server your account is
bound to, adds an enforced network timeout, and replaces the global
status toast inside settings with inline feedback and a reusable
confirm dialog for rotate/log out. Mail-fetching preferences move into
the server tab; the now-superseded claim/recall forms are gone since
onboarding covers both.
This commit is contained in:
randogoth 2026-10-08 21:24:05 +03:00
parent fb0ca5c8cc
commit 84397a3fdd
6 changed files with 270 additions and 334 deletions

View file

@ -152,9 +152,9 @@
</form>
</dialog>
<!-- Tabs, not one scroll: the account, the server, a mailbox preference, the
backup file and the irreversible operations have nothing to say to each
other, and reading all five at once was the overwhelming part. -->
<!-- Tabs, not one scroll: identity, server, the backup file and the
irreversible operations have nothing to say to each other, and reading
all four at once was the overwhelming part. -->
<dialog id="settings">
<header class="dialog-head">
<h2>settings</h2>
@ -164,97 +164,85 @@
<div class="settings-body">
<nav id="settings-tabs" class="settings-tabs" aria-label="settings sections">
<button type="button" data-tab="account" class="active" aria-current="true">account</button>
<button type="button" data-tab="identity" class="active" aria-current="true">identity</button>
<button type="button" data-tab="server">server</button>
<button type="button" data-tab="mail">mail</button>
<button type="button" data-tab="data">backup</button>
<button type="button" data-tab="backup">backup</button>
<button type="button" data-tab="advanced">advanced</button>
</nav>
<div id="settings-panels" class="settings-panels">
<section class="panel" data-panel="account">
<div id="account-card"></div>
<details id="account-claim">
<summary id="account-claim-summary">claim your address</summary>
<p class="hint">Pick a username on a server you have pinned. This is what people write to.</p>
<form id="register-form">
<label>address<input name="address" placeholder="alice@example.org" spellcheck="false" required></label>
<label>invite token<input name="token" placeholder="only if your server asks"></label>
<button class="filled">register</button>
</form>
<p class="hint">Already registered this identity in another browser? Look the address up
instead — this checks it resolves to this identity and claims nothing.</p>
<form id="recall-form">
<label>address<input name="address" placeholder="alice@example.org" spellcheck="false" required></label>
<button>look it up</button>
</form>
</details>
<section class="panel" data-panel="identity">
<div id="identity-fields"></div>
<h3>recovery secret</h3>
<div id="identity-card"></div>
<div id="identity-secret"></div>
</section>
<section class="panel" data-panel="server" hidden>
<h3>your server</h3>
<p class="hint">Pinning a server's public key is what lets this client tell your real server
from anything pretending to be it. Ask its operator for the key over a channel you already
trust.</p>
<details>
<summary>why does this matter?</summary>
<p class="hint">Nothing else authenticates the server, so an unpinned host could be anyone —
registering, looking up an address and fetching all refuse to run against one.</p>
</details>
<ul id="pins" class="kv"></ul>
<form id="pin-form">
<label>host<input name="host" placeholder="example.org" spellcheck="false" required></label>
<label>public key<input name="key" placeholder="base32" spellcheck="false" required></label>
<button class="filled">pin</button>
</form>
</section>
<h3>connection</h3>
<div id="server-connection"></div>
<section class="panel" data-panel="mail" hidden>
<h3>fetching</h3>
<label class="check-row">
<input type="checkbox" id="leave-on-server">
<span>
<span class="check-title">leave mail on server</span>
<span class="hint">Keep a copy on the server after fetching, so another browser can
fetch it too. Deleting a message here removes it from both.</span>
<span class="check-title">Leave mail on server</span>
<span class="hint">Keep a copy on the server after fetching, so another client can fetch
it too. When this is enabled, deleting a message removes it from this client and the
server.</span>
</span>
</label>
<details>
<summary>what happens by default?</summary>
<p class="hint">Fetching acknowledges each message once it has been verified, and the server
drops its copy — so mail you have read lives only in this browser. Back it up under
"backup", or turn the option above on.</p>
</details>
</section>
<section class="panel" data-panel="data" hidden>
<h3>backup &amp; transfer</h3>
<p class="hint">One file holding your mail, contacts and pinned servers — for moving to
another browser, or for keeping what the server no longer has.</p>
<div class="backup-row">
<button type="button" id="export-data" class="filled">export</button>
<button type="button" id="import-data-trigger" class="outline">import</button>
<section class="panel" data-panel="backup" hidden>
<p class="hint">One file holding your mail, contacts and server info for moving to another
client, or for keeping what the server no longer has. It is sealed to this identity and
deliberately leaves the secret itself out.</p>
<div class="action-row">
<button type="button" id="export-data" class="filled">Export</button>
<span class="hint">Write a backup file</span>
</div>
<div class="action-row">
<button type="button" id="import-data-trigger" class="outline">Import</button>
<span class="hint">Restore from a backup file</span>
<input id="import-data" type="file" accept="application/json" hidden>
</div>
<details>
<summary>what is in the file, and is it safe?</summary>
<p class="hint">It is sealed to this identity: only a browser with this secret restored can
open it. It deliberately leaves the secret itself out — that belongs in a password
manager, not in a file in your downloads folder.</p>
</details>
<span id="backup-error" class="error"></span>
</section>
<section class="panel" data-panel="advanced" hidden>
<div id="advanced-card"></div>
<h3>rotations</h3>
<p id="rotations-description" class="hint">Issues a fresh key from your secret with a signed
certificate proving it replaces this one. Contacts accept the change from that chain, and
the old key stays readable. Rotation is not revocation.</p>
<div id="rotations-count"></div>
<button type="button" id="rotate-identity" class="outline">Rotate identity</button>
<span id="rotate-error" class="error"></span>
<h3 class="danger-head">log out</h3>
<p id="logout-description" class="hint">Removes this identity from this client entirely: the
secret, pins, contacts and every cached message. Without a backup of the secret there is no
way back in. Mail already fetched here may have no other copy anywhere, unless "leave mail on
server" was on. Whatever is still on the server comes back on the first fetch after logging
back in.</p>
<button type="button" id="logout" class="danger">Log out and delete all data</button>
<span id="logout-error" class="error"></span>
</section>
</div>
</div>
</dialog>
<dialog id="confirm">
<div class="confirm-body">
<h2 id="confirm-title"></h2>
<p id="confirm-text"></p>
</div>
<footer>
<button type="button" id="confirm-cancel" autofocus>Cancel</button>
<button type="button" id="confirm-ok"></button>
</footer>
</dialog>
<div id="status" class="toast" role="status" aria-live="polite" hidden></div>
<script type="module" src="js/app.js"></script>
</body>