Skip to content

Commit e08dfcd

Browse files
d-csclaude
andcommitted
fix(webapp): span detail panel renders for buffered runs
The right-side span detail panel on the run-detail page calls into `SpanPresenter.call` via the `resources.../runs.$runParam.spans.$spanParam` resource route. The presenter's first step is a Prisma `findFirst` for the parent run; for a buffered run that returns null and the whole `call()` returns undefined. The route loader interprets undefined as "event not found" and redirects with that toast. The browser hops back to the run-detail URL which the previous F2 fix redirects to the `?span=` form, which hits this resource route again, which returns undefined, which redirects... the visible symptom is a permanent loading spinner on the detail panel plus an "Event not found." toast pop on every poll. Add a buffered fallback inside `SpanPresenter.call`: when the `parentRun` Prisma lookup misses, resolve the env from `(projectId, envSlug)`, call `findRunByIdWithMollifierFallback({runId, envId, orgId})`, and synthesise the SpanRun with `buildSyntheticSpanRun` (reusing the existing helper). The resource route now passes `envSlug` through so the presenter can find the env without an extra round trip. `envSlug` is optional on `call()`'s arg type to preserve every existing caller — only the dashboard-side path needs the buffer fallback; other call sites (cached-span lookups, etc.) keep their existing PG-only behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 199f370 commit e08dfcd

2 files changed

Lines changed: 35 additions & 1 deletion

File tree

  • apps/webapp/app

apps/webapp/app/presenters/v3/SpanPresenter.server.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import {
3232
extractAIEmbedData,
3333
} from "~/components/runs/v3/ai";
3434
import { getEventRepositoryForStore } from "~/v3/eventRepository/index.server";
35+
import { findRunByIdWithMollifierFallback } from "~/v3/mollifier/readFallback.server";
36+
import { buildSyntheticSpanRun } from "~/v3/mollifier/syntheticSpanRun.server";
3537

3638
export type PromptSpanData = {
3739
slug: string;
@@ -84,12 +86,18 @@ export class SpanPresenter extends BasePresenter {
8486
public async call({
8587
userId,
8688
projectSlug,
89+
envSlug,
8790
spanId,
8891
runFriendlyId,
8992
linkedRunId,
9093
}: {
9194
userId: string;
9295
projectSlug: string;
96+
// Optional for backwards compatibility, required for the mollifier
97+
// buffer fallback when the parent run isn't yet in PG — we need to
98+
// resolve the env id to satisfy `findRunByIdWithMollifierFallback`'s
99+
// auth check.
100+
envSlug?: string;
93101
spanId: string;
94102
runFriendlyId: string;
95103
linkedRunId?: string;
@@ -127,7 +135,32 @@ export class SpanPresenter extends BasePresenter {
127135
});
128136

129137
if (!parentRun) {
130-
return;
138+
// PG miss → fall back to the mollifier buffer. Without this the
139+
// right-side span detail panel on the run-detail page never
140+
// resolves for buffered runs: `call()` returns undefined, the
141+
// resource route redirects with an "Event not found" toast, the
142+
// run-detail page reloads, the toast fires again — a perpetual
143+
// spin until the drainer materialises the row. Synthesise a
144+
// SpanRun straight from the buffer snapshot, reusing
145+
// `buildSyntheticSpanRun` (the same helper the run-detail
146+
// loader's header fallback already uses).
147+
if (!envSlug) return;
148+
const envRow = await this._replica.runtimeEnvironment.findFirst({
149+
where: { project: { id: project.id }, slug: envSlug },
150+
select: { id: true, slug: true, type: true, organizationId: true },
151+
});
152+
if (!envRow) return;
153+
const buffered = await findRunByIdWithMollifierFallback({
154+
runId: runFriendlyId,
155+
environmentId: envRow.id,
156+
organizationId: envRow.organizationId,
157+
});
158+
if (!buffered) return;
159+
const synth = await buildSyntheticSpanRun({
160+
run: buffered,
161+
environment: { id: envRow.id, slug: envRow.slug, type: envRow.type },
162+
});
163+
return { type: "run" as const, run: synth };
131164
}
132165

133166
const { traceId } = parentRun;

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.spans.$spanParam/route.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
120120
try {
121121
const result = await presenter.call({
122122
projectSlug: projectParam,
123+
envSlug: envParam,
123124
spanId: spanParam,
124125
runFriendlyId: runParam,
125126
userId,

0 commit comments

Comments
 (0)