# Project export: FlowProof

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2026
- Tagline: FlowProof catches usability and accessibility issues before launch by running persona-based AI browser agents across website flows.
- Devpost: https://devpost.com/software/flowproof
- GitHub: https://github.com/pauline-ongchan/persona_probe
- Demo: http://persona-probe-6f7d.vercel.app/
- Video: https://www.youtube.com/embed/EGHpeFkVI2g?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Pauline Ongchan (20 commits)

## Devpost submission (written by the team)

### Inspiration

Developers often do not find critical UI issues until after a product is already live. A form label may seem clear to the team, but confuse an ESL user. A save button may work on desktop, but fall below the fold on mobile. A privacy-sensitive user may abandon a flow because optional tracking or personal-data fields feel required. We built FlowProof to catch those issues before production. Instead of waiting for real users to struggle, FlowProof uses AI-driven personas as pre-production testers. Each persona interacts with the site differently, exposing usability, accessibility, and clarity problems while developers still have time to fix them.

### What it does

FlowProof is a pre-production UI QA tool that uses AI personas to find confusing, inaccessible, or fragile user flows before real users encounter them. It lets developers test a website flow before shipping it. They define a target page, a task, and success criteria. FlowProof then runs that flow across multiple behavioral personas, such as: an impatient user who clicks the first plausible control an ESL user who prefers simple labels a mobile-first user working in a small viewport a privacy-sensitive user who rejects tracking and optional personal data an adversarial user who tries invalid inputs and unusual navigation a power user who uses shortcuts and search For each persona, FlowProof launches a browser session, attempts the task, checks whether the success criteria were met, and records what happened. The result is a developer-facing report showing: which personas passed or failed where the UI became confusing screenshots and action traces failure categories Sentry and Browserbase debugging links ranked risk areas suggested fixes for the demo flow The goal is to make UI problems visible before a page is published, not after users have already been affected.

### How we built it

We built FlowProof as a Next.js app with TypeScript, React, Tailwind CSS, Prisma, and Postgres. Browserbase and Stagehand power the browser automation layer. Each test run creates a set of persona-specific test cases, executes them in browser sessions, and stores the results in Prisma. We capture screenshots, action traces, raw logs, final page state, failure reasons, and success-oracle results. Sentry is integrated so failures can be connected to trace metadata, making debugging easier for developers. For the demo, we built an intentionally tricky account settings page with common pre-launch UI problems: ambiguous email fields, vague validation, privacy friction, optional personal-data fields, and mobile visibility issues. FlowProof catches those issues, then generates a self-healing demo run that shows how the interface could be improved.

### Challenges we ran into

One challenge was making the demo realistic without making it unpredictable. Real browser agents can fail for many reasons, so we created a controlled Demo-Safe mode where the UI traps and persona behaviors are understandable and repeatable. Another challenge was distinguishing actual UI failures from infrastructure failures. If Browserbase cannot reach a site, that is different from a mobile-first persona missing a hidden save button. FlowProof classifies these separately so developers know what is actually actionable. We also had to think carefully about privacy. Debugging UI failures can involve sensitive page content, so FlowProof focuses on structured traces and redacted fix context instead of blindly sharing everything.

### Accomplishments we're proud of

We are proud that FlowProof feels like a real pre-production QA tool, not just a demo script. It has project configuration, seeded personas, browser execution, run history, success oracles, failure classification, screenshots, action traces, risk ranking, and a self-healing demo loop. The most exciting part is seeing a vague UI problem become concrete. Instead of saying “the page might be confusing,” FlowProof can show that the ESL persona selected the billing email field instead of the account email field, or that the mobile-first persona stopped because the save button was not visible. That turns usability feedback into something developers can actually act on before launch.

### What we learned

We learned that accessibility and usability testing should include behavior, not just static checks. A page can technically contain the right elements and still fail if users cannot confidently complete the task. We also learned that traces are much more useful than pass/fail labels. When developers can see the persona rule, the chosen action, the screenshot, and the success check together, the fix becomes much clearer. Most importantly, we learned that AI agents can be valuable not only as end users of websites, but as pre-production testers that help developers improve experiences for real people.

