missions: no-store fetch + visible updated-at timestamp on refresh
ci / gates (push) Successful in 5s
ci / frontend (push) Successful in 25s
ci / rust (push) Successful in 4m35s
ci / e2e (push) Skipped
ci / publish (push) Successful in 37s

- api client: cache: no-store so manual Refresh guarantees a fresh
  server response (was potentially hitting stale HTTP cache).
- MissionCanvas: renders "updated HH:MM:SS" next to the refresh
  button; the timestamp bumps on every successful load so the click
  is visibly acknowledged even when nothing else on the page changed.
This commit is contained in:
Omar Sobh
2026-07-21 23:27:51 -07:00
parent 5a1fcba403
commit d42398d3a8
2 changed files with 19 additions and 0 deletions
@@ -118,6 +118,7 @@ export function MissionCanvas({
const [editOpen, setEditOpen] = useState(false); const [editOpen, setEditOpen] = useState(false);
const [deleteBusy, setDeleteBusy] = useState(false); const [deleteBusy, setDeleteBusy] = useState(false);
const [runs, setRuns] = useState<MissionRunSummary[]>([]); const [runs, setRuns] = useState<MissionRunSummary[]>([]);
const [lastLoadedAt, setLastLoadedAt] = useState<Date | null>(null);
const load = useCallback(async () => { const load = useCallback(async () => {
if (!selectedId) { if (!selectedId) {
@@ -133,6 +134,7 @@ export function MissionCanvas({
]); ]);
setMission(m); setMission(m);
setRuns(r.runs); setRuns(r.runs);
setLastLoadedAt(new Date());
} catch (e) { } catch (e) {
setError(e instanceof Error ? e.message : "load failed"); setError(e instanceof Error ? e.message : "load failed");
} finally { } finally {
@@ -424,6 +426,18 @@ export function MissionCanvas({
{refining ? "Refining…" : "Refine"} {refining ? "Refining…" : "Refine"}
</button> </button>
)} )}
{lastLoadedAt && (
<span
style={{
fontSize: 10,
color: "#6a6a72",
fontFamily: mono,
}}
title={lastLoadedAt.toISOString()}
>
updated {lastLoadedAt.toLocaleTimeString()}
</span>
)}
<button <button
type="button" type="button"
onClick={load} onClick={load}
+5
View File
@@ -174,7 +174,12 @@ export interface CreateMissionRequest {
} }
async function api<T>(path: string, init?: RequestInit): Promise<T> { async function api<T>(path: string, init?: RequestInit): Promise<T> {
// Missions endpoints are mutable state (statuses transition every
// few seconds) so we always bypass the browser cache. The old cache
// heuristic could serve a stale response for the manual Refresh
// click, making it look like a no-op.
const r = await fetch(path, { const r = await fetch(path, {
cache: "no-store",
...init, ...init,
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",