mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-20 23:32:19 +00:00
fix: prevent false empty states during stack hydration (#1659)
* fix: prevent false empty states during stack hydration Only show confirmed-empty UI after successful stack, status, and container fetches. Distinguish loading and recoverable error states in the sidebar, dashboard, and container health panel. * fix: arbitrate overlapping stack status and container fetches Prevent older dashboard status and same-owner container responses from overwriting newer load state after concurrent poll, invalidation, retry, or lifecycle refresh. * fix: do not let soft status polls starve slow foreground loads Skip soft /stacks/statuses poll and invalidation while a statuses request is already in flight so a deferred foreground hydration can still commit after the ten-second cadence. * fix(stacks): surface recoverable errors for confirmed-empty soft failures Sidebar and dashboard soft (background) refresh failures after a confirmed-empty state silently kept showing the empty/adopt prompt instead of a recoverable error, since only the error message was set without flipping the load status. Also reject malformed non-array /stacks responses instead of coercing them into a confirmed-empty list, and drop malformed per-stack status entries before they reach the dashboard table, which previously crashed the entire app on a null entry. * fix(stacks): close two review-found gaps in the load-failure fix A non-empty stack-statuses map where every entry failed validation was still committed as a confirmed-empty success; it now surfaces as a recoverable error instead, and dropped entries are logged. The sidebar's background-failure helper also checked a stale closure snapshot of the file list, which could wipe a list that had just loaded non-empty in the same attempt if the follow-up statuses fetch then failed; it now tracks the freshest committed list for that decision. Also collapses two refs tracking dashboard status-map emptiness into one.
This commit is contained in:
@@ -141,6 +141,63 @@ export function MobileDashboard({ notifications, headerActions, onNavigateToStac
|
||||
? `avg ${cpuAvg.toFixed(0)}% last 10m · peak ${cpuPeak.toFixed(0)}%${cpuPeakLabel ? ` @ ${cpuPeakLabel}` : ''}`
|
||||
: 'collecting metrics…';
|
||||
|
||||
let stackHealthBody: ReactNode;
|
||||
if (data.stackStatusesLoadStatus === 'idle' || data.stackStatusesLoadStatus === 'loading') {
|
||||
stackHealthBody = (
|
||||
<p className="px-1 py-4 font-mono text-[12px] text-stat-subtitle">Loading stacks…</p>
|
||||
);
|
||||
} else if (data.stackStatusesLoadStatus === 'error') {
|
||||
stackHealthBody = (
|
||||
<div className="flex flex-col items-start gap-2 px-1 py-4">
|
||||
<p className="font-mono text-[12px] text-stat-subtitle">
|
||||
{data.stackStatusesLoadError ?? 'Could not load stack health.'}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={data.retryStackStatuses}
|
||||
className="font-mono text-[12px] text-brand underline-offset-2 hover:underline"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
} else if (visibleRows.length === 0) {
|
||||
stackHealthBody = (
|
||||
<p className="px-1 py-4 font-mono text-[12px] text-stat-subtitle">No stacks yet.</p>
|
||||
);
|
||||
} else {
|
||||
stackHealthBody = (
|
||||
<div className="flex flex-col gap-px">
|
||||
{visibleRows.map(row => (
|
||||
<button
|
||||
key={row.file}
|
||||
type="button"
|
||||
onClick={() => onNavigateToStack(row.file)}
|
||||
className={`flex min-h-11 items-center gap-2.5 rounded-[7px] px-2.5 py-[9px] text-left ${ROW_TINT[row.state]}`}
|
||||
>
|
||||
<StateDot tone={ROW_TONE[row.state]} size={7} glow={row.state !== 'healthy'} />
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block truncate font-mono text-[13px] text-stat-value">{row.name}</span>
|
||||
<span className="block truncate font-mono text-[10px] text-stat-icon">{activeNodeName}</span>
|
||||
</span>
|
||||
<span className="block h-[18px] w-[60px] shrink-0">
|
||||
{row.points.length > 1 ? (
|
||||
<MSparkline values={row.points} height={18} color={ROW_STROKE[row.state]} peak={false} />
|
||||
) : (
|
||||
<span className="block h-full w-full border-b border-dashed border-hairline" />
|
||||
)}
|
||||
</span>
|
||||
<span
|
||||
className={`w-[34px] shrink-0 text-right font-mono tabular-nums text-[12px] ${row.cpu >= 80 ? 'text-warning' : 'text-stat-subtitle'}`}
|
||||
>
|
||||
{`${row.cpu.toFixed(0)}%`}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full min-h-0 flex-col">
|
||||
<Masthead
|
||||
@@ -202,38 +259,7 @@ export function MobileDashboard({ notifications, headerActions, onNavigateToStac
|
||||
<SectionHead right={<button type="button" onClick={onViewAllStacks} className="text-brand">view all →</button>}>
|
||||
stack health
|
||||
</SectionHead>
|
||||
{visibleRows.length === 0 ? (
|
||||
<p className="px-1 py-4 font-mono text-[12px] text-stat-subtitle">No stacks yet.</p>
|
||||
) : (
|
||||
<div className="flex flex-col gap-px">
|
||||
{visibleRows.map(row => (
|
||||
<button
|
||||
key={row.file}
|
||||
type="button"
|
||||
onClick={() => onNavigateToStack(row.file)}
|
||||
className={`flex min-h-11 items-center gap-2.5 rounded-[7px] px-2.5 py-[9px] text-left ${ROW_TINT[row.state]}`}
|
||||
>
|
||||
<StateDot tone={ROW_TONE[row.state]} size={7} glow={row.state !== 'healthy'} />
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block truncate font-mono text-[13px] text-stat-value">{row.name}</span>
|
||||
<span className="block truncate font-mono text-[10px] text-stat-icon">{activeNodeName}</span>
|
||||
</span>
|
||||
<span className="block h-[18px] w-[60px] shrink-0">
|
||||
{row.points.length > 1 ? (
|
||||
<MSparkline values={row.points} height={18} color={ROW_STROKE[row.state]} peak={false} />
|
||||
) : (
|
||||
<span className="block h-full w-full border-b border-dashed border-hairline" />
|
||||
)}
|
||||
</span>
|
||||
<span
|
||||
className={`w-[34px] shrink-0 text-right font-mono tabular-nums text-[12px] ${row.cpu >= 80 ? 'text-warning' : 'text-stat-subtitle'}`}
|
||||
>
|
||||
{`${row.cpu.toFixed(0)}%`}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{stackHealthBody}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user