atproto-handle typeahead on hand-typed handle inputs
closedProblem
Handles hand-typed by users (OAuth login today; mailbox→DID mapping in 3726817 tomorrow) are easy to mistype with zero feedback until a failed lookup.
Proposal
Use the community typeahead service (https://typeahead.waow.tech/docs):
GET https://typeahead.waow.tech/xrpc/tech.waow.typeahead.searchActors?q=…&limit=8
(canonical endpoint; CORS-enabled, 60 req/min/IP, 60s edge cache; returns
did/handle/displayName/avatar).
- Client JS (extend
static/app.jsor newstatic/typeahead.js): debounced input → dropdown with avatar + handle + displayName → select fills visible handle + hidden DID field. SendX-Client: sovrn.at. Degrades to plain text input on fetch failure. - Apply to: login handle input (
login.templ, posts to/oauth/login); the 3726817 DID-mapping handle input must be built with typeahead from the start (dependency noted there). - Disclosure: keystrokes go to a third-party directory service (public data, but say so in UI copy or docs).
Acceptance
- Typing a handle prefix shows avatar-bearing suggestions; selecting one submits the correct handle/DID; no-JS typing still works.
2 Comments
Implementation complete (working copy, uncommitted)
Client-side typeahead on the login handle input, per spec + user decisions (hidden-DID preference, per-page script, no disclosure copy).
Changes: - New
internal/appview/ui/static/typeahead.js: debounced (250ms, min 2 chars, AbortController) query toGET https://typeahead.waow.tech/xrpc/tech.waow.typeahead.searchActors?q=…&limit=8withX-Client: sovrn.at; avatar+handle+displayName dropdown; textContent-only rendering; select fills visible handle + hidden DID; manual edit clears stale DID; Escape/outside-click/focusout dismiss; ArrowUp/Down + Enter keyboard selection with aria-activedescendant. -internal/appview/ui/login.templ:data-typeahead-rootwrapper, visiblename="handle"input (handle-or-DID), hiddenname="did",ul[data-typeahead-list]listbox, per-page<script src="/static/typeahead.js" defer>(layout untouched). No-JS POST unchanged. -internal/appview/ui/embed.go: servesGET /static/typeahead.js. -internal/authbroker/handler.go:POST /oauth/loginprefers hiddendidwhendid:-prefixed via purepickLoginIdentifier(handle, did); handle-only path unchanged.Tests (all new, passing):
TestTypeaheadJSServed,TestLoginPageHasTypeahead,TestPickLoginIdentifier(7 cases),TestLoginHandlerPrefersDIDOverHandle,TestLoginHandlerFallsBackToHandle,TestLoginHandlerTrimsPaddedDID. FullSOVRN_INTEGRATION=0 go test ./...green. Spec review ✅, quality review approved (minor polish deferred: avatar referrerpolicy, static cache headers).Reuse note for 3726817: any future handle input gets typeahead by adding
data-typeahead+ list/hidden-DID markup inside adata-typeahead-root— no JS changes needed.Follow-up polish applied
Two minor items from quality review, now done:
static/typeahead.js): suggestion avatars only load fromhttps://URLs and carryreferrerpolicy="no-referrer", so image hosts learn nothing about the login page via Referer and can’t be used as tracking pixels. Non-https avatars fall back to text rows.embed.go): all four/static/*routes now sendCache-Control: public, max-age=3600; pinned by new assertions inTestVendoredHTMXServed/TestTypeaheadJSServed(written first, watched fail, then fixed).Full
SOVRN_INTEGRATION=0 go test ./...green;gofmt/go vetclean;node --checkon typeahead.js passes.