Skip to content
Open
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 app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@context-labs/halo-app",
"version": "0.1.16",
"version": "0.1.17",
"type": "module",
"private": true,
"scripts": {
Expand Down
119 changes: 54 additions & 65 deletions app/src/mainview/onboarding/OnboardingPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ReactNode } from "react";
import { useCallback, useState } from "react";
import { useCallback, useRef, useState } from "react";
import { useNavigate } from "@tanstack/react-router";
import { ArrowLeft, ArrowRight } from "lucide-react";

Expand All @@ -13,13 +13,14 @@ import { FileImportDialog } from "~/tracing/fileimport/FileImportDialog";
import { LangfuseImportDialog } from "~/tracing/langfuse/LangfuseImportDialog";
import { PhoenixImportDialog } from "~/tracing/phoenix/PhoenixImportDialog";

type OnboardingStep = "welcome" | "video" | "import";
type OnboardingStep = "welcome" | "import";

const DEFAULT_INGEST_URL = "http://127.0.0.1:8799/v1/traces";
const HALO_CHAT_TEXT_CLASS_NAME =
"text-[0.9375rem] leading-[1.95] tracking-[-0.011em] text-foreground/85 antialiased [text-wrap:pretty]";

const STEPS: Array<{ id: OnboardingStep; label: string }> = [
{ id: "welcome", label: "Welcome" },
{ id: "video", label: "Video" },
{ id: "import", label: "Import" },
];