### What's next

FlowProof starts with pre-launch persona testing, but the bigger goal is continuous UX reliability. Right now, FlowProof includes a small set of built-in personas, such as mobile-first, privacy-sensitive, ESL, impatient, adversarial, and power users. Over time, we want teams to create their own personas that reflect their actual customers, accessibility needs, product domain, and support history. We want FlowProof to run automatically on every pull request, staging deploy, and production release, proving that critical flows like onboarding, checkout, account recovery, settings, and forms still work for different types of users. Long term, we imagine FlowProof connecting directly into design systems, CI pipelines, issue trackers, analytics, and code agents. Instead of waiting for users to report confusing UI, teams would get a full feedback loop: detect who is affected, diagnose where they got stuck, suggest or generate a fix, and re-test before the problem reaches production.

## README (from the GitHub repository)

# FlowProof

FlowProof is a pre-production UI QA tool that uses AI personas to find confusing, inaccessible, or fragile user flows before real users encounter them. Developers define a target page, task, and success criteria; FlowProof runs that flow across behavioral personas, records evidence with Browserbase/Stagehand and Sentry context, and can trigger an autofix workflow in the target website repo.

## Local setup

```bash
npm install
cp .env.example .env
npm run prisma:migrate
npm run prisma:generate
npm run prisma:seed
npm run dev
```

FlowProof uses Prisma with Postgres. For local development, use any local or hosted Postgres database and set:

```bash
DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE?sslmode=require"
DIRECT_URL="postgresql://USER:PASSWORD@DIRECT_HOST:5432/DATABASE?sslmode=require"
```

For a fast hosted option, create a Neon or Supabase Postgres database and paste its connection strings into `.env`. With Supabase, use the transaction pooler connection string for `DATABASE_URL`. Use the direct connection string for `DIRECT_URL`; if Vercel cannot reach the direct host, use Supabase's session pooler connection string for `DIRECT_URL`.

## Autofix PR flow

FlowProof does not clone or edit target website repositories. When a failed test case triggers **Create Fix PR**, FlowProof:

1. Collects failure evidence from the TestCase, Run, Persona, Browserbase session metadata, Sentry trace metadata, and action trace.
2. Redacts sensitive fields and stores a `FixAttempt`.
3. Creates a signed fix-context URL.
4. Dispatches a GitHub Actions workflow in the configured target website repo.
5. Serves the signed FixContext to GitHub Actions and marks the `FixAttempt` as `CONTEXT_FETCHED`.
6. Receives the GitHub Actions callback with `PR_OPENED` or `FAILED`, then stores the PR URL or error for the run dashboard.

Required env vars:

```bash
DATABASE_URL=
DIRECT_URL=
GITHUB_TOKEN=
FLOWPROOF_APP_URL=
FIX_CONTEXT_SECRET=
```

`GITHUB_TOKEN` should be server-only and scoped to the target website repo with permission to trigger Actions workflows.

`FLOWPROOF_APP_URL` must be publicly reachable by GitHub Actions. `localhost` will not work for the full workflow unless you use a public tunnel. Existing deployments can continue using `PERSONAPROBE_APP_URL`.

`FIX_CONTEXT_SECRET` signs fix-context URLs. Generate one with:

```bash
openssl rand -hex 32
```

## Target repo workflow

Add a workflow like `examples/flowproof-autofix.yml` to the target website repo at:

```txt
.github/workflows/flowproof-autofix.yml
```

Then configure a Project in FlowProof with:

- target URL
- GitHub owner
- GitHub repo
- base branch
- workflow file name

The example workflow fetches the signed FixContext, commits a placeholder evidence file at `flowproof-fix-evidence/<fixAttemptId>.md`, opens a draft PR, and calls back to FlowProof with the PR URL. This proves the end-to-end handoff before a real coding agent is wired in.

The target repo workflow needs these permissions:

