mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-30 21:01:54 +08:00
fix: address follow-up review comments (eval error-class test, elapsed sentinel, cancel diff)
- config-eval test: assert a malformed file throws a loader error, NOT a ConfigFileEvalError, so the documented "Failed to load config file" routing is actually protected (cubic) - progress-content: guard useElapsedSeconds against the startedAt=0 "not started" sentinel so the counter shows 0 instead of ~epoch-since-1970 on first paint (vercel) - config index: clear a cancelled run's captured diff so it can't linger in the API shape or be replayed by the commit route (greptile observation)
This commit is contained in:
parent
8b09fa3479
commit
8837389870
@ -721,7 +721,9 @@ export async function cancelConfigAgentRun(options: {
|
||||
}
|
||||
await tx.configAgentRun.update({
|
||||
where: { id: options.runId },
|
||||
data: { status: "cancelled", finishedAt: new Date(options.nowMs), sandboxId: null, stage: null, baseCommitSha: null },
|
||||
// Clear the captured change too: a cancelled run is abandoned, so its diff/base
|
||||
// must not linger in the API shape or be replayable by the commit route.
|
||||
data: { status: "cancelled", finishedAt: new Date(options.nowMs), sandboxId: null, stage: null, baseCommitSha: null, diff: null },
|
||||
});
|
||||
return { cancelled: true, sandboxId: run.sandboxId ?? undefined, previousStatus: run.status };
|
||||
});
|
||||
|
||||
@ -26,20 +26,27 @@ function stageIndex(stage: AgentStage | null | undefined): number {
|
||||
|
||||
/**
|
||||
* Live "seconds since the run started" counter. The run's `startedAt` is a
|
||||
* wall-clock epoch value; we capture the start→now offset against a fresh
|
||||
* monotonic anchor and then advance on that monotonic clock. The anchor and
|
||||
* offset are recomputed whenever `startedAt` changes (e.g. when a flow renders
|
||||
* the box before its real start timestamp is known), so the counter resets
|
||||
* instead of freezing a stale offset. Recomputing the offset every render —
|
||||
* which is what the old code did — re-added the elapsed time on top of the
|
||||
* monotonic delta and made the timer tick at ~2× speed.
|
||||
* wall-clock epoch value, with `0` as the "not started yet" sentinel — until a
|
||||
* real start time arrives the counter stays at 0 (otherwise it would briefly
|
||||
* read ~epoch-since-1970). For a real `startedAt` we capture the start→now offset
|
||||
* against a fresh monotonic anchor and advance on that monotonic clock. Both are
|
||||
* recomputed whenever `startedAt` changes (e.g. when a flow renders the box
|
||||
* before its real start timestamp is known), so the counter resets instead of
|
||||
* freezing a stale offset. Recomputing the offset every render — which the old
|
||||
* code did — re-added the elapsed time on top of the monotonic delta and made
|
||||
* the timer tick at ~2× speed.
|
||||
*/
|
||||
function elapsedOffsetMs(startedAt: number): number {
|
||||
return startedAt > 0 ? Math.max(0, currentEpochMsFromPerformance() - startedAt) : 0;
|
||||
}
|
||||
|
||||
function useElapsedSeconds(startedAt: number): number {
|
||||
const [elapsedMs, setElapsedMs] = useState(() => Math.max(0, currentEpochMsFromPerformance() - startedAt));
|
||||
const [elapsedMs, setElapsedMs] = useState(() => elapsedOffsetMs(startedAt));
|
||||
useEffect(() => {
|
||||
const anchorPerfMs = performance.now();
|
||||
const offsetMs = Math.max(0, currentEpochMsFromPerformance() - startedAt);
|
||||
const offsetMs = elapsedOffsetMs(startedAt);
|
||||
setElapsedMs(offsetMs);
|
||||
if (startedAt <= 0) return;
|
||||
const anchorPerfMs = performance.now();
|
||||
const t = setInterval(() => setElapsedMs(offsetMs + (performance.now() - anchorPerfMs)), 1000);
|
||||
return () => clearInterval(t);
|
||||
}, [startedAt]);
|
||||
|
||||
@ -130,8 +130,11 @@ import.meta.vitest?.test("evalConfigFileContent rejects missing config import ta
|
||||
`, "/tmp/hexclave-missing-import-config.ts")).toThrow();
|
||||
});
|
||||
|
||||
import.meta.vitest?.test("evalConfigFileContent rejects syntactically invalid content", ({ expect }) => {
|
||||
// jiti surfaces a ParseError (not a ConfigFileEvalError), so callers route this
|
||||
// to "Failed to load config file" rather than "Invalid config".
|
||||
expect(() => evalConfigFileContent("export const config = {", "stack.config.ts")).toThrow();
|
||||
import.meta.vitest?.test("evalConfigFileContent surfaces invalid syntax as a loader error, not ConfigFileEvalError", ({ expect }) => {
|
||||
// A malformed file fails inside jiti's parser, so the thrown error is a loader
|
||||
// error — NOT a ConfigFileEvalError. Callers depend on that distinction to route
|
||||
// it to "Failed to load config file" rather than "Invalid config".
|
||||
const evalInvalid = () => evalConfigFileContent("export const config = {", "stack.config.ts");
|
||||
expect(evalInvalid).toThrow();
|
||||
expect(evalInvalid).not.toThrow(ConfigFileEvalError);
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user