diff --git a/apps/web/README.md b/apps/web/README.md index b3aa3ff..586369b 100644 --- a/apps/web/README.md +++ b/apps/web/README.md @@ -162,6 +162,12 @@ The AT-native publishing path is stateless on the Cartesium server: - `/cartesium/oauth-client-metadata.json` exposes deployment-specific OAuth metadata. `/cartesium/resolve-handle` performs DNS TXT resolution with a bounded, public-host-only HTTPS well-known fallback. +- The account picker searches public actor directories after two typed + characters, using Typeahead first and the Bluesky public AppView as a + fallback when the primary service is unavailable. Suggestions are optional; + users may still enter a handle or DID manually. A selected suggestion only + supplies the handle to the existing OAuth flow; its returned DID is not used + as an authentication authority. Set `CARTESIUM_PUBLICATION_V1_COLLECTION` to the production NSID selected for Cartesium publication V1 records. Set `CARTESIUM_PUBLICATION_V2_COLLECTION` diff --git a/apps/web/src/lib/components/AtprotoSignInDialog.svelte b/apps/web/src/lib/components/AtprotoSignInDialog.svelte new file mode 100644 index 0000000..a69840f --- /dev/null +++ b/apps/web/src/lib/components/AtprotoSignInDialog.svelte @@ -0,0 +1,370 @@ + + + + +{#if open} +
+ +
+{/if} + + diff --git a/apps/web/src/lib/components/Calculator3dShell.svelte b/apps/web/src/lib/components/Calculator3dShell.svelte index e29bd0b..63caf4c 100644 --- a/apps/web/src/lib/components/Calculator3dShell.svelte +++ b/apps/web/src/lib/components/Calculator3dShell.svelte @@ -58,6 +58,7 @@ import { createPublishedDocumentV2 } from '$lib/publication/document-v2'; import type { PublishingOAuth } from '$lib/publishing/oauth'; import AppToolbar from './AppToolbar.svelte'; + import AtprotoSignInDialog from './AtprotoSignInDialog.svelte'; import ExpressionPanel3d from './ExpressionPanel3d.svelte'; import GraphCanvas3d from './GraphCanvas3d.svelte'; import GraphLibrary from './GraphLibrary.svelte'; @@ -109,6 +110,9 @@ let accountHandle = $state(null); let accountBusy = $state(false); let publishingBusy = $state(false); + let signInDialogOpen = $state(false); + let signInDialogPurpose = $state<'signin' | 'publish'>('signin'); + let signInDialogError = $state(''); let publishedGraphs = $state([]); let publishedCursor = $state(null); let publishedLoading = $state(false); @@ -389,26 +393,52 @@ return account.oauth; } - async function signInAccount(): Promise { + function openAtprotoSignIn(purpose: 'signin' | 'publish' = 'signin') { + if (accountBusy || publishingBusy) return; + if (purpose === 'signin' && publishingOAuth?.session) { + shareStatus = 'Already signed in with AT Protocol'; + return; + } + signInDialogPurpose = purpose; + signInDialogError = ''; + signInDialogOpen = true; + } + + function closeAtprotoSignIn() { + if (accountBusy) return; + if (signInDialogPurpose === 'publish') { + window.sessionStorage.removeItem(PENDING_PUBLICATION_KEY); + } + signInDialogOpen = false; + signInDialogError = ''; + } + + async function submitAtprotoSignIn(identifier: string): Promise { if (accountBusy) return; accountBusy = true; + signInDialogError = ''; try { const oauth = await ensureAtprotoAccount(); if (oauth.session) { + signInDialogOpen = false; shareStatus = 'Already signed in with AT Protocol'; return; } shareStatus = 'Opening AT Protocol authorization…'; - const started = await beginAtprotoSignIn(oauth, 'signin'); - if (!started) shareStatus = ''; + await beginAtprotoSignIn(oauth, identifier, signInDialogPurpose); } catch (error) { - shareStatus = - error instanceof Error ? error.message : 'Could not sign in with AT Protocol.'; + const message = error instanceof Error ? error.message : 'Could not sign in with AT Protocol.'; + signInDialogError = message; + shareStatus = message; } finally { accountBusy = false; } } + function signInAccount(): void { + openAtprotoSignIn('signin'); + } + async function signOutAccount(): Promise { const oauth = publishingOAuth; if (!oauth?.session || accountBusy) return; @@ -458,9 +488,10 @@ PENDING_PUBLICATION_KEY, JSON.stringify({ document: currentDocument(), presentation }) ); - shareStatus = 'Opening AT Protocol authorization…'; - const started = await beginAtprotoSignIn(oauth, 'publish'); - if (!started) window.sessionStorage.removeItem(PENDING_PUBLICATION_KEY); + signInDialogPurpose = 'publish'; + signInDialogError = ''; + signInDialogOpen = true; + shareStatus = ''; return; } const preview = await graphCanvas.exportPreviewPng(); @@ -1179,6 +1210,14 @@ publicationurl={publicationUrl} /> {/if} +