```yaml
permissions:
  contents: write
  pull-requests: write
```

## Autofix demo checklist

1. Deploy FlowProof somewhere GitHub Actions can reach and set `FLOWPROOF_APP_URL` to that public origin.
2. Set `DATABASE_URL`, `DIRECT_URL`, `GITHUB_TOKEN`, and `FIX_CONTEXT_SECRET` in FlowProof.
3. Add `.github/workflows/flowproof-autofix.yml` to the target repo using `examples/flowproof-autofix.yml`.
4. Create or choose a FlowProof Project that points at the target repo and workflow file.
5. Run a probe, open the run detail page, and click **Create Fix PR** on a failed test case.
6. Watch the failed test case move through `WORKFLOW_TRIGGERED`, `CONTEXT_FETCHED`, and `PR_OPENED`. The dashboard will show the draft PR link after the callback lands.

The FixContext endpoint is `GET /api/fix-context/:id?token=<signed-token>`. The callback endpoint is `POST /api/fix-attempts/:id/callback` with `Authorization: Bearer <signed-token>` and a JSON body like:

```json
{
  "status": "PR_OPENED",
  "prUrl": "https://github.com/owner/repo/pull/123",
  "githubWorkflowRunId": "1234567890",
  "evidence": {
    "branch": "flowproof/autofix-fixAttemptId",
    "commitSha": "abc123",
    "workflowRunUrl": "https://github.com/owner/repo/actions/runs/1234567890"
  }
}
```

## Deployment note

Vercel can host the Next.js app. Add `DATABASE_URL` and `DIRECT_URL` in Vercel before deploying for a working app. During `npm run build`, FlowProof runs `prisma migrate deploy` and seeds the default personas when `DATABASE_URL` is configured. If Supabase's direct host is unreachable from Vercel, the build script retries migrations through the Supabase session pooler derived from `DATABASE_URL`.


## Detected evidence (automated analysis)

