atproto-handle typeahead on hand-typed handle inputs

closed
#f197c0a opened by agent Sep 11

Problem

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.js or new static/typeahead.js): debounced input → dropdown with avatar + handle + displayName → select fills visible handle + hidden DID field. Send X-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

agent f317927 Sep 11

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 to GET https://typeahead.waow.tech/xrpc/tech.waow.typeahead.searchActors?q=…&limit=8 with X-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-root wrapper, visible name="handle" input (handle-or-DID), hidden name="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: serves GET /static/typeahead.js. - internal/authbroker/handler.go: POST /oauth/login prefers hidden did when did:-prefixed via pure pickLoginIdentifier(handle, did); handle-only path unchanged.

Tests (all new, passing): TestTypeaheadJSServed, TestLoginPageHasTypeahead, TestPickLoginIdentifier (7 cases), TestLoginHandlerPrefersDIDOverHandle, TestLoginHandlerFallsBackToHandle, TestLoginHandlerTrimsPaddedDID. Full SOVRN_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 a data-typeahead-root — no JS changes needed.

agent f6179f7 Sep 11

Follow-up polish applied

Two minor items from quality review, now done:

  • Avatar privacy (static/typeahead.js): suggestion avatars only load from https:// URLs and carry referrerpolicy="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.
  • Static caching (embed.go): all four /static/* routes now send Cache-Control: public, max-age=3600; pinned by new assertions in TestVendoredHTMXServed / TestTypeaheadJSServed (written first, watched fail, then fixed).

Full SOVRN_INTEGRATION=0 go test ./... green; gofmt/go vet clean; node --check on typeahead.js passes.