Expand Down Expand Up @@ -56,24 +57,14 @@ export function OnboardingPage() {
return (
<main className="min-h-screen overflow-auto bg-background text-foreground">
<div className="mx-auto flex min-h-screen w-full max-w-3xl flex-col px-8 py-10">
<header className="flex items-center justify-between">
<header className="flex items-center">
<StepRail onStepChange={setStep} step={step} />
<Button
onClick={() => void finish()}
size="sm"
variant="secondary"
>
Skip Onboarding
</Button>
</header>

<div className="flex flex-1 flex-col pt-20 pb-10">
{step === "welcome" ? <WelcomeStep onContinue={goNext} /> : null}
{step === "video" ? (
<VideoStep onBack={() => setStep("welcome")} onContinue={goNext} />
) : null}
{step === "import" ? (
<ImportStep onBack={() => setStep("video")} onFinish={() => void finish()} />
<ImportStep onBack={() => setStep("welcome")} onFinish={finish} />
) : null}
</div>
</div>
Expand All @@ -84,54 +75,39 @@ export function OnboardingPage() {
function WelcomeStep({ onContinue }: { onContinue: () => void }) {
return (
<OnboardingStepLayout
description="HALO helps you understand what your AI agents are doing by collecting traces, sessions, and telemetry in one local workspace."
primaryAction={
<Button onClick={onContinue} size="sm" variant="secondary">
Next
Get Started
<ArrowRight className="ml-2 h-3.5 w-3.5" />
</Button>
}
title="Welcome to HALO"
>
<div className="max-w-2xl space-y-4 text-base leading-7 text-muted-foreground">
<p>
Watch the short walkthrough, then bring in your first traces when you
are ready.
<div className="max-w-2xl space-y-4">
<p className={HALO_CHAT_TEXT_CLASS_NAME}>
Point HALO at OpenTelemetry-compatible traces from your agent runs. It
reads across executions, looks for repeated harness-level failure modes,
and writes up the issues that are worth fixing.
</p>
<p className={HALO_CHAT_TEXT_CLASS_NAME}>
The method is a loop: collect traces, run HALO, use the report
as input to a coding agent, ship the harness changes, then collect more
traces and do it again.
</p>
<p className={HALO_CHAT_TEXT_CLASS_NAME}>
For the best results in a production environment, it is best to run a
HALO loop every 24 hours to review the previous day&apos;s executions. For a
hosted version of HALO, please see{" "}
<a
className="text-link underline-offset-2 hover:underline"
href="https://inference.net"
rel="noreferrer"
target="_blank"
>
inference.net
</a>
.
</p>
</div>
</OnboardingStepLayout>
);
}

function VideoStep({
onBack,
onContinue,
}: {
onBack: () => void;
onContinue: () => void;
}) {
return (
<OnboardingStepLayout
description="Learn how to inspect agent traces, follow sessions, and bring data into HALO."
onBack={onBack}
primaryAction={
<Button onClick={onContinue} size="sm" variant="secondary">
Next
<ArrowRight className="ml-2 h-3.5 w-3.5" />
</Button>
}
title="Watch the HALO walkthrough"
>
<div className="overflow-hidden rounded-xl border border-border/70 bg-card">
<div className="relative aspect-video w-full">
<iframe
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen
className="absolute inset-0 h-full w-full"
src="https://www.youtube.com/embed/SGKrnYSttBI"
title="How to use HALO"
/>
</div>
</div>
</OnboardingStepLayout>
);
Expand All @@ -142,13 +118,14 @@ function ImportStep({
onFinish,
}: {
onBack: () => void;
onFinish: () => void;
onFinish: () => Promise<void>;
}) {
const [langfuseDialogOpen, setLangfuseDialogOpen] = useState(false);
const [phoenixDialogOpen, setPhoenixDialogOpen] = useState(false);
const [fileDialogOpen, setFileDialogOpen] = useState(false);
const [demoDialogOpen, setDemoDialogOpen] = useState(false);
const [localAgentSetupOpen, setLocalAgentSetupOpen] = useState(false);
const completedImportRef = useRef(false);
const utils = trpc.useUtils();
const infoQuery = trpc.telemetry.info.useQuery();

Expand All @@ -167,6 +144,16 @@ function ImportStep({
const handleReadDocumentation = useCallback(() => {
void openExternalUrl(APP_DOCS_URL);
}, []);
const handleImported = useCallback(async () => {
if (completedImportRef.current) return;
completedImportRef.current = true;
setLangfuseDialogOpen(false);
setPhoenixDialogOpen(false);
setFileDialogOpen(false);
setDemoDialogOpen(false);
refreshTelemetry();
await onFinish();
}, [onFinish, refreshTelemetry]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import guard blocks retry finish

Low Severity

handleImported sets completedImportRef to true before await onFinish(). If onboarding.complete or navigation fails after a successful import, later import completions skip handleImported and no longer auto-complete onboarding, even though dialogs were already closed on the first attempt.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cd27a4c. Configure here.


trpc.live.workspace.useSubscription(undefined, {
onData() {
Expand All @@ -179,7 +166,7 @@ function ImportStep({
description="Import existing data from a provider, upload a file, or connect a live agent."
onBack={onBack}
primaryAction={
<Button onClick={onFinish} size="sm" variant="secondary">
<Button onClick={() => void onFinish()} size="sm" variant="secondary">
Dashboard
<ArrowRight className="ml-2 h-3.5 w-3.5" />
</Button>
Expand All @@ -198,22 +185,22 @@ function ImportStep({
/>

<LangfuseImportDialog
onImported={refreshTelemetry}
onImported={() => void handleImported()}
onOpenChange={setLangfuseDialogOpen}
open={langfuseDialogOpen}
/>
<PhoenixImportDialog
onImported={refreshTelemetry}
onImported={() => void handleImported()}
onOpenChange={setPhoenixDialogOpen}
open={phoenixDialogOpen}
/>
<FileImportDialog
onImported={refreshTelemetry}
onImported={() => void handleImported()}
onOpenChange={setFileDialogOpen}
open={fileDialogOpen}
/>
<DemoTracesImportDialog
onImported={refreshTelemetry}
onImported={() => void handleImported()}
onOpenChange={setDemoDialogOpen}
open={demoDialogOpen}
/>
Expand All @@ -235,7 +222,7 @@ function OnboardingStepLayout({
title,
}: {
children: ReactNode;
description: string;
description?: string;
onBack?: () => void;
primaryAction: ReactNode;
title: string;
Expand All @@ -244,9 +231,11 @@ function OnboardingStepLayout({
<section className="flex min-h-0 w-full flex-1 flex-col">
<div className="mb-8">
<h1 className="text-3xl font-medium tracking-normal">{title}</h1>
<p className="mt-3 max-w-2xl text-base text-muted-foreground">
{description}
</p>
{description ? (
<p className="mt-3 max-w-2xl text-base text-muted-foreground">
{description}
</p>
) : null}
</div>

<div>{children}</div>
Expand Down
35 changes: 16 additions & 19 deletions app/src/mainview/tracing/DemoTracesImportDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
DownloadCloud,
ExternalLink,
Loader2,
Play,
RotateCcw,
XCircle,
} from "lucide-react";
Expand Down Expand Up @@ -166,8 +165,7 @@ export function DemoTracesImportDialog({
Close
</Button>
<Button disabled={loadDemo.isPending} onClick={beginImport}>
<Play className="mr-2 h-4 w-4" />
Load demo traces
Load Traces
</Button>
</>
) : null}
Expand All @@ -189,23 +187,22 @@ export function DemoTracesImportDialog({
Cancel import
</Button>
) : (
<>
<Button onClick={() => onOpenChange(false)} variant="ghost">
Close
</Button>
<Button
disabled={loadDemo.isPending}
onClick={beginImport}
variant={jobFailed ? "default" : "secondary"}
>
{jobFailed ? (
jobFailed ? (
<>
<Button onClick={() => onOpenChange(false)} variant="ghost">
Close
</Button>
<Button
disabled={loadDemo.isPending}
onClick={beginImport}
>
<RotateCcw className="mr-2 h-4 w-4" />
) : (
<Database className="mr-2 h-4 w-4" />
)}
{jobFailed ? "Retry demo import" : "Load again"}
</Button>
</>
Retry demo import
</Button>
</>
) : (
<Button onClick={() => onOpenChange(false)}>Close</Button>
)
)
) : null}
</div>
Expand Down
25 changes: 9 additions & 16 deletions app/src/mainview/tracing/fileimport/FileImportDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -265,20 +265,11 @@ export function FileImportDialog({
Cancel import
</Button>
) : (
<>
<Button onClick={() => onOpenChange(false)} variant="ghost">
Close
</Button>
<Button
onClick={() => {
setActiveJobId(null);
setStep("choose");
}}
variant={jobFailed ? "secondary" : "default"}
>
Start another import
</Button>
{jobFailed && latestJob ? (
jobFailed && latestJob ? (
<>
<Button onClick={() => onOpenChange(false)} variant="ghost">
Close
</Button>
<Button
disabled={startImport.isPending}
onClick={() =>
Expand All @@ -288,8 +279,10 @@ export function FileImportDialog({
<RotateCcw className="mr-2 h-4 w-4" />
Retry import
</Button>
) : null}
</>
</>
) : (
<Button onClick={() => onOpenChange(false)}>Close</Button>
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Success import loses repeat action

Medium Severity

After a successful import, the dialog footer now only shows Close. The previous Start another import / Load again controls that reset the wizard were removed. Those dialogs are shared by ImportDataRoutePage and TraceMonitorPage, so users must close and reopen the dialog to run another import.

Additional Locations (3)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cd27a4c. Configure here.

)
) : null}
</div>
Expand Down
25 changes: 9 additions & 16 deletions app/src/mainview/tracing/langfuse/LangfuseImportDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -297,20 +297,11 @@ export function LangfuseImportDialog({
Cancel import
</Button>
) : (
<>
<Button onClick={() => onOpenChange(false)} variant="ghost">
Close
</Button>
<Button
onClick={() => {
setActiveJobId(null);
setStep("select");
}}
variant={jobFailed ? "secondary" : "default"}
>
Start another import
</Button>
{jobFailed && latestJob ? (
jobFailed && latestJob ? (
<>
<Button onClick={() => onOpenChange(false)} variant="ghost">
Close
</Button>
<Button
disabled={startImport.isPending}
onClick={() =>
Expand All @@ -323,8 +314,10 @@ export function LangfuseImportDialog({
<RotateCcw className="mr-2 h-4 w-4" />
Retry import
</Button>
) : null}
</>
</>
) : (
<Button onClick={() => onOpenChange(false)}>Close</Button>
)
)
) : null}
</div>
Expand Down
Loading