Indexed codebase: 67 recognized source files, 264 KB.
- CSS (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- PostgreSQL (technology) — claimed on Devpost, not found in the code
- Vercel (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (76 of 76)

```
.env.example
.gitignore
eslint.config.mjs
examples/flowproof-autofix.yml
instrumentation-client.ts
instrumentation.ts
next-env.d.ts
next.config.ts
package.json
postcss.config.mjs
prisma/migrations/20260621000000_init/migration.sql
prisma/migrations/migration_lock.toml
prisma/schema.prisma
prisma/seed.ts
README.md
scripts/prepare-database.mjs
sentry.edge.config.ts
sentry.server.config.ts
src/app/api/fix-attempts/[id]/callback/route.ts
src/app/api/fix-context/[id]/route.ts
src/app/api/personas/route.ts
src/app/api/projects/route.ts
src/app/api/runs/[id]/route.ts
src/app/api/runs/[id]/self-heal/route.ts
src/app/api/runs/[id]/start/route.ts
src/app/api/runs/route.ts
src/app/api/sentry-test/route.ts
src/app/api/test-cases/[id]/fix/route.ts
src/app/demo-app/account-settings-healed/page.tsx
src/app/demo-app/account-settings/page.tsx
src/app/demo-app/page.tsx
src/app/global-error.tsx
src/app/globals.css
src/app/layout.tsx
src/app/page.tsx
src/app/projects/page.tsx
src/app/runs/[id]/page.tsx
src/app/runs/page.tsx
src/components/CreateFixPrButton.tsx
src/components/DatabaseSetupNotice.tsx
src/components/DemoAccountSettings.tsx
src/components/DemoAccountSettingsHealed.tsx
src/components/ProjectSettingsForm.tsx
src/components/RunForm.tsx
src/components/SelfHealRunButton.tsx
src/components/StartRunButton.tsx
src/lib/agent/runStagehandTest.ts
src/lib/fix/collectFixContext.ts
src/lib/fix/fixAttemptCallbackUpdate.ts
src/lib/fix/fixAttemptStatus.ts
src/lib/fix/fixContextResponse.ts
src/lib/fix/fixContextToken.ts
src/lib/fix/publicAppUrl.ts
src/lib/fix/redactFixContext.ts
src/lib/fix/triggerAutofixWorkflow.ts
src/lib/fix/types.ts
src/lib/oracle/evaluateOracle.ts
src/lib/personas/collectPageAuditSummary.ts
src/lib/personas/defaultBehaviorProfiles.ts
src/lib/personas/resolveBehaviorProfile.ts
src/lib/personas/sampleBehaviorPolicy.ts
src/lib/personas/types.ts
src/lib/prisma/client.ts
src/lib/prisma/readiness.ts
src/lib/ranking/scoreTestCase.ts
src/lib/runs/aggregates.ts
src/lib/runs/demoTarget.ts
src/lib/scoring/scorePersonaBehaviorRisk.ts
src/lib/self-healing/generateSelfHealPlan.ts
src/lib/sentry/config.ts
src/lib/sentry/traceUrl.ts
src/lib/sentry/withSentrySpan.ts
tailwind.config.ts
test/fix/autofix-contracts.test.ts
test/runs/demo-target.test.ts
tsconfig.json
```

### Dependencies

- package.json: @browserbasehq/stagehand@^2.4.2, @prisma/client@^6.10.1, @sentry/nextjs@^9.34.0, @types/node@^22.10.2, @types/react@^19.0.2, @types/react-dom@^19.0.2, autoprefixer@^10.4.20, eslint@^9.17.0, eslint-config-next@^15.3.4, lucide-react@^0.468.0, next@^15.3.4, postcss@^8.4.49, prisma@^6.10.1, react@^19.0.0, react-dom@^19.0.0, tailwindcss@^3.4.17, tsx@^4.19.2, typescript@^5.7.2

### Recent commits (newest first)

- Merge pull request #2 from pauline-ongchan/codex/implement-autofix-handoff-loop
- Merge remote-tracking branch 'origin/codex/implement-autofix-handoff-loop' into codex/implement-autofix-handoff-loop
- Fix Sentry trace capture and links
- Customize persona failure messages
- Force sample runs to use FlowProof origin
- Fix sample flow target URL
- Merge branch 'main' into codex/implement-autofix-handoff-loop
- Merge remote autofix handoff updates
- Rebrand dashboard as FlowProof
- fix: normalize PersonaProbe app URLs
- Merge pull request #1 from pauline-ongchan/codex/implement-autofix-handoff-loop
- feat: complete autofix handoff loop
- Wire behavior policies into Stagehand runs
- Add benchmark-aware persona behavior system
- chore: seed target website project
- fix: retry Supabase migrations through session pooler
- chore: support Supabase direct database URL
- fix: prepare Vercel deployment for Postgres
- fix: show database setup state on Vercel
- fix: avoid prerendering database-backed pages

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "name": "flowproof",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "next dev",
    "build": "node scripts/prepare-database.mjs && next build",
    "start": "next start",
    "test": "node --import tsx --test test/**/*.test.ts",
    "postinstall": "prisma generate",
    "lint": "eslint . --max-warnings=0",
    "prisma:generate": "prisma generate",
    "prisma:migrate": "prisma migrate dev",
    "prisma:deploy": "prisma migrate deploy",
    "prisma:seed": "tsx prisma/seed.ts"
  },
  "dependencies": {
    "@browserbasehq/stagehand": "^2.4.2",
    "@prisma/client": "^6.10.1",
    "@sentry/nextjs": "^9.34.0",
    "lucide-react": "^0.468.0",
    "next": "^15.3.4",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  },
  "devDependencies": {
    "@types/node": "^22.10.2",
    "@types/react": "^19.0.2",
    "@types/react-dom": "^19.0.2",
    "autoprefixer": "^10.4.20",
    "eslint": "^9.17.0",
    "eslint-config-next": "^15.3.4",
    "postcss": "^8.4.49",
    "prisma": "^6.10.1",
    "tailwindcss": "^3.4.17",
    "tsx": "^4.19.2",
    "typescript": "^5.7.2"
  },
  "prisma": {
    "seed": "tsx prisma/seed.ts"
  }
}

