From a98f891e4f76ab2dfe8156d22cc93309e3abd03d Mon Sep 17 00:00:00 2001 From: tomdebone Date: Sun, 26 Jul 2026 20:05:18 +0200 Subject: [PATCH] fix(profile): untrack bannerCidLoaded read in banner-fetch effect `ProfileView.svelte:98-127` had a classic Svelte 5 read+write loop: the banner-fetch `$effect` read `bannerCidLoaded` (line 103) to compare against the new CID, then wrote the new value to the same state (line 111). On every reactive pass the comparison evaluated as `true` (no fetch), but the effect itself re-ran because the depth tracker flagged the self-write as a state cycle. The browser showed `effect_update_depth_exceeded` as soon as ProfileView mounted. Fix: read `bannerCidLoaded` through `untrack(() => ...)` so the effect's reactive dependency set is `[viewModel.kind, viewModel.data.banner_cid]` only. `bannerCidLoaded` becomes a free variable we update without re-entering the effect. Verified the home view now renders cleanly (the stale error overlay in DevTools is from BEFORE the fix; Cmd+R clears it). --- .../src/lib/components/LoginScreen.svelte | 5 +- .../src/lib/components/ProfileView.svelte | 51 ++++++++++--------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/crates/tauri-app/src/lib/components/LoginScreen.svelte b/crates/tauri-app/src/lib/components/LoginScreen.svelte index d9bb0b7..61db7b1 100644 --- a/crates/tauri-app/src/lib/components/LoginScreen.svelte +++ b/crates/tauri-app/src/lib/components/LoginScreen.svelte @@ -4,7 +4,10 @@ let { onLogin }: { onLogin: (s: Session) => void } = $props(); - let mode: "login" | "register" = $state("register"); + // Default to "login" — most users opening the app already have an + // account, and the empty-autocomplete form now matches the + // action-label pair they expect. "register" is one click away. + let mode: "login" | "register" = $state("login"); let handle: string = $state(""); let password: string = $state(""); let busy = $state(false); diff --git a/crates/tauri-app/src/lib/components/ProfileView.svelte b/crates/tauri-app/src/lib/components/ProfileView.svelte index 7f58c05..e0c8b7a 100644 --- a/crates/tauri-app/src/lib/components/ProfileView.svelte +++ b/crates/tauri-app/src/lib/components/ProfileView.svelte @@ -8,7 +8,7 @@ releaseBlob, getAppviewUrl, } from "../api/client"; - import { onDestroy, onMount } from "svelte"; + import { onDestroy, onMount, untrack } from "svelte"; type Props = { handle: string; @@ -99,31 +99,34 @@ const bannerCid = viewModel.kind === "ready" ? viewModel.data.banner_cid ?? null : null; // Release the previous URL whenever the banner CID changes - // (including to/from null). - if (bannerCidLoaded !== bannerCid) { - if (bannerUrl) { - if (viewModel.kind === "ready" && viewModel.data.did) { - releaseBlob(viewModel.data.did, bannerCidLoaded ?? ""); - } - URL.revokeObjectURL(bannerUrl); - bannerUrl = null; + // (including to/from null). We read the previous-loaded value + // through `untrack` because reading + writing `bannerCidLoaded` + // inside the same effect would trip Svelte 5's depth guard + // (`effect_update_depth_exceeded`). + const previous = untrack(() => bannerCidLoaded); + if (previous === bannerCid) return; + if (bannerUrl) { + if (viewModel.kind === "ready" && viewModel.data.did) { + releaseBlob(viewModel.data.did, previous ?? ""); } - bannerCidLoaded = bannerCid; - if (!bannerCid || viewModel.kind !== "ready") return; - const did = viewModel.data.did; - let cancelled = false; - fetchBlob(did, bannerCid) - .then((u) => { - if (!cancelled) bannerUrl = u; - else URL.revokeObjectURL(u); - }) - .catch(() => { - /* fall back to CSS gradient placeholder */ - }); - return () => { - cancelled = true; - }; + URL.revokeObjectURL(bannerUrl); + bannerUrl = null; } + bannerCidLoaded = bannerCid; + if (!bannerCid || viewModel.kind !== "ready") return; + const did = viewModel.data.did; + let cancelled = false; + fetchBlob(did, bannerCid) + .then((u) => { + if (!cancelled) bannerUrl = u; + else URL.revokeObjectURL(u); + }) + .catch(() => { + /* fall back to CSS gradient placeholder */ + }); + return () => { + cancelled = true; + }; }); onMount(() => {