fix: use replay fallback for direct demo hashes
This commit is contained in:
@@ -134,6 +134,12 @@ sessionStorage.removeItem("lda.workflowConsole.target");
|
|||||||
location.reload();
|
location.reload();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
When a stateful demo deep link is opened before this browser session has started
|
||||||
|
a live run, the presentation selects the reviewed recording for that beat even
|
||||||
|
if the loopback health probe succeeds. Start from the Scene 8 handoff action to
|
||||||
|
run live; otherwise the typed interrupt, output, and trace links remain useful
|
||||||
|
replay-backed entry points.
|
||||||
|
|
||||||
## Useful Deep Links
|
## Useful Deep Links
|
||||||
|
|
||||||
- Title: `http://127.0.0.1:5173/present#scene/thesis/title`
|
- Title: `http://127.0.0.1:5173/present#scene/thesis/title`
|
||||||
|
|||||||
@@ -231,15 +231,15 @@ describe("PresentationRoute", () => {
|
|||||||
expect(await screen.findByRole("button", { name: /run prepared workflow/i })).toBeInTheDocument();
|
expect(await screen.findByRole("button", { name: /run prepared workflow/i })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not prime replay evidence on a live direct hash", async () => {
|
it("uses replay fallback for a live direct hash before a run starts", async () => {
|
||||||
window.sessionStorage.setItem("lda.workflowConsole.target", "http://127.0.0.1:8765/rpc");
|
window.sessionStorage.setItem("lda.workflowConsole.target", "http://127.0.0.1:8765/rpc");
|
||||||
window.location.hash = "#scene/typed-human-boundary/approval";
|
window.location.hash = "#scene/typed-human-boundary/approval";
|
||||||
const { PresentationRoute } = await import("./PresentationRoute.js");
|
const { PresentationRoute } = await import("./PresentationRoute.js");
|
||||||
render(<PresentationRoute />);
|
render(<PresentationRoute />);
|
||||||
|
|
||||||
expect(await screen.findByText(/Live target is ready/i)).toBeInTheDocument();
|
expect(await screen.findByText(/Replay evidence is active/i)).toBeInTheDocument();
|
||||||
expect(screen.queryByText("Workflow input")).not.toBeInTheDocument();
|
expect(await screen.findByText("Workflow input")).toBeInTheDocument();
|
||||||
expect(screen.queryByRole("button", { name: "Submit" })).not.toBeInTheDocument();
|
expect(await screen.findByRole("button", { name: "Submit" })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("switches to replay evidence when a configured target fails health", async () => {
|
it("switches to replay evidence when a configured target fails health", async () => {
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ export const PresentationRoute = () => {
|
|||||||
mode: presentationTarget.mode === "live" ? "live" : "replay",
|
mode: presentationTarget.mode === "live" ? "live" : "replay",
|
||||||
status: targetStatus,
|
status: targetStatus,
|
||||||
});
|
});
|
||||||
|
const requiredDemoStage = state.location.kind === "main"
|
||||||
|
? requirementForDemoBeat(state.location.sceneId, state.location.beatId).requiredStage
|
||||||
|
: null;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const hash = hashForLocation(state.location);
|
const hash = hashForLocation(state.location);
|
||||||
@@ -96,13 +99,21 @@ export const PresentationRoute = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (demo.state.phase !== "ready") return;
|
if (demo.state.phase !== "ready") return;
|
||||||
// The resolved target owns the initial mode: a healthy loopback target must
|
// A direct evidence hash has no live run context to inspect. Use the
|
||||||
// remain live, while an invalid or unreachable target starts from the
|
// reviewed recording until the handoff action creates a live timeline.
|
||||||
// offline recording. The health hook is the point where an HTTP target
|
const directHashNeedsEvidence = requiredDemoStage !== null;
|
||||||
// becomes known to be unreachable; URL shape alone is not enough.
|
const shouldUseReplay = targetStatus.kind === "failed"
|
||||||
const desiredMode = targetStatus.kind === "failed" ? "replay" : presentationTarget.mode;
|
|| (targetStatus.kind === "ready" && directHashNeedsEvidence);
|
||||||
|
const desiredMode = shouldUseReplay ? "replay" : presentationTarget.mode;
|
||||||
if (demo.state.mode !== desiredMode) demo.setMode(desiredMode);
|
if (demo.state.mode !== desiredMode) demo.setMode(desiredMode);
|
||||||
}, [demo.setMode, demo.state.mode, demo.state.phase, presentationTarget.mode, targetStatus.kind]);
|
}, [
|
||||||
|
demo.setMode,
|
||||||
|
demo.state.mode,
|
||||||
|
demo.state.phase,
|
||||||
|
presentationTarget.mode,
|
||||||
|
requiredDemoStage,
|
||||||
|
targetStatus.kind,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (demo.state.phase === "ready" && demo.state.mode === "replay") {
|
if (demo.state.phase === "ready" && demo.state.mode === "replay") {
|
||||||
|
|||||||
@@ -18,12 +18,25 @@ describe("presentationTargetHealth", () => {
|
|||||||
target: "http://127.0.0.1:8765/rpc",
|
target: "http://127.0.0.1:8765/rpc",
|
||||||
probe: "ready",
|
probe: "ready",
|
||||||
liveActive: false,
|
liveActive: false,
|
||||||
|
replayActive: false,
|
||||||
})).toMatchObject({
|
})).toMatchObject({
|
||||||
kind: "ready",
|
kind: "ready",
|
||||||
label: "Live target ready",
|
label: "Live target ready",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("labels a reviewed replay selected alongside a healthy target", () => {
|
||||||
|
expect(presentationTargetHealth({
|
||||||
|
target: "http://127.0.0.1:8765/rpc",
|
||||||
|
probe: "ready",
|
||||||
|
liveActive: false,
|
||||||
|
replayActive: true,
|
||||||
|
})).toMatchObject({
|
||||||
|
kind: "replay",
|
||||||
|
label: "Replay evidence",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("marks live active only after live timeline starts", () => {
|
it("marks live active only after live timeline starts", () => {
|
||||||
expect(presentationTargetHealth({
|
expect(presentationTargetHealth({
|
||||||
target: "http://127.0.0.1:8765/rpc",
|
target: "http://127.0.0.1:8765/rpc",
|
||||||
@@ -46,4 +59,4 @@ describe("presentationTargetHealth", () => {
|
|||||||
label: "Replay fallback",
|
label: "Replay fallback",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,11 +20,13 @@ export const presentationTargetHealth = ({
|
|||||||
target,
|
target,
|
||||||
probe,
|
probe,
|
||||||
liveActive,
|
liveActive,
|
||||||
|
replayActive = false,
|
||||||
failureReason,
|
failureReason,
|
||||||
}: {
|
}: {
|
||||||
readonly target: string | null;
|
readonly target: string | null;
|
||||||
readonly probe: TargetProbeState;
|
readonly probe: TargetProbeState;
|
||||||
readonly liveActive: boolean;
|
readonly liveActive: boolean;
|
||||||
|
readonly replayActive?: boolean;
|
||||||
readonly failureReason?: string | undefined;
|
readonly failureReason?: string | undefined;
|
||||||
}): PresentationTargetHealth => {
|
}): PresentationTargetHealth => {
|
||||||
if (!target) {
|
if (!target) {
|
||||||
@@ -35,6 +37,14 @@ export const presentationTargetHealth = ({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (replayActive && probe === "ready") {
|
||||||
|
return {
|
||||||
|
kind: "replay",
|
||||||
|
label: "Replay evidence",
|
||||||
|
detail: "reviewed recording",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (liveActive && probe === "ready") {
|
if (liveActive && probe === "ready") {
|
||||||
return {
|
return {
|
||||||
kind: "active",
|
kind: "active",
|
||||||
@@ -68,4 +78,4 @@ export const presentationTargetHealth = ({
|
|||||||
label: "Replay fallback",
|
label: "Replay fallback",
|
||||||
detail: failureReason ?? "live target unreachable",
|
detail: failureReason ?? "live target unreachable",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ export const usePresentationTargetStatus = (
|
|||||||
target: targetState.mode === "live" ? targetState.target : null,
|
target: targetState.mode === "live" ? targetState.target : null,
|
||||||
probe,
|
probe,
|
||||||
liveActive: liveActive(demoState),
|
liveActive: liveActive(demoState),
|
||||||
|
replayActive: demoState.mode === "replay",
|
||||||
failureReason,
|
failureReason,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user