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}
+