Commits
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The modal passkey ceremony in handleEmailSubmit was an unbounded blocking
await: when navigator.credentials.get() neither resolved nor rejected (rpID
mismatch on preview domains), execution never reached the email OTP fetch,
the finally never ran, and the go button stayed disabled on '...' forever.
- Wrap the modal startAuthentication in an 8s raceTimeout; on timeout/failure
call WebAuthnAbortService.cancelCeremony() and fall through to email OTP.
- Replace the decorative (never-wired) conditionalAbort AbortController with
WebAuthnAbortService.cancelCeremony(), which the library honors, so the
mount-time conditional ceremony is actually cancelled before the modal one
and on unmount.
- Scope tryConditionalWebAuthn's loading flag to the verify step only, so
cancelling the conditional ceremony can't clobber loading mid-submit.
Adds AuthPill.svelte.test.ts covering the stall->OTP fallback (loading clears),
cancellation on failure, and the successful passkey short-circuit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The @mrgnw/anahtar 0.0.29-diagnostic.0 self-dependency was diagnostic
cruft committed by accident; a package cannot depend on a prerelease of
itself and it breaks `pnpm install --frozen-lockfile` (lockfile never
contained it). Removing it makes package.json match the lockfile again.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Email is the identity key but was compared case-sensitively and stored
as typed, so a different case (e.g. mobile auto-capitalization) missed
the user/passkey lookup, and the OTP path then created a duplicate
account instead of reusing the existing one.
- add normalizeEmail() (trim + lowercase)
- d1/sqlite/postgres: case-insensitive getUserByEmail + OTP lookups
(COLLATE NOCASE / LOWER) so existing mixed-case rows still resolve;
store new users and OTPs normalized
- AuthPill/AuthFlow email inputs: autocapitalize=none, autocorrect=off,
spellcheck=false
- tests for normalizeEmail and adapter case-insensitivity (52 pass)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Added roadmap section outlining potential changes to implementation and UI separation.
The `·` dot separators between pill segments are now opt-in. Default
rendering relies on the pill's flex gap; pass `separators={true}` to
restore the old look.
BREAKING: consumers who relied on the dots must set `separators` to true.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Consumers can now render extra icons inside the pill, next to sign-out,
via a Snippet prop. Used in anani for an internal-only /debug link.
<AuthPill ...>
{#snippet actions()}
<a class="anahtar-pill-icon" href="/debug">...</a>
{/snippet}
</AuthPill>
When actions is undefined, the pill renders exactly as before (no
separator, no gap). Added inside the authenticated branch only.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Merged countdown and manual prompt into a single unified layout:
ring + title + subtitle + Add passkey now + Maybe later.
Updated all 87 locale files with new translations.
- Add passkey/check-email and passkey/remove to route table (was missing)
- Add verify response section (hasPasskey, skipPasskeyPrompt)
- Document all three UI components with usage examples
- Add 'Building your own UI' section with API patterns
- Add conditional WebAuthn (passkey autofill on page load) pattern
- Add passkey-first login (check-email before OTP) pattern
- Add database migrations section (ALTER TABLE for upgrades)
- Note passkey rpID domain binding (preview != prod)
- Fix D1 example to use async getAuth pattern (matching real usage)
- Add rpName and cookie to AuthConfig docs
- Add export {} to app.d.ts example
- Default countdownSeconds 3 -> 5 for less rushed feel
- Ring/icon is now a <button> - click to skip timer and register now
- Hover scales ring up 5% as visual affordance
- All tests pass
Use country-specific domains instead of generic .com:
tu@exemple.cat, vous@exemple.fr, you@example.co.jp, etc.
Fix non-ASCII domains (ig, yo) and Cyrillic domain (uk).
Add 46 new translations sourced from anani language speaker data.
New: ak, am, as, az, be, ff, gu, ha, ht, hy, ig, jv, kk, km, kn, ku,
ln, lo, mg, ml, mn, mr, my, ne, ny, om, or, pa, ps, rn, rw, sd, si, so,
sq, st, su, tg, ti, tk, ts, tt, ug, uz, yo, zu
All marked as initial translations pending review.
- New d1Adapter() with prepare/bind/run pattern (exec() crashes D1 runtime)
- Exported as @mrgnw/anahtar/d1
- Version 0.0.5
- Add name field to passkey schema (types, sqlite, postgres adapters)
- Thread name through register-finish handler and passkey.ts
- Add guessDeviceName() client helper for UA-based device names
- AuthFlow sends auto-generated name on passkey registration
- Add congrats screen (step 4) with 'You're a passkey!' and 3s auto-redirect
- Replace PasskeyPrompt icon with key SVG + pulse glow animation
- Fix OtpInput import type bug in AuthFlow
- Fix test storePasskey calls to include name field
The @sveltejs/vite-plugin-svelte caused vitest to hang indefinitely in
DOM environments (jsdom/happy-dom) on Node 25. Replaced with a minimal
vite plugin that uses svelte's compile/compileModule directly.
- Split test configs: vitest.unit.ts (node) + vitest.browser.ts (happy-dom)
- Add svelteTesting() plugin for auto-cleanup between tests
- Fix AuthFlow email validation test to use fireEvent.submit
- All 68 tests passing (46 unit + 22 component)
- otp.test.ts: generate/verify OTP with mock AuthDB
- session.test.ts: create/validate/invalidate sessions with mock AuthDB
- sqlite.test.ts: full SQLite adapter integration tests with :memory: DB
- Add better-sqlite3 as devDep for integration tests
- Core: OTP generation/verification, session management, WebAuthn passkey flows
- DB adapters: SQLite (sync) and Postgres (async) with configurable table prefix
- SvelteKit integration: handle() hook and catch-all route handlers
- UI components: AuthFlow, OtpInput, PasskeyPrompt (Svelte 5)
- AuthDB interface uses MaybePromise<T> to support both sync and async adapters
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The modal passkey ceremony in handleEmailSubmit was an unbounded blocking
await: when navigator.credentials.get() neither resolved nor rejected (rpID
mismatch on preview domains), execution never reached the email OTP fetch,
the finally never ran, and the go button stayed disabled on '...' forever.
- Wrap the modal startAuthentication in an 8s raceTimeout; on timeout/failure
call WebAuthnAbortService.cancelCeremony() and fall through to email OTP.
- Replace the decorative (never-wired) conditionalAbort AbortController with
WebAuthnAbortService.cancelCeremony(), which the library honors, so the
mount-time conditional ceremony is actually cancelled before the modal one
and on unmount.
- Scope tryConditionalWebAuthn's loading flag to the verify step only, so
cancelling the conditional ceremony can't clobber loading mid-submit.
Adds AuthPill.svelte.test.ts covering the stall->OTP fallback (loading clears),
cancellation on failure, and the successful passkey short-circuit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The @mrgnw/anahtar 0.0.29-diagnostic.0 self-dependency was diagnostic
cruft committed by accident; a package cannot depend on a prerelease of
itself and it breaks `pnpm install --frozen-lockfile` (lockfile never
contained it). Removing it makes package.json match the lockfile again.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Email is the identity key but was compared case-sensitively and stored
as typed, so a different case (e.g. mobile auto-capitalization) missed
the user/passkey lookup, and the OTP path then created a duplicate
account instead of reusing the existing one.
- add normalizeEmail() (trim + lowercase)
- d1/sqlite/postgres: case-insensitive getUserByEmail + OTP lookups
(COLLATE NOCASE / LOWER) so existing mixed-case rows still resolve;
store new users and OTPs normalized
- AuthPill/AuthFlow email inputs: autocapitalize=none, autocorrect=off,
spellcheck=false
- tests for normalizeEmail and adapter case-insensitivity (52 pass)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Consumers can now render extra icons inside the pill, next to sign-out,
via a Snippet prop. Used in anani for an internal-only /debug link.
<AuthPill ...>
{#snippet actions()}
<a class="anahtar-pill-icon" href="/debug">...</a>
{/snippet}
</AuthPill>
When actions is undefined, the pill renders exactly as before (no
separator, no gap). Added inside the authenticated branch only.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add passkey/check-email and passkey/remove to route table (was missing)
- Add verify response section (hasPasskey, skipPasskeyPrompt)
- Document all three UI components with usage examples
- Add 'Building your own UI' section with API patterns
- Add conditional WebAuthn (passkey autofill on page load) pattern
- Add passkey-first login (check-email before OTP) pattern
- Add database migrations section (ALTER TABLE for upgrades)
- Note passkey rpID domain binding (preview != prod)
- Fix D1 example to use async getAuth pattern (matching real usage)
- Add rpName and cookie to AuthConfig docs
- Add export {} to app.d.ts example
Add 46 new translations sourced from anani language speaker data.
New: ak, am, as, az, be, ff, gu, ha, ht, hy, ig, jv, kk, km, kn, ku,
ln, lo, mg, ml, mn, mr, my, ne, ny, om, or, pa, ps, rn, rw, sd, si, so,
sq, st, su, tg, ti, tk, ts, tt, ug, uz, yo, zu
All marked as initial translations pending review.
- Add name field to passkey schema (types, sqlite, postgres adapters)
- Thread name through register-finish handler and passkey.ts
- Add guessDeviceName() client helper for UA-based device names
- AuthFlow sends auto-generated name on passkey registration
- Add congrats screen (step 4) with 'You're a passkey!' and 3s auto-redirect
- Replace PasskeyPrompt icon with key SVG + pulse glow animation
- Fix OtpInput import type bug in AuthFlow
- Fix test storePasskey calls to include name field
The @sveltejs/vite-plugin-svelte caused vitest to hang indefinitely in
DOM environments (jsdom/happy-dom) on Node 25. Replaced with a minimal
vite plugin that uses svelte's compile/compileModule directly.
- Split test configs: vitest.unit.ts (node) + vitest.browser.ts (happy-dom)
- Add svelteTesting() plugin for auto-cleanup between tests
- Fix AuthFlow email validation test to use fireEvent.submit
- All 68 tests passing (46 unit + 22 component)
- Core: OTP generation/verification, session management, WebAuthn passkey flows
- DB adapters: SQLite (sync) and Postgres (async) with configurable table prefix
- SvelteKit integration: handle() hook and catch-all route handlers
- UI components: AuthFlow, OtpInput, PasskeyPrompt (Svelte 5)
- AuthDB interface uses MaybePromise<T> to support both sync and async adapters