1
0
forked from wrenn/wrenn

Fix team name blink on navigation by lifting teams into a singleton store

Teams list was fetched on every Sidebar mount (each page navigation),
causing a flash from '…' to the real name on every tab switch. Move teams
into a module-level reactive store (teams.svelte.ts) that fetches once per
session and is shared between Sidebar and the team page.
This commit is contained in:
2026-03-24 14:44:09 +06:00
parent 71a7fdb76f
commit bf494f73fc
3 changed files with 40 additions and 17 deletions

View File

@ -3,7 +3,8 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { Popover } from 'bits-ui'; import { Popover } from 'bits-ui';
import { auth } from '$lib/auth.svelte'; import { auth } from '$lib/auth.svelte';
import { listTeams, createTeam, switchTeam, type TeamWithRole } from '$lib/api/team'; import { teams as teamsStore } from '$lib/teams.svelte';
import { createTeam, switchTeam } from '$lib/api/team';
import { import {
IconMonitor, IconMonitor,
IconBox, IconBox,
@ -25,9 +26,7 @@
let teamPopoverOpen = $state(false); let teamPopoverOpen = $state(false);
// Real teams from API let currentTeamName = $derived(teamsStore.list.find((t) => t.id === auth.teamId)?.name ?? '');
let teams = $state<TeamWithRole[]>([]);
let currentTeamName = $derived(teams.find((t) => t.id === auth.teamId)?.name ?? '');
let userName = $derived(auth.email ?? ''); let userName = $derived(auth.email ?? '');
// Create team dialog // Create team dialog
@ -69,10 +68,7 @@
} }
async function fetchTeams() { async function fetchTeams() {
const result = await listTeams(); await teamsStore.fetch();
if (result.ok) {
teams = result.data;
}
} }
async function handleSwitchTeam(teamId: string) { async function handleSwitchTeam(teamId: string) {
@ -180,7 +176,7 @@
> >
Teams Teams
</div> </div>
{#each teams as team (team.id)} {#each teamsStore.list as team (team.id)}
<button <button
class="flex w-full items-center gap-2.5 rounded-[var(--radius-input)] px-2.5 py-2 text-ui transition-colors duration-150 hover:bg-[var(--color-bg-3)] {team.id === class="flex w-full items-center gap-2.5 rounded-[var(--radius-input)] px-2.5 py-2 text-ui transition-colors duration-150 hover:bg-[var(--color-bg-3)] {team.id ===
auth.teamId auth.teamId

View File

@ -0,0 +1,31 @@
import { listTeams, type TeamWithRole } from '$lib/api/team';
function createTeamsStore() {
let teams = $state<TeamWithRole[]>([]);
let loaded = $state(false);
return {
get list() {
return teams;
},
get loaded() {
return loaded;
},
async fetch() {
if (loaded) return;
const result = await listTeams();
if (result.ok) {
teams = result.data;
loaded = true;
}
},
// Call after mutating teams (create/switch triggers a full reload, but
// adding a team locally avoids a flicker in the popover list).
set(newTeams: TeamWithRole[]) {
teams = newTeams;
loaded = true;
}
};
}
export const teams = createTeamsStore();

View File

@ -17,9 +17,9 @@
searchUsers, searchUsers,
type TeamInfo, type TeamInfo,
type TeamMember, type TeamMember,
type TeamWithRole,
type UserSearchResult type UserSearchResult
} from '$lib/api/team'; } from '$lib/api/team';
import { teams as teamsStore } from '$lib/teams.svelte';
let collapsed = $state( let collapsed = $state(
typeof window !== 'undefined' typeof window !== 'undefined'
@ -30,12 +30,11 @@
// Page data // Page data
let team = $state<TeamInfo | null>(null); let team = $state<TeamInfo | null>(null);
let members = $state<TeamMember[]>([]); let members = $state<TeamMember[]>([]);
let allTeams = $state<TeamWithRole[]>([]);
let loading = $state(true); let loading = $state(true);
let error = $state<string | null>(null); let error = $state<string | null>(null);
// True when this is the user's only team — deleting/leaving would leave them teamless // True when this is the user's only team — deleting/leaving would leave them teamless
let isLastTeam = $derived(allTeams.length <= 1); let isLastTeam = $derived(teamsStore.list.length <= 1);
// Current user's role — derived from members list // Current user's role — derived from members list
let myRole = $derived(members.find((m) => m.user_id === auth.userId)?.role ?? 'member'); let myRole = $derived(members.find((m) => m.user_id === auth.userId)?.role ?? 'member');
@ -86,9 +85,9 @@
loading = false; loading = false;
return; return;
} }
const [teamResult, teamsResult] = await Promise.all([ const [teamResult] = await Promise.all([
getTeam(auth.teamId), getTeam(auth.teamId),
listTeams() teamsStore.fetch()
]); ]);
if (teamResult.ok) { if (teamResult.ok) {
team = teamResult.data.team; team = teamResult.data.team;
@ -96,9 +95,6 @@
} else { } else {
error = teamResult.error; error = teamResult.error;
} }
if (teamsResult.ok) {
allTeams = teamsResult.data;
}
loading = false; loading = false;
} }