Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/src/components/layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ export function Layout() {
{/* Main */}
<div className="relative flex-1 flex flex-col overflow-hidden">
<ConnectionBanner status={sseStatus} retryAttempt={sseRetryAttempt} />
<main id="main" className="flex-1 overflow-auto">
<main id="main" className="flex-1 min-h-0 overflow-auto">
<Outlet />
</main>
</div>
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/components/layout/__tests__/Layout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ describe("Layout accessibility", () => {
expect(screen.getByText("Skip to main content")).toHaveAttribute("href", "#main");
expect(screen.getByRole("main")).toHaveAttribute("id", "main");
expect(screen.getByRole("main").parentElement).toHaveClass("relative");
// Regression: <main> is a flex item inside a flex-col/overflow-hidden
// parent. Without min-h-0, a flex item's default min-height:auto lets it
// grow past its allotted space to fit tall content (e.g. a long
// generated report) instead of respecting its own overflow-auto -- the
// excess then gets hard-clipped by the parent's overflow-hidden with no
// scrollbar at all, rather than scrolling into view.
expect(screen.getByRole("main")).toHaveClass("flex-1", "min-h-0", "overflow-auto");
});

it("exposes session actions on keyboard focus and labels the rename input", async () => {
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/i18n/locales/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,8 @@
"runNotFound": "التشغيل غير موجود",
"runNotFoundDesc": "ربما تم حذف دليل التشغيل، أو ربما لا يمتلك المتصفح حق الوصول إلى API. تحقّق من ضبط مفتاح مصادقة API في الإعدادات عند الوصول عن بُعد.",
"goBack": "رجوع",
"showFullPrompt": "عرض الطلب كاملاً",
"showLessPrompt": "عرض أقل",
"chart": "الرسم البياني",
"trades": "التداولات",
"factor": "تحليل العوامل",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,8 @@
"runNotFound": "Durchlauf nicht gefunden",
"runNotFoundDesc": "Das Durchlauf-Verzeichnis wurde möglicherweise entfernt oder Ihrem Browser fehlt die API-Zugriffskonfiguration. Prüfen Sie, ob der API-Authentifizierungsschlüssel in den Einstellungen gesetzt ist, falls Sie remote zugreifen.",
"goBack": "Zurück",
"showFullPrompt": "Vollständigen Prompt anzeigen",
"showLessPrompt": "Weniger anzeigen",
"chart": "Diagramm",
"trades": "Trades",
"factor": "Faktor-Research",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,8 @@
"runNotFound": "Run not found",
"runNotFoundDesc": "The run directory may have been removed, or your browser may not have API access configured. Check that the API authentication key is set in Settings if accessing remotely.",
"goBack": "Go back",
"showFullPrompt": "Show full prompt",
"showLessPrompt": "Show less",
"chart": "Chart",
"trades": "Trades",
"factor": "Factor Research",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,8 @@
"runNotFound": "Ejecución no encontrada",
"runNotFoundDesc": "El directorio de la ejecución puede haberse eliminado, o tu navegador puede no tener el acceso a la API configurado. Verifica que la clave de autenticación de la API esté configurada en Configuración si accedes de forma remota.",
"goBack": "Volver",
"showFullPrompt": "Ver prompt completo",
"showLessPrompt": "Ver menos",
"chart": "Gráfico",
"trades": "Operaciones",
"factor": "Investigación de factores",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/i18n/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,8 @@
"runNotFound": "実行が見つかりません",
"runNotFoundDesc": "実行ディレクトリが削除されたか、ブラウザに API アクセスが設定されていない可能性があります。リモートでアクセスする場合は、設定で API 認証キーが設定されているか確認してください。",
"goBack": "戻る",
"showFullPrompt": "プロンプト全文を表示",
"showLessPrompt": "折りたたむ",
"chart": "チャート",
"trades": "トレード",
"factor": "ファクター分析",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/i18n/locales/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,8 @@
"runNotFound": "실행을 찾을 수 없음",
"runNotFoundDesc": "실행 디렉터리가 제거되었거나 브라우저에 API 액세스가 설정되지 않았을 수 있습니다. 원격으로 액세스하는 경우 설정에서 API 인증 키가 설정되어 있는지 확인하세요.",
"goBack": "뒤로",
"showFullPrompt": "전체 프롬프트 보기",
"showLessPrompt": "간략히 보기",
"chart": "차트",
"trades": "거래",
"factor": "팩터 분석",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,8 @@
"runNotFound": "运行记录未找到",
"runNotFoundDesc": "运行目录可能已被删除,或者浏览器未配置 API 访问。如果远程访问,请检查设置中的 API 认证密钥是否已设置。",
"goBack": "返回",
"showFullPrompt": "显示完整提示词",
"showLessPrompt": "收起",
"chart": "图表",
"trades": "交易",
"factor": "因子研究",
Expand Down
21 changes: 20 additions & 1 deletion frontend/src/pages/RunDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ export function RunDetail() {
const { t } = useTranslation();
const [run, setRun] = useState<RunData | null>(null);
const [code, setCode] = useState<Record<string, string>>({});
// The prompt header sits above the scrollable body (flex-1 min-h-0
// overflow-auto), so an unclamped multi-paragraph prompt eats most of the
// viewport and leaves almost no room for the tabs/dashboard below it.
const [promptExpanded, setPromptExpanded] = useState(false);
const [tab, setTab] = useState<Tab>(requestedInitialTab);
const [loading, setLoading] = useState(true);
const [selectedSymbol, setSelectedSymbol] = useState("");
Expand Down Expand Up @@ -164,6 +168,7 @@ export function RunDetail() {
cancelBulkChartLoadRef.current = true;
setRun(null);
setCode({});
setPromptExpanded(false);
setTab(requestedInitialTab);
setLoading(true);
setSelectedSymbol("");
Expand Down Expand Up @@ -364,7 +369,21 @@ export function RunDetail() {
</div>
{run.elapsed_seconds && <span className="text-xs text-muted-foreground">{run.elapsed_seconds.toFixed(1)}s</span>}
</div>
{run.prompt && <p className="text-sm text-muted-foreground">{run.prompt}</p>}
{run.prompt && (
<div>
<p className={cn("text-sm text-muted-foreground whitespace-pre-wrap", !promptExpanded && "line-clamp-3")}>
{run.prompt}
</p>
<button
type="button"
aria-expanded={promptExpanded}
onClick={() => setPromptExpanded((v) => !v)}
className="mt-1 text-xs text-primary hover:underline"
>
{promptExpanded ? t("runDetail.showLessPrompt") : t("runDetail.showFullPrompt")}
</button>
</div>
)}
{run.metrics && <MetricsCard metrics={run.metrics as Record<string, number>} />}

<div className="flex flex-wrap items-center gap-1">
Expand Down
35 changes: 35 additions & 0 deletions frontend/src/pages/__tests__/RunDetail.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,41 @@ describe("RunDetail page", () => {
expect(screen.queryByText("OLD_CODE")).not.toBeInTheDocument();
});

it("collapses an expanded prompt when navigating to another run", async () => {
const firstPrompt = "First run prompt\n".repeat(8).trim();
const secondPrompt = "Second run prompt\n".repeat(8).trim();
apiMock.getRun.mockImplementation((runId: string) => Promise.resolve({
status: "success",
run_id: runId,
prompt: runId === "first" ? firstPrompt : secondPrompt,
}));
apiMock.getRunCode.mockResolvedValue({});

const router = renderRunDetail("/runs/first");
const expandButton = await screen.findByRole("button", { name: "Show full prompt" });
const firstPromptElement = expandButton.previousElementSibling;
expect(firstPromptElement).toHaveTextContent("First run prompt");
expect(firstPromptElement).toHaveClass("line-clamp-3");
expect(expandButton).toHaveAttribute("aria-expanded", "false");

fireEvent.click(expandButton);
expect(firstPromptElement).not.toHaveClass("line-clamp-3");
expect(screen.getByRole("button", { name: "Show less" })).toHaveAttribute(
"aria-expanded",
"true",
);

await act(async () => {
await router.navigate("/runs/second");
});

const secondExpandButton = await screen.findByRole("button", { name: "Show full prompt" });
const secondPromptElement = secondExpandButton.previousElementSibling;
expect(secondPromptElement).toHaveTextContent("Second run prompt");
expect(secondPromptElement).toHaveClass("line-clamp-3");
expect(secondExpandButton).toHaveAttribute("aria-expanded", "false");
});

it("ignores a chart response that finishes after the route changes", async () => {
const oldChart = deferred<RunData>();
apiMock.getRun.mockImplementation((runId: string, params: Record<string, string>) => {
Expand Down