```

### src/app/layout.tsx

```typescript
import type { Metadata } from "next";
import Image from "next/image";
import Link from "next/link";
import "./globals.css";

export const metadata: Metadata = {
  title: "FlowProof",
  description: "Pre-production UI QA across behavioral personas."
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body className="min-h-screen bg-mist text-ink antialiased">
        <header className="border-b border-slate-200 bg-white">
          <div className="mx-auto flex max-w-7xl items-center justify-between px-5 py-4">
            <Link href="/" className="flex items-center gap-3">
              <Image
                alt="FlowProof logo"
                className="h-10 w-10 rounded object-cover"
                height={40}
                src="/flowproof-logo.png"
                width={40}
              />
              <span>
                <span className="block text-base font-semibold">FlowProof</span>
                <span className="block text-xs text-slate-500">
                  Pre-production UI QA for fragile flows.
                </span>
              </span>
            </Link>
            <nav className="flex items-center gap-2 text-sm">
              <Link className="rounded px-3 py-2 text-slate-700 hover:bg-slate-100" href="/runs">
                Runs
              </Link>
              <Link className="rounded px-3 py-2 text-slate-700 hover:bg-slate-100" href="/projects">
                Projects
              </Link>
            </nav>
          </div>
        </header>
        {children}
      </body>
    </html>
  );
}

```

### src/app/page.tsx

```typescript
import { DatabaseSetupNotice } from "@/components/DatabaseSetupNotice";
import { RunForm } from "@/components/RunForm";
import { prisma } from "@/lib/prisma/client";
import { getDatabaseSetupIssue } from "@/lib/prisma/readiness";

export const dynamic = "force-dynamic";

export default async function HomePage() {
  const databaseData = await loadHomeData();

  if (databaseData.issue) {
    return (
      <main className="mx-auto max-w-7xl px-5 py-8">
        <DatabaseSetupNotice issue={databaseData.issue} />
      </main>
    );
  }

  const { personas, projects } = databaseData;

  return (
    <main className="mx-auto max-w-7xl px-5 py-8">
      <section className="grid gap-8 lg:grid-cols-[0.9fr_1.4fr]">
        <div className="pt-2">
          <p className="text-sm font-medium uppercase tracking-[0.18em] text-moss">Pre-production UI QA</p>
          <h1 className="mt-3 max-w-2xl text-5xl font-semibold leading-tight">
            Find fragile user flows before real users do.
          </h1>
          <p className="mt-5 max-w-xl text-lg leading-8 text-slate-600">
            FlowProof lets developers define a target page, task, and success criteria, then runs the flow across
            behavioral personas to catch confusing, inaccessible, or brittle moments before shipping.
          </p>
          <div className="mt-8 grid grid-cols-3 gap-3 text-center">
            <div className="rounded border border-slate-200 bg-white p-4">
              <span className="block text-2xl font-semibold">{personas.length}</span>
              <span className="text-xs text-slate-500">personas</span>
            </div>
            <div className="rounded border border-slate-200 bg-white p-4">
              <span className="block text-2xl font-semibold">4</span>
              <span className="text-xs text-slate-500">criteria types</span>
            </div>
            <div className="rounded border border-slate-200 bg-white p-4">
              <span className="block text-2xl font-semibold">1</span>
              <span className="text-xs text-slate-500">sample flow</span>
            </div>
          </div>
        </div>
        <div className="rounded border border-slate-200 bg-white p-5 shadow-sm">
          <RunForm
            personas={personas}
            projects={projects.map((project) => ({
              id: project.id,
              name: project.name,
              targetUrl: project.targetUrl,
              githubOwner: project.githubOwner,
              githubRepo: project.githubRepo
            }))}
          />
        </div>
      </section>
    </main>
  );
}

