refactor(desktop): extract community destination restore hook

AppShell.tsx crossed the 1000-line desktop ratchet again after the
forward-history wiring. Move the community destination restore effect
into its own hook alongside useCommunityNavigationTransitions instead of
squeezing the call site further.

Signed-off-by: Matt Toohey <contact@matttoohey.com>
This commit is contained in:
Matt Toohey
2026-08-28 10:43:42 +10:00
parent 3485f05cd5
commit 9f53f25f94
2 changed files with 89 additions and 49 deletions

View File

@@ -15,6 +15,7 @@ import {
import { useAppNavigation } from "@/app/navigation/useAppNavigation";
import { describeHistoryLocation } from "@/app/navigation/navigationHistory";
import { useBackForwardControls } from "@/app/navigation/useBackForwardControls";
import { useCommunityDestinationRestore } from "@/app/useCommunityDestinationRestore";
import { useCommunityNavigationTransitions } from "@/app/useCommunityNavigationTransitions";
import { useLiveHomeFeedActions } from "@/app/useLiveHomeFeedActions";
import { useChannelBrowserDialog } from "@/app/useChannelBrowserDialog";
@@ -82,11 +83,6 @@ import { CommunityRail } from "@/features/sidebar/ui/CommunityRail";
import { useChannelMutes } from "@/features/sidebar/lib/useChannelMutes";
import { useChannelStars } from "@/features/sidebar/lib/useChannelStars";
import { useCommunities } from "@/features/communities/useCommunities";
import {
consumePendingCommunityRestore,
loadCommunityDestination,
saveCommunityDestination,
} from "@/features/communities/communityNavigationStorage";
import { useAddCommunityDialogState } from "@/features/communities/addCommunityPrefill";
import { useApplyTemplate } from "@/features/channel-templates/useApplyTemplate";
import { relayClient } from "@/shared/api/relayClient";
@@ -276,54 +272,15 @@ export function AppShell() {
),
[huddleBackingChannelIds, memberChannels, revealedHuddleChannelIds],
);
const hasRestoredCommunityDestinationRef = React.useRef(false);
React.useEffect(() => {
const activeCommunityId = communitiesHook.activeCommunity?.id;
if (
hasRestoredCommunityDestinationRef.current ||
!channelsQuery.isSuccess ||
channelsQuery.dataUpdatedAt === 0 ||
!activeCommunityId
) {
return;
}
hasRestoredCommunityDestinationRef.current = true;
// Restoration belongs to an explicit community transition. Cold boot and
// reconnect remounts must preserve the route the user explicitly opened.
if (!consumePendingCommunityRestore(activeCommunityId)) {
return;
}
const destination = loadCommunityDestination(activeCommunityId);
if (!destination || destination.kind === "home") {
return;
}
const channelIsAvailable = sidebarChannels.some(
(channel) => channel.id === destination.channelId,
);
if (!channelIsAvailable) {
saveCommunityDestination(activeCommunityId, { kind: "home" });
void goHome({ replace: true });
return;
}
// The normal switch path writes the remembered channel into the hash before
// the target community mounts, so no intermediate Inbox frame is painted.
// Older transition callers may still arrive at neutral Home; repair those.
if (selectedView === "home") {
void goChannel(destination.channelId, { replace: true });
}
}, [
channelsQuery.dataUpdatedAt,
channelsQuery.isSuccess,
communitiesHook.activeCommunity?.id,
useCommunityDestinationRestore({
activeCommunityId: communitiesHook.activeCommunity?.id,
channelsDataUpdatedAt: channelsQuery.dataUpdatedAt,
channelsLoaded: channelsQuery.isSuccess,
goChannel,
goHome,
selectedView,
sidebarChannels,
]);
});
const [terminalContextOverride, setTerminalContextOverride] =
React.useState<TerminalContextOverride | null>(null);
const { activeChannel, terminalContext } = useTerminalContext({

View File

@@ -0,0 +1,83 @@
import * as React from "react";
import type { deriveShellRoute } from "@/app/AppShell.helpers";
import type { useAppNavigation } from "@/app/navigation/useAppNavigation";
import {
consumePendingCommunityRestore,
loadCommunityDestination,
saveCommunityDestination,
} from "@/features/communities/communityNavigationStorage";
import type { Channel } from "@/shared/api/types";
type ShellRoute = ReturnType<typeof deriveShellRoute>;
type AppNavigation = ReturnType<typeof useAppNavigation>;
/**
* Restores the channel a community was last viewed on, once for the first
* successful channel load after an explicit community transition.
*/
export function useCommunityDestinationRestore({
activeCommunityId,
channelsDataUpdatedAt,
channelsLoaded,
goChannel,
goHome,
selectedView,
sidebarChannels,
}: {
activeCommunityId: string | undefined;
channelsDataUpdatedAt: number;
channelsLoaded: boolean;
goChannel: AppNavigation["goChannel"];
goHome: AppNavigation["goHome"];
selectedView: ShellRoute["selectedView"];
sidebarChannels: Channel[];
}) {
const hasRestoredCommunityDestinationRef = React.useRef(false);
React.useEffect(() => {
if (
hasRestoredCommunityDestinationRef.current ||
!channelsLoaded ||
channelsDataUpdatedAt === 0 ||
!activeCommunityId
) {
return;
}
hasRestoredCommunityDestinationRef.current = true;
// Restoration belongs to an explicit community transition. Cold boot and
// reconnect remounts must preserve the route the user explicitly opened.
if (!consumePendingCommunityRestore(activeCommunityId)) {
return;
}
const destination = loadCommunityDestination(activeCommunityId);
if (!destination || destination.kind === "home") {
return;
}
const channelIsAvailable = sidebarChannels.some(
(channel) => channel.id === destination.channelId,
);
if (!channelIsAvailable) {
saveCommunityDestination(activeCommunityId, { kind: "home" });
void goHome({ replace: true });
return;
}
// The normal switch path writes the remembered channel into the hash before
// the target community mounts, so no intermediate Inbox frame is painted.
// Older transition callers may still arrive at neutral Home; repair those.
if (selectedView === "home") {
void goChannel(destination.channelId, { replace: true });
}
}, [
activeCommunityId,
channelsDataUpdatedAt,
channelsLoaded,
goChannel,
goHome,
selectedView,
sidebarChannels,
]);
}