diff --git a/apps/web/e2e/smoke.mjs b/apps/web/e2e/smoke.mjs index 702404c..30073f5 100644 --- a/apps/web/e2e/smoke.mjs +++ b/apps/web/e2e/smoke.mjs @@ -10,7 +10,7 @@ const BASE = `http://localhost:${PORT}`; const API = (process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8000").replace(/\/+$/, ""); const PAGES = [ - ["/", /Strata/], + ["/", /layer by layer/], ["/library/", /Reference populations/], ["/library/population/", /No population was specified|Loading/], ["/examples/", /Example analyses/], diff --git a/apps/web/src/app/analyses/new/page.tsx b/apps/web/src/app/analyses/new/page.tsx index bc0ac09..ef72676 100644 --- a/apps/web/src/app/analyses/new/page.tsx +++ b/apps/web/src/app/analyses/new/page.tsx @@ -53,8 +53,9 @@ function Composer() { const spaces = useResource((s) => api.spaces(s), []); const qpadmOk = health?.capabilities.qpadm ?? false; + const methodParam = sp.get("method"); const [state, setState] = useState({ - method: "admixture_fit", + method: METHODS.some((m) => m.key === methodParam) ? (methodParam as MethodKey) : "admixture_fit", datasetId: sp.get("dataset") ?? "", sample: "", targetPopulation: [], @@ -90,9 +91,12 @@ function Composer() { }, [datasets.data, state.datasetId]); useEffect(() => { if (ds && !ds.sample_names.includes(state.sample)) set({ sample: ds.sample_names[0] ?? "" }); - // Exploratory methods need a dataset whose coordinates live in a known space. - if (ds?.space && !state.space) set({ space: ds.space }); - }, [ds, state.sample, state.space]); + }, [ds, state.sample]); + // A dataset whose coordinates live in a known space (G25 rows) can only be analysed there, + // so choosing it also chooses its space. + useEffect(() => { + if (ds?.space) set({ space: ds.space }); + }, [ds?.id, ds?.space]); useEffect(() => { if (!state.space && spaces.data?.length) set({ space: spaces.data[0]!.id }); }, [spaces.data, state.space]); @@ -122,46 +126,58 @@ function Composer() { -
+
Method -
- {METHODS.map((m) => { - const disabled = m.formal && !qpadmOk; - return ( -
+ ))}
@@ -170,7 +186,7 @@ function Composer() {
set({ targetPopulation: v })} max={1} @@ -241,7 +257,7 @@ function Composer() {
set({ sources })} max={8} @@ -253,18 +269,18 @@ function Composer() { value={state.bootstrap} min={0} max={5000} - hint="Resamples individuals within each source; 0 disables intervals." + hint="0 turns intervals off." onChange={(bootstrap) => set({ bootstrap })} /> set({ seed })} /> ) : null} @@ -274,7 +290,7 @@ function Composer() {
set({ sources })} max={24} @@ -302,7 +318,7 @@ function Composer() {
set({ right })} max={30} @@ -313,8 +329,8 @@ function Composer() { set({ maxSources })} /> ) : null} ) : null} @@ -326,27 +342,61 @@ function Composer() {
-
+
+ +
- -
diff --git a/apps/web/src/app/analysis/page.tsx b/apps/web/src/app/analysis/page.tsx index dd1e311..5c12002 100644 --- a/apps/web/src/app/analysis/page.tsx +++ b/apps/web/src/app/analysis/page.tsx @@ -4,7 +4,7 @@ import Link from "next/link"; import { useSearchParams } from "next/navigation"; import { Suspense, useEffect, useState } from "react"; import { ApiError, api, pollJob, TERMINAL } from "@/lib/api"; -import { analysisLabel, dateTime } from "@/lib/format"; +import { analysisLabel } from "@/lib/format"; import { useResource } from "@/lib/useResource"; import type { Job } from "@/lib/types"; import { AnalysisDocument } from "@/components/results/AnalysisDocument"; @@ -26,8 +26,8 @@ function FailureNote({ message }: { message: string }) { ); return ( -
-

The analysis failed.

+
+

The analysis failed.

{text}

); @@ -73,13 +73,11 @@ function AnalysisInner() {

{STATUS_TEXT[status]}

- Submitted {dateTime(a.created_at)} - {job?.started_at ? `; started ${dateTime(job.started_at)}` : ""}. - {typeof job?.progress === "number" ? ` ${Math.round(job.progress * 100)}% complete.` : ""} This - page updates by itself; you can leave it and return from your workspace. + {typeof job?.progress === "number" ? `${Math.round(job.progress * 100)}% complete. ` : ""}You can + leave this page.

-
-
+
+
{pollError ? : null}
diff --git a/apps/web/src/app/assistant/page.tsx b/apps/web/src/app/assistant/page.tsx index bbdb164..d60d853 100644 --- a/apps/web/src/app/assistant/page.tsx +++ b/apps/web/src/app/assistant/page.tsx @@ -97,7 +97,7 @@ function MethodNote({ part }: { part: ToolPart }) {
{part.n} - {part.name} + {part.name} {part.status === "running" ? "running…" @@ -110,13 +110,13 @@ function MethodNote({ part }: { part: ToolPart }) {

Input

-
+          
             {JSON.stringify(part.input, null, 2)}
           

Result

-
+          
             {part.status === "running" ? "…" : JSON.stringify(part.result ?? part.error, null, 2)}
           
@@ -133,7 +133,7 @@ function AssistantAnswer({ turn, index }: { turn: AssistantTurn; index: number } .filter((x) => x.r !== null); return (
-
+
{turn.parts.map((p, i) => p.kind === "text" ? ( @@ -156,7 +156,7 @@ function AssistantAnswer({ turn, index }: { turn: AssistantTurn; index: number } Result {index}.{t.n}. {" "} - Output of {t.name} + Output of {t.name} {typeof t.result?.analysis_id === "string" ? ( <> ; open the full analysis @@ -305,13 +305,8 @@ export default function AssistantPage() { - It sees population identifiers and derived coordinates, never genotypes. Numbers it - states are checked against tool results; unverified values are flagged. -

- } + lede="Answers come from real analyses, with every step shown." + aside={

It never sees genotypes.

} />
@@ -353,15 +348,19 @@ export default function AssistantPage() { {turns.length === 0 ? (
-

For example

-
    +

    Start from a question like these

    +
      {[ "Which reference populations are closest to Yamnaya Samara in the Strata PCA?", - "Fit Corded Ware as a mixture of Yamnaya and Globular Amphora, with intervals.", + "Which Bronze Age populations in the library come from the steppe?", "What does a qpAdm p-value of 0.2 tell me, and what does it not?", ].map((q) => (
    • -
    • @@ -370,19 +369,19 @@ export default function AssistantPage() {
) : null} -
    +
      {turns.map((t, i) => { if (t.role === "user") return ( -
    1. - Q. -

      {t.content}

      +
    2. + You asked: +

      {t.content}

    3. ); answerNo += 1; return ( -
    4. - A. +
    5. + Answer:
      @@ -395,7 +394,7 @@ export default function AssistantPage() { {error ? : null}
      { e.preventDefault(); void send(); @@ -409,7 +408,7 @@ export default function AssistantPage() {