mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-09-01 21:27:58 +00:00
fix: reconcile sticky update indicators with Anatomy preview (#1698)
* fix: reconcile sticky update indicators with Anatomy preview Sidebar, Updates filter, and Fleet treated retained partial/failed scanner has_update as confirmed. Keep raw state for retention/notifications, project confirmed-only to APIs, show distinct incomplete indicators, and clear sticky rows only after an authoritative-negative preview. Closes #1685 * test: align sidebar truncate E2E with failed-over-retained precedence Purple update indicators are confirmed-only; hasUpdate with a failed check correctly shows the failed trailing icon. * fix: clear confirmed update rows on authoritative-negative preview Address audit SF-1/SF-2/SF-3: observation-watermark clears for older ok+has_update rows (DB + memory gens), Fleet checkability parity with backend not_checkable, and Updates chip confirmed-only regressions. * fix: tombstone equal-generation writers on preview clear Advance the per-stack write generation when clearing at the observation watermark so a scanner reserved before preview cannot recreate the row after an authoritative-negative reconcile. * fix: clear sticky updates with digest and tag preview parity Share detection across scanner and preview, keep GET read-only with POST reconcile, gate Apply to digest and rebuild updates, and invalidate the hub fleet cache on clear. * test: set digestUpdate on auto-update checkImage mocks Scheduler and execute routes now gate Compose on digest drift; fixtures that expect an apply need digestUpdate so they exercise the update path. * fix: clear unused lint errors on sticky update branch Drop unused partial helper and fleet invalidate import; keep the CacheService inflight self-ref as let with an eslint exception so tsc stays green. * fix: use inflight holder for CacheService prefer-const Keep generation-aware ownership without a let self-reference that fights ESLint and tsc.
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
import { apiFetch } from './api';
|
||||
|
||||
export interface FetchUpdatePreviewOptions {
|
||||
/** When true (default), POST to reconcile sticky state. Falls back to GET on 404/405. */
|
||||
reconcile?: boolean;
|
||||
/** Optional node-scoped fetch (Fleet). When omitted, uses hub apiFetch. */
|
||||
fetchImpl?: (path: string, init?: RequestInit) => Promise<Response>;
|
||||
}
|
||||
|
||||
export interface FetchUpdatePreviewResult {
|
||||
ok: boolean;
|
||||
status: number;
|
||||
preview: unknown | null;
|
||||
/** True only when POST succeeded and the body reported reconciled. */
|
||||
reconciled: boolean;
|
||||
/** True when POST was unsupported and GET was used instead. */
|
||||
usedGetFallback: boolean;
|
||||
}
|
||||
|
||||
async function asPreviewResult(
|
||||
res: Response,
|
||||
opts: { usedGetFallback: boolean; readReconciledFlag: boolean },
|
||||
): Promise<FetchUpdatePreviewResult> {
|
||||
if (!res.ok) {
|
||||
return {
|
||||
ok: false,
|
||||
status: res.status,
|
||||
preview: null,
|
||||
reconciled: false,
|
||||
usedGetFallback: opts.usedGetFallback,
|
||||
};
|
||||
}
|
||||
const body = await res.json() as { reconciled?: unknown };
|
||||
return {
|
||||
ok: true,
|
||||
status: res.status,
|
||||
preview: body,
|
||||
reconciled: opts.readReconciledFlag && body.reconciled === true,
|
||||
usedGetFallback: opts.usedGetFallback,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefer POST /stacks/:name/update-preview (reconcile). On 404/405 only, fall
|
||||
* back to GET and treat as not reconciled. Ordinary 5xx / network errors do
|
||||
* not fall back.
|
||||
*/
|
||||
export async function fetchUpdatePreview(
|
||||
stackName: string,
|
||||
options: FetchUpdatePreviewOptions = {},
|
||||
): Promise<FetchUpdatePreviewResult> {
|
||||
const reconcile = options.reconcile !== false;
|
||||
const fetchImpl = options.fetchImpl ?? ((path: string, init?: RequestInit) => apiFetch(path, init));
|
||||
const path = `/stacks/${encodeURIComponent(stackName)}/update-preview`;
|
||||
|
||||
if (!reconcile) {
|
||||
const res = await fetchImpl(path, { method: 'GET' });
|
||||
return asPreviewResult(res, { usedGetFallback: false, readReconciledFlag: false });
|
||||
}
|
||||
|
||||
const postRes = await fetchImpl(path, { method: 'POST' });
|
||||
if (postRes.status === 404 || postRes.status === 405) {
|
||||
const getRes = await fetchImpl(path, { method: 'GET' });
|
||||
return asPreviewResult(getRes, { usedGetFallback: true, readReconciledFlag: false });
|
||||
}
|
||||
|
||||
return asPreviewResult(postRes, { usedGetFallback: false, readReconciledFlag: true });
|
||||
}
|
||||
Reference in New Issue
Block a user