feat: rework first run as a setup checklist and split settings into tabs
This commit is contained in:
parent
6f82a84b69
commit
1710d7b144
5 changed files with 604 additions and 232 deletions
216
web/index.html
216
web/index.html
|
|
@ -21,6 +21,8 @@
|
|||
<symbol id="i-moon" viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></symbol>
|
||||
<symbol id="i-reply" viewBox="0 0 24 24"><path d="M9 17 4 12l5-5"/><path d="M4 12h11a5 5 0 0 1 5 5v2"/></symbol>
|
||||
<symbol id="i-back" viewBox="0 0 24 24"><path d="M15 18l-6-6 6-6"/></symbol>
|
||||
<symbol id="i-close" viewBox="0 0 24 24"><path d="M18 6 6 18M6 6l12 12"/></symbol>
|
||||
<symbol id="i-check" viewBox="0 0 24 24"><path d="M20 6 9 17l-5-5"/></symbol>
|
||||
</svg>
|
||||
<header class="topbar">
|
||||
<div class="brand">smol mail</div>
|
||||
|
|
@ -34,21 +36,43 @@
|
|||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Two cards, one at a time: deciding between "create" and "restore" is the
|
||||
only question this screen asks. Everything the new identity still needs is
|
||||
deferred to the checklist in the app, so neither card is a wall of fields. -->
|
||||
<main id="setup" class="setup" hidden>
|
||||
<h2>welcome</h2>
|
||||
<p>Identity is one 32-byte master secret. Back it up: it is the only secret, and losing it means losing the address.</p>
|
||||
<div class="row">
|
||||
<button id="create-identity">create a new identity</button>
|
||||
</div>
|
||||
<p class="hint">or restore from a saved master:</p>
|
||||
<div class="row restore-row">
|
||||
<label>master<input id="restore-master" placeholder="64 hex characters" spellcheck="false"></label>
|
||||
<label>address (optional)<input id="restore-address" placeholder="alice@example.org, if already registered"
|
||||
spellcheck="false"></label>
|
||||
<label id="restore-key-label">server's public key
|
||||
<input id="restore-key" placeholder="base32" spellcheck="false"></label>
|
||||
<button id="restore-identity">restore</button>
|
||||
</div>
|
||||
<section id="setup-create" class="setup-card">
|
||||
<h2>welcome</h2>
|
||||
<p class="lead">Encrypted mail with no account and no password. Your identity is one secret,
|
||||
kept in this browser and nowhere else.</p>
|
||||
<button type="button" id="create-identity" class="filled large">create a new identity</button>
|
||||
<p class="hint centered">The next screen walks you through the three things left to set up.</p>
|
||||
<button type="button" id="show-restore" class="link">I already have a secret to restore</button>
|
||||
</section>
|
||||
|
||||
<section id="setup-restore" class="setup-card" hidden>
|
||||
<h2>restore</h2>
|
||||
<p class="lead">Paste the secret you saved. Your keys come back from it — it is all this client
|
||||
needs.</p>
|
||||
<form id="restore-form" class="stack">
|
||||
<label>your master secret
|
||||
<input id="restore-master" placeholder="64 hex characters" spellcheck="false" required>
|
||||
</label>
|
||||
<details id="restore-extra">
|
||||
<summary>I also know my address</summary>
|
||||
<p class="hint">Optional, and easy to add later from settings. A secret does not record which
|
||||
address it was registered under, and this browser was never told — so filling these in is
|
||||
what brings the address back now.</p>
|
||||
<label>address
|
||||
<input id="restore-address" placeholder="alice@example.org" spellcheck="false">
|
||||
</label>
|
||||
<label id="restore-key-label">your server's public key
|
||||
<input id="restore-key" placeholder="base32, from the server's operator" spellcheck="false">
|
||||
</label>
|
||||
</details>
|
||||
<button class="filled large">restore</button>
|
||||
</form>
|
||||
<button type="button" id="show-create" class="link">back</button>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<main id="app" hidden>
|
||||
|
|
@ -68,6 +92,17 @@
|
|||
</div>
|
||||
</nav>
|
||||
<div class="listpane">
|
||||
<!-- What setup still owes, over the mailbox it is holding up rather than
|
||||
inside the settings dialog the user would have to think to open. -->
|
||||
<aside id="setup-banner" class="setup-banner" hidden>
|
||||
<div class="setup-banner-head">
|
||||
<h2>finish setting up</h2>
|
||||
<span id="setup-banner-count" class="setup-banner-count"></span>
|
||||
<button type="button" id="setup-dismiss" class="icon-only" aria-label="hide this checklist"
|
||||
title="hide this checklist"><svg class="icon"><use href="#i-close"/></svg></button>
|
||||
</div>
|
||||
<ol id="setup-steps" class="setup-steps"></ol>
|
||||
</aside>
|
||||
<div id="list-toolbar" class="toolbar">
|
||||
<div id="mail-tools" class="tools">
|
||||
<button id="fetch" class="primary"><svg class="icon"><use href="#i-refresh"/></svg><span class="label">fetch</span></button>
|
||||
|
|
@ -98,72 +133,107 @@
|
|||
</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. -->
|
||||
<dialog id="settings">
|
||||
<h2>settings</h2>
|
||||
<header class="dialog-head">
|
||||
<h2>settings</h2>
|
||||
<button type="button" id="settings-close" class="icon-only" aria-label="close settings"
|
||||
title="close"><svg class="icon"><use href="#i-close"/></svg></button>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<h3><span id="step-server" class="step" data-n="1">1</span> your server</h3>
|
||||
<p class="hint">Pin the key of the server that will hold your mail. Get it from its operator over a
|
||||
channel you already trust — registering, recalling and fetching all refuse an unpinned server.</p>
|
||||
<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>
|
||||
<ul id="pins" class="kv"></ul>
|
||||
</section>
|
||||
<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="server">server</button>
|
||||
<button type="button" data-tab="mail">mail</button>
|
||||
<button type="button" data-tab="data">backup</button>
|
||||
<button type="button" data-tab="advanced">advanced</button>
|
||||
</nav>
|
||||
|
||||
<section>
|
||||
<h3><span id="step-address" class="step" data-n="2">2</span> your address</h3>
|
||||
<p class="hint">Claim 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="if required"></label>
|
||||
<button class="filled">register</button>
|
||||
</form>
|
||||
<details>
|
||||
<summary>already registered elsewhere?</summary>
|
||||
<p class="hint">Restoring a master brings back the identity, not the memory of what address it was
|
||||
registered under in a different browser — the server was never asked. Recall it here: this
|
||||
looks the address up and checks it resolves to this identity's key, without registering anything.</p>
|
||||
<form id="recall-form">
|
||||
<label>address<input name="address" placeholder="alice@example.org" spellcheck="false" required></label>
|
||||
<button>recall</button>
|
||||
</form>
|
||||
</details>
|
||||
</section>
|
||||
<div id="settings-panels" class="settings-panels">
|
||||
<section class="panel" data-panel="account">
|
||||
<div id="account-card"></div>
|
||||
|
||||
<section>
|
||||
<h3>your identity</h3>
|
||||
<div id="identity-card"></div>
|
||||
</section>
|
||||
<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>
|
||||
<h3>fetch</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">Fetch keeps a copy on the server instead of deleting it; delete a message
|
||||
here to remove it from both.</span>
|
||||
</span>
|
||||
</label>
|
||||
</section>
|
||||
<h3>recovery secret</h3>
|
||||
<div id="identity-card"></div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>backup</h3>
|
||||
<p class="hint">Mail, once fetched, exists only here unless "leave mail on server" is on above.
|
||||
This backs up the local inbox, requests, sent mail, contacts and pinned servers into one file.
|
||||
It never includes the master: that has its own reveal-and-copy flow above, meant for a
|
||||
password manager, not a downloadable file.</p>
|
||||
<div class="backup-row">
|
||||
<button type="button" id="export-data">export</button>
|
||||
<button type="button" id="import-data-trigger">import</button>
|
||||
<input id="import-data" type="file" accept="application/json" hidden>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</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 & 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>
|
||||
<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>
|
||||
</section>
|
||||
|
||||
<section class="panel" data-panel="advanced" hidden>
|
||||
<div id="advanced-card"></div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer><button id="settings-close">close</button></footer>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<div id="status" class="toast" role="status" aria-live="polite" hidden></div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue