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}
- {displayHandle(profile.handle)} - {profile.did} +
{displayHandle(profile.handle)}
+
{profile.did}
+
+ +
+
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} diff --git a/crates/tauri-app/src/lib/components/NavRail.test.ts b/crates/tauri-app/src/lib/components/NavRail.test.ts index 4649cca..9814ed1 100644 --- a/crates/tauri-app/src/lib/components/NavRail.test.ts +++ b/crates/tauri-app/src/lib/components/NavRail.test.ts @@ -29,10 +29,10 @@ async function mountHarness() { } describe("NavRail (callback-prop pattern)", () => { - it("renders 4 buttons with home active", async () => { + it("renders 5 buttons with home active", async () => { await mountHarness(); const btns = target.querySelectorAll("button.rail__btn"); - expect(btns.length).toBe(4); + expect(btns.length).toBe(5); expect(btns[0].classList.contains("active")).toBe(true); expect(target.querySelector('[data-testid="view-value"]')?.textContent).toBe("home"); }); @@ -56,5 +56,10 @@ describe("NavRail (callback-prop pattern)", () => { await tick(); expect(target.querySelector('[data-testid="view-value"]')?.textContent).toBe("profile"); expect(btns[2].classList.contains("active")).toBe(true); + + btns[4].dispatchEvent(new MouseEvent("click", { bubbles: true })); + await tick(); + expect(target.querySelector('[data-testid="view-value"]')?.textContent).toBe("settings"); + expect(btns[4].classList.contains("active")).toBe(true); }); }); \ No newline at end of file diff --git a/crates/tauri-app/src/lib/components/NavRailHarness.svelte b/crates/tauri-app/src/lib/components/NavRailHarness.svelte index 8312fcf..dc5b624 100644 --- a/crates/tauri-app/src/lib/components/NavRailHarness.svelte +++ b/crates/tauri-app/src/lib/components/NavRailHarness.svelte @@ -5,7 +5,7 @@ import NavRail from "./NavRail.svelte"; - type View = "home" | "compose" | "profile" | "search"; + type View = "home" | "compose" | "profile" | "search" | "settings"; let view: View = $state("home");