mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-20 15:22:59 +00:00
fix(stacks): reflect apply progress and clear the anatomy update banner (#1279)
* fix(stacks): reflect apply progress and clear the anatomy update banner The "Update available" banner in the stack Anatomy panel ran its apply in the background but gave no feedback: the apply button stayed idle and the banner never went away once the update finished. Pass an in-flight flag to the panel so the apply button disables and shows progress while the update runs, and re-check the update preview when the apply finishes so the banner clears on success and stays in place when the update did not take effect. * fix(stacks): scope the update re-check to its stack and keep the banner on refresh failure The post-apply re-check of the Anatomy update banner tracked only the in-flight flag, so switching stacks while the first was still updating looked like a completion for the newly selected stack and triggered a stray preview request that could clear that stack's valid banner. Track the stack name alongside the flag and only re-check when the completion belongs to the stack on screen. Also keep the banner when the re-check itself fails (non-OK response or network error) instead of clearing it, so a transient read failure can no longer hide an update that may still be pending. Both failure paths now log for diagnosis.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { parse as parseYaml } from 'yaml';
|
||||
import { GitBranch, Pencil, ExternalLink, Rocket, FolderOpen } from 'lucide-react';
|
||||
import { Button } from './ui/button';
|
||||
@@ -19,6 +19,7 @@ interface StackAnatomyPanelProps {
|
||||
onApplyUpdate: () => void;
|
||||
onOpenFiles?: () => void;
|
||||
canEdit: boolean;
|
||||
applying?: boolean;
|
||||
notifications?: NotificationItem[];
|
||||
}
|
||||
|
||||
@@ -236,6 +237,7 @@ export default function StackAnatomyPanel({
|
||||
onApplyUpdate,
|
||||
onOpenFiles,
|
||||
canEdit,
|
||||
applying = false,
|
||||
notifications,
|
||||
}: StackAnatomyPanelProps) {
|
||||
const anatomy = useMemo(() => parseAnatomy(content), [content]);
|
||||
@@ -295,6 +297,38 @@ export default function StackAnatomyPanel({
|
||||
return () => { cancelled = true; };
|
||||
}, [stackName]);
|
||||
|
||||
// When an apply for the current stack finishes (applying true -> false on the same
|
||||
// stackName), re-check the preview: a landed update clears has_update so the banner
|
||||
// unmounts; if it did not land, or the re-check itself fails, the banner stays.
|
||||
// Tracking stackName alongside applying avoids treating a stack switch made while the
|
||||
// first stack is still applying as a completion for the newly selected stack.
|
||||
const prevApplyRef = useRef({ applying, stackName });
|
||||
useEffect(() => {
|
||||
const prev = prevApplyRef.current;
|
||||
const finishedApplying = prev.applying && !applying && prev.stackName === stackName;
|
||||
prevApplyRef.current = { applying, stackName };
|
||||
if (!finishedApplying) return;
|
||||
let cancelled = false;
|
||||
const run = async () => {
|
||||
try {
|
||||
const res = await apiFetch(`/stacks/${stackName}/update-preview`);
|
||||
if (cancelled) return;
|
||||
if (!res.ok) {
|
||||
// Re-check failed: keep the banner already shown rather than hiding a
|
||||
// possibly-still-pending update. The apply action reports its own outcome.
|
||||
console.error(`[StackAnatomyPanel] update-preview re-check returned ${res.status}; keeping the existing banner`);
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
if (!cancelled) setUpdatePreview(data);
|
||||
} catch (err) {
|
||||
console.error('[StackAnatomyPanel] update-preview re-check failed:', err);
|
||||
}
|
||||
};
|
||||
void run();
|
||||
return () => { cancelled = true; };
|
||||
}, [applying, stackName]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
const run = async () => {
|
||||
@@ -494,7 +528,7 @@ export default function StackAnatomyPanel({
|
||||
</>
|
||||
)}
|
||||
{hasUpdate && updatePreview && (
|
||||
<div className={cn('mt-3 mb-3 rounded-lg border p-3', bannerTone)}>
|
||||
<div data-testid="update-available-banner" className={cn('mt-3 mb-3 rounded-lg border p-3', bannerTone)}>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="min-w-0">
|
||||
<div className="font-mono text-[11px] uppercase tracking-wide">
|
||||
@@ -524,11 +558,12 @@ export default function StackAnatomyPanel({
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={applying}
|
||||
className={cn('shrink-0 h-7 gap-1', applyBtnTone)}
|
||||
onClick={onApplyUpdate}
|
||||
>
|
||||
<Rocket className="h-3 w-3" strokeWidth={1.5} />
|
||||
apply
|
||||
<Rocket className={cn('h-3 w-3', applying && 'animate-pulse')} strokeWidth={1.5} />
|
||||
{applying ? 'applying...' : 'apply'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user