Clone this repository
For self-hosted knots, clone URLs may differ based on your setup.
Three design fixes to the "your sites" list:
- Static icon placement: the site row was flex-wrap, so the action
buttons dropped below the label when the name or claimed slug URL was
wide. Split each row into a non-wrapping header (label left, actions
pinned right) with the inline editors stacked full-width beneath.
- Download icon: the Unicode download arrow (U+2B73) is absent from many
system fonts and rendered as tofu. Replaced with a currentColor SVG.
- Slug icon: the link glyph was the wrong metaphor for claiming a *name*;
replaced with a tag SVG. Both new SVGs pick up the button hover color,
which the emoji glyphs cannot.
The signed-in identity was computed from a scattered `handle ?? hint?.handle ??
agent?.did ?? hint?.did` chain across five mutable module vars, and nothing wrote
an identity hint before oauth.signIn() navigated away. So a fresh login returned
with no hint and flashed signed-out form → raw DID → handle as each async step
landed. A page reload avoided this only by accident (its stored hint carried a
handle).
Collapse it to one owner:
- A single `auth` AuthState { phase: signedOut|restoring|signedIn, identity, lastAccount },
mutated only through `authDispatch(event)` — the sole writer of both `auth` and
the `agent` credential. render() is now a pure function of `auth` for display.
- Invariant made structural: phase==="signedIn" iff `agent?.did` is set (RESTORED
is the only transition that assigns `agent`, and it degrades to signedOut without
a DID). Publish/writes still gate on the REAL `agent`, never on the optimistic phase.
- The fix: on the "Continue as" card path we already know the full identity, so
SIGN_IN_INTENT stashes the hint BEFORE navigating. The OAuth return then restores
through the same optimistic path as a reload — no signed-out flash, no DID flash.
Identity accretes did→handle→avatar and never discards a known field.
Retires the `restoring`/`hint`/`handle`/`avatar` module vars (folded into `auth`).
A first-time typed handle (no DID to stash pre-navigation) still shows one brief
resolving state — acceptable, it has no prior identity anyway. Hint format unchanged
(stays DID-keyed). Verified by typecheck + review; OAuth flow needs live confirmation.
Now that the remembered account persists across sign-out, give the signed-out
"Continue as" card an ✕ to forget it — drops the persistent record and hides
the card, leaving just the sign-in form. Restores clearLastAccount() for this
one caller.
Signing out cleared the persistent last-account record, so the signed-out
screen dropped to a bare login form. Treat explicit sign-out like an expired
session: end the session (revoke + clear the hint) but keep `lastAccount`, so
the "Continue as" card with the remembered handle + avatar stays offered.
Retire the now-unused clearLastAccount().
The session-restore spinner beside the identity was the "flash" a returning
user saw before landing signed-in. That restore is a passive background
reconcile the user never initiated, so it warrants no spinner — the hinted
identity + avatar already render immediately. Retire the auth-spinner element,
its inline first-paint reveal, the render() toggle, and the now-unused
.spinner / @keyframes spin CSS.
Fixes a flash where a returning user's identity briefly shows the raw
DID between oauth.init() resolving agent and loadIdentity() resolving
handle, since agent?.did was checked before hint?.handle.
On reload with a session hint, render() was blanking the confidently-seeded
identity down to a bare spinner for the whole oauth.init() round-trip, so a
returning user read as "loading" rather than "logged in". Now the remembered
identity + avatar (from the hint) stay the prominent element during the
optimistic restore; the spinner is demoted to a small in-flight cue beside them.
This also makes render() agree with the index.html pre-bundle seed (which
already paints identity+avatar+spinner) instead of fighting it.
Render-only optimism: authed still reflects the live agent, and Publish stays
gated on real auth via canPublish(state, authed) -- never on
showSignedIn/restoring -- so no write can fire against an unvalidated or expired
session. A failed init() reverts to the signed-out form as before.