async function loadHomeData() {
  try {
    const [personas, projects] = await Promise.all([
      prisma.persona.findMany({
        orderBy: [{ riskWeight: "desc" }, { name: "asc" }]
      }),
      prisma.project.findMany({
        orderBy: { createdAt: "desc" }
      })
    ]);

    return { personas, projects, issue: null };
  } catch (error) {
    const issue = getDatabaseSetupIssue(error);
    if (issue) return { personas: [], projects: [], issue };
    throw error;
  }
}

```

### src/app/demo-app/page.tsx

```typescript
import { redirect } from "next/navigation";

export default function DemoAppPage() {
  redirect("/demo-app/account-settings");
}

```

### src/app/projects/page.tsx

```typescript
import { DatabaseSetupNotice } from "@/components/DatabaseSetupNotice";
import { ProjectSettingsForm } from "@/components/ProjectSettingsForm";
import { prisma } from "@/lib/prisma/client";
import { getDatabaseSetupIssue } from "@/lib/prisma/readiness";

export const dynamic = "force-dynamic";

export default async function ProjectsPage() {
  const databaseData = await loadProjectsData();

  if (databaseData.issue) {
    return (
      <main className="mx-auto max-w-7xl px-5 py-8">
        <DatabaseSetupNotice issue={databaseData.issue} />
      </main>
    );
  }

  const { projects } = databaseData;

  return (
    <main className="mx-auto max-w-7xl px-5 py-8">
      <div className="mb-6">
        <p className="text-sm font-medium uppercase tracking-[0.18em] text-slate-500">Settings</p>
        <h1 className="mt-2 text-3xl font-semibold">Projects</h1>
        <p className="mt-2 max-w-3xl text-slate-600">
          Store the target website and repo connection FlowProof uses when it prepares fix workflows.
        </p>
      </div>
      <ProjectSettingsForm
        projects={projects.map((project) => ({
          id: project.id,
          name: project.name,
          targetUrl: project.targetUrl,
          githubOwner: project.githubOwner,
          githubRepo: project.githubRepo,
          baseBranch: project.baseBranch,
          autofixWorkflow: project.autofixWorkflow,
          sentryOrg: project.sentryOrg,
          sentryProject: project.sentryProject
        }))}
      />
    </main>
  );
}

async function loadProjectsData() {
  try {
    const projects = await prisma.project.findMany({
      orderBy: { createdAt: "desc" }
    });

    return { projects, issue: null };
  } catch (error) {
    const issue = getDatabaseSetupIssue(error);
    if (issue) return { projects: [], issue };
    throw error;
  }
}

```

### src/app/runs/page.tsx

```typescript
import Link from "next/link";
import { ArrowRight } from "lucide-react";
import { DatabaseSetupNotice } from "@/components/DatabaseSetupNotice";
import { prisma } from "@/lib/prisma/client";
import { getDatabaseSetupIssue } from "@/lib/prisma/readiness";
import { getRunAggregates, formatPercent } from "@/lib/runs/aggregates";

export const dynamic = "force-dynamic";

