diff --git a/crates/tauri-app/src/App.svelte b/crates/tauri-app/src/App.svelte
index 588c6b0..075476d 100644
--- a/crates/tauri-app/src/App.svelte
+++ b/crates/tauri-app/src/App.svelte
@@ -7,6 +7,8 @@
fetchProfile,
fetchSearch,
fetchPost,
+ openExternalUrl,
+ showError,
type Session,
type Post,
type ProfileResponse,
@@ -19,7 +21,7 @@
import Terminal from "./lib/components/Terminal.svelte";
import Skeleton from "./lib/components/Skeleton.svelte";
- type View = "home" | "compose" | "profile" | "search";
+ type View = "home" | "compose" | "profile" | "search" | "settings";
let view: View = $state("home");
let currentUser: Session | null = $state(null);
@@ -351,6 +353,19 @@
await refreshTimeline(true);
}
+ async function handleLogout() {
+ try {
+ await session.logout();
+ view = "home";
+ profile = null;
+ searchResults = [];
+ threadRoot = null;
+ threadParent = null;
+ userPosts = [];
+ } catch (e) {
+ showError(`logout failed: ${String(e)}`);
+ }
+ }
// Derive a display handle. The session already gives us the user's
// real handle (e.g. "alice.bsky.social"). When the AppView decorates
// posts that have empty handles it falls back to a synthetic
@@ -360,6 +375,24 @@
if (h.startsWith("@")) return h;
return `@${h}`;
}
+
+ // Mirror the URLs the Rust shell reads from MAARCADETWEET_PDS_URL /
+ // MAARCADETWEET_APPVIEW_URL (see `crates/tauri-app/src-tauri/src/lib.rs`).
+ // Used in the Settings view to show which backends the client is
+ // talking to. Kept as plain helpers so they can be swapped for a
+ // `pds_describe`/`appview_describe` Tauri command later.
+ function pdsBase(): string {
+ if (typeof import.meta !== "undefined" && (import.meta as any).env?.VITE_PDS_URL) {
+ return (import.meta as any).env.VITE_PDS_URL as string;
+ }
+ return "http://127.0.0.1:2583";
+ }
+ function appviewBase(): string {
+ if (typeof import.meta !== "undefined" && (import.meta as any).env?.VITE_APPVIEW_URL) {
+ return (import.meta as any).env.VITE_APPVIEW_URL as string;
+ }
+ return "http://127.0.0.1:2584";
+ }
{#if !currentUser}
@@ -446,9 +479,10 @@
{:else if profile}
+
+
+
+
- followers
@@ -478,15 +528,90 @@
- {profile.posts.length}
+
{#if profile.posts.length === 0}
- // no posts yet
+ // no posts yet — compose your first one
{:else}
+ // recent posts
{#each profile.posts as p (p.uri)}
{/each}
{/if}
{/if}
+ {:else if view === "settings"}
+
+ $
+ // settings
+
+
+ // account
+
+
+
- handle
+ - @{currentUser?.handle ?? "?"}
+
+
+
- did
+ - {currentUser?.did ?? "?"}
+
+
+
- posts in cache
+ - {userPosts.length}
+
+
+
+ // actions
+
+
+
+
+
+
+ // about
+
+
+
- app
+ - maarcadetweet
+
+
+
- version
+ - 0.1.0
+
+
+
- backend
+ - {pdsBase()}
+
+
+
- appview
+ - {appviewBase()}
+
+
+
+
+
+
+
{:else if view === "search"}
$
@@ -753,4 +878,69 @@
border-color: var(--red);
background: rgba(255, 59, 48, 0.08);
}
+
+ .btn--danger {
+ color: var(--red);
+ border-color: var(--red);
+ }
+ .btn--danger:hover:not(:disabled) {
+ background: rgba(255, 59, 48, 0.08);
+ color: var(--red);
+ border-color: var(--red);
+ }
+
+ .profile__h3,
+ .settings__h3 {
+ font-family: var(--font-mono);
+ font-size: var(--fs-50);
+ color: var(--text-dim);
+ letter-spacing: 0.04em;
+ margin: var(--s-4) 0 var(--s-2);
+ font-weight: 400;
+ }
+
+ .did-cell {
+ word-break: break-all;
+ font-size: var(--fs-50);
+ }
+
+ .settings {
+ padding: 0 var(--s-3);
+ display: flex;
+ flex-direction: column;
+ gap: var(--s-2);
+ }
+ .settings__rows {
+ display: flex;
+ flex-direction: column;
+ gap: var(--s-1);
+ padding: var(--s-2) var(--s-4);
+ margin: 0 0 var(--s-4);
+ font-family: var(--font-mono);
+ font-size: var(--fs-50);
+ }
+ .settings__rows > div {
+ display: flex;
+ gap: var(--s-3);
+ }
+ .settings__rows dt {
+ color: var(--text-dim);
+ letter-spacing: 0.04em;
+ min-width: 9rem;
+ }
+ .settings__rows dd {
+ margin: 0;
+ color: var(--text);
+ }
+ .settings__actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--s-2);
+ margin: 0 0 var(--s-4);
+ }
+ .settings__signout {
+ margin-top: var(--s-4);
+ padding-top: var(--s-4);
+ border-top: 1px dashed var(--line);
+ }
diff --git a/crates/tauri-app/src/lib/components/NavRail.svelte b/crates/tauri-app/src/lib/components/NavRail.svelte
index edf6b21..9768c51 100644
--- a/crates/tauri-app/src/lib/components/NavRail.svelte
+++ b/crates/tauri-app/src/lib/components/NavRail.svelte
@@ -4,7 +4,7 @@
// `$bindable`, use a callback prop to bubble state changes up to
// the parent.
- type View = "home" | "compose" | "profile" | "search";
+ type View = "home" | "compose" | "profile" | "search" | "settings";
let {
view = "home",
@@ -19,6 +19,7 @@
{ id: "compose", label: "compose", key: "c", icon: "compose" },
{ id: "profile", label: "profile", key: "p", icon: "profile" },
{ id: "search", label: "search", key: "/", icon: "search" },
+ { id: "settings", label: "settings", key: ",", icon: "settings" },
];
@@ -44,6 +45,11 @@
+ {:else if item.icon === "settings"}
+
{:else}