fix(routing): show hydration shell on stack detail refresh

Avoid flashing HomeDashboard when activeView is editor but selectedFile
is not loaded yet on cold load or hard refresh.
This commit is contained in:
SaelixCode
2026-07-09 08:25:05 -04:00
parent 7517a4f49c
commit 9d2f76da6e
3 changed files with 22 additions and 4 deletions
+2
View File
@@ -843,6 +843,8 @@ export default function EditorLayout() {
onFleetActiveTabChange={setFleetActiveTab}
renderEditor={renderEditor}
stackUpdates={stackUpdates}
urlHydratingStack={urlHydratingStack}
isFileLoading={isFileLoading}
/>
</div>
);
@@ -16,6 +16,7 @@ import type { MuteRuleDraft } from '@/lib/muteRules';
import type { ActiveView } from './hooks/useViewNavigationState';
import type { StackUpdateInfo } from '@/types/imageUpdates';
import type { SecurityTab, FleetTab } from '@/lib/events';
import { isStackEditorDeepLink } from '@/lib/router/readUrlRouteState';
// Paid-tier views are loaded on demand. Their internal PaidGate /
// CapabilityGate wrappers render
@@ -101,6 +102,8 @@ export interface ViewRouterProps {
// not on every parent render that lands on a different view.
renderEditor: () => ReactNode;
stackUpdates: Record<string, StackUpdateInfo>;
urlHydratingStack: string | null;
isFileLoading: boolean;
}
export function ViewRouter({
@@ -131,6 +134,8 @@ export function ViewRouter({
onFleetActiveTabChange,
renderEditor,
stackUpdates,
urlHydratingStack,
isFileLoading,
}: ViewRouterProps): ReactNode {
const { can } = useAuth();
if (activeView === 'settings') {
@@ -175,10 +180,14 @@ export function ViewRouter({
</PaidGate>
);
}
// Fall-through: when activeView === 'editor' but selectedFile is
// null or the stack is still loading, drop through to the default
// HomeDashboard render below. This matches the pre-extraction
// behavior of the conditional ternary chain in EditorLayout.tsx.
// Stack editor deep link: keep a loading shell until selectedFile is set.
// Without this, cold load / refresh shows HomeDashboard while URL hydration runs.
if (activeView === 'editor' && !selectedFile) {
const awaitingStack = urlHydratingStack != null || isFileLoading || isStackEditorDeepLink();
if (awaitingStack) {
return <ViewSkeleton />;
}
}
if (!isLoading && selectedFile && activeView === 'editor') {
return renderEditor();
}
@@ -19,6 +19,13 @@ const DEFAULT: UrlRouteState = {
filterNodeId: null,
};
/** True when the current URL is a stack editor deep link (cold-load bootstrap). */
export function isStackEditorDeepLink(): boolean {
if (typeof window === 'undefined') return false;
const parsed = parsePath(window.location.pathname, window.location.search);
return parsed.view === 'editor' && parsed.stackName != null;
}
/** Read shell navigation fields from the current browser URL (cold-load bootstrap). */
export function readUrlRouteState(): UrlRouteState {
if (typeof window === 'undefined') return DEFAULT;