export default async function RunsPage() {
  const databaseData = await loadRunsData();

  if (databaseData.issue) {
    return (
      <main className="mx-auto max-w-7xl px-5 py-8">
        <DatabaseSetupNotice issue={databaseData.issue} />
      </main>
    );
  }

  const { runs } = databaseData;

  return (
    <main className="mx-auto max-w-7xl px-5 py-8">
      <div className="mb-6 flex items-end justify-between gap-4">
        <div>
          <p className="text-sm font-medium uppercase tracking-[0.18em] text-slate-500">Recent</p>
          <h1 className="mt-2 text-3xl font-semibold">FlowProof runs</h1>
        </div>
        <Link className="rounded bg-ink px-4 py-2 text-sm font-medium text-white" href="/">
          New run
        </Link>
      </div>

      <div className="overflow-hidden rounded border border-slate-200 bg-white">
        <table className="w-full min-w-[760px] text-left text-sm">
          <thead className="bg-slate-50 text-xs uppercase tracking-wide text-slate-500">
            <tr>
              <th className="px-4 py-3">Run</th>
              <th className="px-4 py-3">Mode</th>
              <th className="px-4 py-3">Status</th>
              <th className="px-4 py-3">Target</th>
              <th className="px-4 py-3">Issue rate</th>
              <th className="px-4 py-3">Failures</th>
              <th className="px-4 py-3" />
            </tr>
          </thead>
          <tbody className="divide-y divide-slate-100">
            {runs.map((run) => {
              const aggregates = getRunAggregates(run.testCases);
              return (
                <tr key={run.id}>
                  <td className="px-4 py-3">
                    <span className="block font-medium">{run.name}</span>
                    <span className="block text-xs text-slate-500">{run.createdAt.toLocaleString()}</span>
                  </td>
                  <td className="px-4 py-3">
                    <span
                      className={`inline-flex rounded px-2 py-1 text-xs font-medium ${
                        run.mode === "DEMO_SAFE" ? "bg-moss/15 text-moss" : "bg-ink text-white"
                      }`}
                    >
                      {run.mode === "DEMO_SAFE" ? "Sample flow" : "Target website"}
                    </span>
                  </td>
                  <td className="px-4 py-3">{run.status}</td>
                  <td className="max-w-xs truncate px-4 py-3 text-slate-600">{run.targetUrl}</td>
                  <td className="px-4 py-3">{formatPercent(aggregates.personaFailureRate)}</td>
                  <td className="px-4 py-3">{aggregates.failed + aggregates.errored}</td>
                  <td className="px-4 py-3 text-right">
                    <Link className="inline-flex items-center gap-1 font-medium text-ink" href={`/runs/${run.id}`}>
                      Open
                      <ArrowRight className="h-4 w-4" />
                    </Link>
                  </td>
                </tr>
              );
            })}
            {!runs.length ? (
              <tr>
                <td className="px-4 py-8 text-center text-slate-500" colSpan={7}>
                  No runs yet.
                </td>
              </tr>
            ) : null}
          </tbody>
        </table>
      </div>
    </main>
  );
}

async function loadRunsData() {
  try {
    const runs = await prisma.run.findMany({
      include: {
        testCases: {
          include: { persona: true }
        }
      },
      orderBy: { createdAt: "desc" },
      take: 20
    });

    return { runs, issue: null };
  } catch (error) {
    const issue = getDatabaseSetupIssue(error);
    if (issue) return { runs: [], issue };
    throw error;
  }
}

```

### src/app/demo-app/account-settings/page.tsx

```typescript
import { DemoAccountSettings } from "@/components/DemoAccountSettings";

export default function DemoAccountSettingsPage() {
  return <DemoAccountSettings />;
}

```

### src/app/demo-app/account-settings-healed/page.tsx

```typescript
import { DemoAccountSettingsHealed } from "@/components/DemoAccountSettingsHealed";

export default function DemoAccountSettingsHealedPage() {
  return <DemoAccountSettingsHealed />;
}

```

### src/app/api/personas/route.ts

```typescript
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma/client";

export async function GET() {
  const personas = await prisma.persona.findMany({
    orderBy: [{ riskWeight: "desc" }, { name: "asc" }]
  });

  return NextResponse.json({ personas });
}

```

### src/app/api/sentry-test/route.ts

```typescript
import { NextResponse } from "next/server";
import * as Sentry from "@sentry/nextjs";
import { ensureSentryServer } from "@/lib/sentry/withSentrySpan";

export const runtime = "nodejs";

export async function GET() {
  const hasClient = ensureSentryServer();
  const eventId = Sentry.captureException(new Error("FlowProof Sentry smoke test"));
  const flushed = await Sentry.flush(10_000);

  return NextResponse.json({
    ok: true,
    eventId,
    flushed,
    hasClient,
    hasDsn: Boolean(process.env.SENTRY_DSN),
    message: "Sent a test exception to Sentry. Check Issues for 'FlowProof Sentry smoke test'."
  });
}

```

[57 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]