<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Harbor Viewer</title>
    <!-- Vendored dependencies for offline/air-gapped support -->
    <script src="/static/vue.global.prod.js"></script>
    <link rel="stylesheet" href="/static/tailwind.min.css" />
    <script src="/static/marked.min.js"></script>
    <script src="/static/purify.min.js"></script>
    <link rel="stylesheet" href="/static/highlight-github.min.css" />
    <script src="/static/highlight.min.js"></script>
    <script src="/static/highlight-json.min.js"></script>
    <style>
      [v-cloak] {
        display: none;
      }
      @keyframes pulse-dot {
        0%,
        100% {
          opacity: 1;
        }
        50% {
          opacity: 0.5;
        }
      }
      .animate-pulse-dot {
        animation: pulse-dot 1.5s ease-in-out infinite;
      }
      .prose pre {
        margin: 0;
        background: transparent;
      }
      .prose code {
        background: transparent;
        padding: 0;
      }
    </style>
  </head>
  <body class="bg-slate-50 text-slate-900 min-h-screen">
    <div id="app" v-cloak>
      <!-- Header -->
      <header
        class="bg-white border-b border-slate-200 sticky top-0 z-20 shadow-sm"
      >
        <div
          class="max-w-7xl mx-auto px-4 py-3 flex items-center justify-between"
        >
          <div class="flex items-center gap-4">
            <button
              v-if="currentView !== 'jobs'"
              @click="goBack"
              class="text-slate-500 hover:text-slate-700"
            >
              <svg
                class="w-5 h-5"
                fill="none"
                stroke="currentColor"
                viewBox="0 0 24 24"
              >
                <path
                  stroke-linecap="round"
                  stroke-linejoin="round"
                  stroke-width="2"
                  d="M15 19l-7-7 7-7"
                />
              </svg>
            </button>
            <h1
              class="text-xl font-semibold text-slate-800 cursor-pointer"
              @click="goToJobs"
            >
              Harbor Viewer
            </h1>
            <!-- Breadcrumb -->
            <nav
              v-if="currentView !== 'jobs'"
              class="flex items-center gap-2 text-sm text-slate-500"
            >
              <span
                class="cursor-pointer hover:text-slate-700"
                @click="goToJobs"
                >Jobs</span
              >
              <span>/</span>
              <span
                v-if="selectedJobId"
                class="cursor-pointer hover:text-slate-700"
                @click="goToTrials(selectedJobId)"
                >{{ selectedJobId }}</span
              >
              <template v-if="selectedTrialId">
                <span>/</span>
                <span class="text-slate-700"
                  >{{ trajectory?.task_name || selectedTrialId }}</span
                >
              </template>
            </nav>
          </div>
          <div class="flex items-center gap-3">
            <div class="flex items-center gap-2 text-sm text-slate-500">
              <span>Auto-refresh</span>
              <button
                type="button"
                role="switch"
                :aria-checked="autoRefresh"
                @click="autoRefresh = !autoRefresh"
                :class="[
                'relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2',
                autoRefresh ? 'bg-indigo-600' : 'bg-slate-300'
              ]"
              >
                <span class="sr-only">Toggle auto-refresh</span>
                <span
                  :class="[
                  'inline-block h-5 w-5 transform rounded-full bg-white shadow transition',
                  autoRefresh ? 'translate-x-5' : 'translate-x-1'
                ]"
                ></span>
              </button>
            </div>
            <button
              @click="refresh"
              class="text-slate-500 hover:text-slate-700"
              :class="{'animate-spin': loading}"
            >
              <svg
                class="w-5 h-5"
                fill="none"
                stroke="currentColor"
                viewBox="0 0 24 24"
              >
                <path
                  stroke-linecap="round"
                  stroke-linejoin="round"
                  stroke-width="2"
                  d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
                />
              </svg>
            </button>
          </div>
        </div>
      </header>

      <main class="max-w-7xl mx-auto px-4 py-6">
        <!-- Loading State -->
        <div
          v-if="loading && !jobs.length && currentView === 'jobs'"
          class="text-center py-12 text-slate-500"
        >
          Loading...
        </div>

        <!-- Jobs List View -->
        <div v-else-if="currentView === 'jobs'">
          <!-- Stats Cards -->
          <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
            <div
              class="bg-white rounded-xl p-4 border border-slate-200 shadow-sm text-center"
            >
              <div class="text-2xl font-semibold">{{ totalStats.jobs }}</div>
              <div class="text-sm text-slate-500">Jobs</div>
            </div>
            <div
              class="bg-white rounded-xl p-4 border border-slate-200 shadow-sm text-center"
            >
              <div class="text-2xl font-semibold">{{ totalStats.trials }}</div>
              <div class="text-sm text-slate-500">Total Trials</div>
            </div>
            <div
              class="bg-white rounded-xl p-4 border border-slate-200 shadow-sm text-center"
            >
              <div class="text-2xl font-semibold text-emerald-600">
                {{ totalStats.success }}
              </div>
              <div class="text-sm text-emerald-600">Success</div>
            </div>
            <div
              class="bg-white rounded-xl p-4 border border-slate-200 shadow-sm text-center"
            >
              <div class="text-2xl font-semibold text-red-600">
                {{ totalStats.failed }}
              </div>
              <div class="text-sm text-red-600">Failed</div>
            </div>
          </div>

          <!-- Jobs Table -->
          <div
            v-if="jobs.length === 0"
            class="text-center py-12 text-slate-500"
          >
            No jobs found
          </div>
          <div
            v-else
            class="bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden"
          >
            <table class="w-full">
              <thead class="bg-slate-50 border-b border-slate-200">
                <tr>
                  <th
                    class="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase"
                  >
                    Status
                  </th>
                  <th
                    class="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase"
                  >
                    Job
                  </th>
                  <th
                    class="px-4 py-3 text-center text-xs font-medium text-slate-500 uppercase"
                  >
                    Trials
                  </th>
                  <th
                    class="px-4 py-3 text-center text-xs font-medium text-emerald-600 uppercase"
                  >
                    Success
                  </th>
                  <th
                    class="px-4 py-3 text-center text-xs font-medium text-red-600 uppercase"
                  >
                    Failed
                  </th>
                </tr>
              </thead>
              <tbody class="divide-y divide-slate-100">
                <tr
                  v-for="job in jobs"
                  :key="job.job_id"
                  @click="goToTrials(job.job_id)"
                  class="hover:bg-slate-50 cursor-pointer"
                >
                  <td class="px-4 py-3">
                    <span class="flex items-center gap-2">
                      <span
                        :class="['w-2 h-2 rounded-full', statusDotClass(job.status)]"
                      ></span>
                      <span class="text-xs" :class="statusTextClass(job.status)"
                        >{{ statusText(job.status) }}</span
                      >
                    </span>
                  </td>
                  <td class="px-4 py-3 font-medium text-slate-900">
                    {{ job.job_id }}
                  </td>
                  <td class="px-4 py-3 text-center">{{ job.trial_count }}</td>
                  <td class="px-4 py-3 text-center text-emerald-600">
                    {{ job.success_count || 0 }}
                  </td>
                  <td class="px-4 py-3 text-center text-red-600">
                    {{ job.failed_count || 0 }}
                  </td>
                </tr>
              </tbody>
            </table>
          </div>
        </div>

        <!-- Trials List View -->
        <div v-else-if="currentView === 'trials'">
          <!-- Stats Cards -->
          <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
            <div
              class="bg-white rounded-xl p-4 border border-slate-200 shadow-sm text-center"
            >
              <div class="text-2xl font-semibold">{{ trials.length }}</div>
              <div class="text-sm text-slate-500">Trials</div>
            </div>
            <div
              class="bg-white rounded-xl p-4 border border-slate-200 shadow-sm text-center"
            >
              <div class="text-2xl font-semibold">{{ trialsCompleted }}</div>
              <div class="text-sm text-slate-500">Completed</div>
            </div>
            <div
              class="bg-white rounded-xl p-4 border border-slate-200 shadow-sm text-center"
            >
              <div class="text-2xl font-semibold text-emerald-600">
                {{ trialsSuccess }}
              </div>
              <div class="text-sm text-emerald-600">Success</div>
            </div>
            <div
              class="bg-white rounded-xl p-4 border border-slate-200 shadow-sm text-center"
            >
              <div class="text-2xl font-semibold text-red-600">
                {{ trialsFailed }}
              </div>
              <div class="text-sm text-red-600">Failed</div>
            </div>
          </div>

          <!-- Job Config & Log Tabs -->
          <div
            v-if="config || jobLog"
            class="mb-6 bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden"
          >
            <div class="border-b border-slate-200 flex">
              <button
                v-if="config"
                @click="activeJobTab = 'config'"
                :class="['px-4 py-3 text-sm font-medium transition-colors', activeJobTab === 'config' ? 'text-indigo-600 border-b-2 border-indigo-600 bg-white' : 'text-slate-500 hover:text-slate-700 bg-slate-50']"
              >
                Configuration
              </button>
              <button
                v-if="jobLog"
                @click="activeJobTab = 'log'"
                :class="['px-4 py-3 text-sm font-medium transition-colors', activeJobTab === 'log' ? 'text-indigo-600 border-b-2 border-indigo-600 bg-white' : 'text-slate-500 hover:text-slate-700 bg-slate-50']"
              >
                Job Log
              </button>
            </div>
            <div class="p-4">
              <div v-if="activeJobTab === 'config' && config">
                <pre
                  class="text-sm overflow-auto max-h-80 bg-slate-50 rounded-lg p-4"
                ><code class="language-json" ref="configCodeRef">{{ JSON.stringify(config, null, 2) }}</code></pre>
              </div>
              <div
                v-if="activeJobTab === 'log' && jobLog"
                class="bg-slate-900 rounded-lg p-4 max-h-80 overflow-auto"
              >
                <pre
                  class="text-xs text-slate-100 whitespace-pre-wrap font-mono"
                >
{{ jobLog }}</pre
                >
              </div>
            </div>
          </div>

          <!-- Trials Table -->
          <div v-if="loading" class="text-center py-12 text-slate-500">
            Loading trials...
          </div>
          <div
            v-else-if="trials.length === 0"
            class="text-center py-12 text-slate-500"
          >
            No trials found
          </div>
          <div
            v-else
            class="bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden"
          >
            <table class="w-full">
              <thead class="bg-slate-50 border-b border-slate-200">
                <tr>
                  <th
                    class="px-4 py-3 text-center text-xs font-medium text-slate-500 uppercase w-16"
                  >
                    Status
                  </th>
                  <th
                    class="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase"
                  >
                    Task
                  </th>
                  <th
                    class="px-4 py-3 text-left text-xs font-medium text-slate-500 uppercase"
                  >
                    Exception
                  </th>
                  <th
                    v-for="key in rewardKeys"
                    :key="key"
                    class="px-4 py-3 text-center text-xs font-medium text-slate-500 uppercase"
                  >
                    {{ key }}
                  </th>
                </tr>
              </thead>
              <tbody class="divide-y divide-slate-100">
                <tr
                  v-for="trial in trials"
                  :key="trial.trial_id"
                  @click="goToTrajectory(selectedJobId, trial.trial_id)"
                  class="hover:bg-slate-50 cursor-pointer"
                >
                  <td class="px-4 py-3 text-center">
                    <span
                      :class="['w-2 h-2 rounded-full inline-block', rewardDotClass(trial)]"
                    ></span>
                  </td>
                  <td class="px-4 py-3 font-medium text-slate-900">
                    {{ trial.task_name || trial.trial_id }}
                  </td>
                  <td class="px-4 py-3">
                    <span
                      v-if="trial.exception_type"
                      class="px-2 py-1 rounded text-xs bg-red-100 text-red-700"
                    >
                      {{ trial.exception_type }}
                    </span>
                    <span v-else class="text-slate-400">-</span>
                  </td>
                  <td
                    v-for="key in rewardKeys"
                    :key="key"
                    class="px-4 py-3 text-center"
                  >
                    <span
                      v-if="trial.rewards?.[key] !== undefined"
                      :class="['px-2 py-1 rounded-full text-sm', trial.rewards[key] === 1 ? 'bg-emerald-100 text-emerald-700' : 'bg-red-100 text-red-700']"
                    >
                      {{ formatReward(trial.rewards[key]) }}
                    </span>
                    <span v-else class="text-slate-400">-</span>
                  </td>
                </tr>
              </tbody>
            </table>
          </div>
        </div>

        <!-- Trajectory Detail View -->
        <div v-else-if="currentView === 'trajectory'">
          <div v-if="loading" class="text-center py-12 text-slate-500">
            Loading trajectory...
          </div>
          <div v-else-if="!trajectory" class="text-center py-12 text-slate-500">
            Trajectory not found
          </div>
          <template v-else>
            <!-- Trajectory Header Card -->
            <div
              class="bg-white rounded-xl border border-slate-200 shadow-sm mb-6"
            >
              <div class="p-4 border-b border-slate-200">
                <h2 class="text-lg font-semibold text-slate-900">
                  {{ trajectory.task_name || trajectory.session_id }}
                </h2>
                <div class="flex flex-wrap gap-4 text-sm text-slate-500 mt-1">
                  <span
                    ><strong>Agent:</strong> {{ trajectory.agent_name }} v{{
                    trajectory.agent_version }}</span
                  >
                  <span v-if="trajectory.model_name"
                    ><strong>Model:</strong> {{ trajectory.model_name }}</span
                  >
                  <span
                    ><strong>Steps:</strong> {{ trajectory.steps?.length || 0
                    }}</span
                  >
                </div>
              </div>

              <!-- Metrics -->
              <div
                class="grid grid-cols-2 md:grid-cols-4 gap-4 p-4 text-center"
              >
                <div>
                  <div class="text-xl font-semibold">
                    ${{ (trajectory.metrics?.cost_usd || 0).toFixed(4) }}
                  </div>
                  <div class="text-sm text-slate-500">Cost</div>
                </div>
                <div>
                  <div class="text-xl font-semibold">
                    {{ (trajectory.metrics?.prompt_tokens || 0).toLocaleString()
                    }}
                  </div>
                  <div class="text-sm text-slate-500">Prompt</div>
                </div>
                <div>
                  <div class="text-xl font-semibold">
                    {{ (trajectory.metrics?.completion_tokens ||
                    0).toLocaleString() }}
                  </div>
                  <div class="text-sm text-slate-500">Completion</div>
                </div>
                <div v-if="trajectory.metrics?.cached_tokens">
                  <div class="text-xl font-semibold text-emerald-600">
                    {{ trajectory.metrics.cached_tokens.toLocaleString() }}
                  </div>
                  <div class="text-sm text-slate-500">Cached</div>
                </div>
              </div>

              <!-- Verifier Result -->
              <div
                v-if="trajectory.verifier?.rewards"
                class="p-4 border-t border-slate-200"
              >
                <h3 class="text-xs font-medium text-slate-500 uppercase mb-2">
                  Verifier Result
                </h3>
                <div class="flex flex-wrap gap-6">
                  <div v-for="(value, key) in trajectory.verifier.rewards" :key="key">
                    <span class="text-slate-500 capitalize">{{ key }}:</span>
                    <span
                      :class="['font-semibold ml-1', value === 1 ? 'text-emerald-600' : 'text-red-600']"
                    >
                      {{ value }}
                    </span>
                  </div>
                </div>
              </div>

              <!-- Exception - Structured -->
              <div
                v-if="trajectory.exception_info"
                class="p-4 border-t border-slate-200"
              >
                <div class="bg-red-50 border border-red-200 rounded-lg p-4">
                  <div class="flex items-start gap-3">
                    <span class="text-red-500 text-xl flex-shrink-0">&#9888;</span>
                    <div class="flex-1 min-w-0">
                      <div class="font-medium text-red-700">
                        {{ trajectory.exception_info.exception_type }}
                      </div>
                      <div class="text-sm text-red-600 mt-1 break-words">
                        {{ trajectory.exception_info.exception_message }}
                      </div>
                      <div class="text-xs text-red-400 mt-2">
                        Occurred at: {{
                        formatDateTime(trajectory.exception_info.occurred_at) }}
                      </div>
                      <details class="mt-3">
                        <summary
                          class="text-sm text-red-500 cursor-pointer hover:text-red-700"
                        >
                          Show full traceback
                        </summary>
                        <pre
                          class="mt-2 text-xs text-red-800 whitespace-pre-wrap bg-red-100 p-3 rounded overflow-auto max-h-64"
                        >
{{ trajectory.exception_info.exception_traceback }}</pre
                        >
                      </details>
                    </div>
                  </div>
                </div>
              </div>

              <!-- Exception - Fallback for legacy data -->
              <div
                v-else-if="trajectory.exception"
                class="p-4 border-t border-slate-200"
              >
                <div class="bg-red-50 border border-red-200 rounded-lg p-4">
                  <h3 class="text-xs font-medium text-red-700 uppercase mb-2">
                    Exception
                  </h3>
                  <pre
                    class="text-sm text-red-800 whitespace-pre-wrap max-h-64 overflow-auto"
                  >
{{ trajectory.exception }}</pre
                  >
                </div>
              </div>
            </div>

            <!-- Tabbed Interface -->
            <div
              class="bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden"
            >
              <!-- Tab Headers -->
              <div class="border-b border-slate-200 flex">
                <button
                  v-if="trajectory.has_trajectory"
                  @click="activeTab = 'steps'"
                  :class="['px-4 py-3 text-sm font-medium transition-colors', activeTab === 'steps' ? 'text-indigo-600 border-b-2 border-indigo-600 bg-white' : 'text-slate-500 hover:text-slate-700 bg-slate-50']"
                >
                  Steps
                  <span
                    v-if="trajectory?.steps"
                    class="ml-2 px-1.5 py-0.5 rounded text-xs bg-slate-100 text-slate-600"
                    >{{ trajectory.steps.length }}</span
                  >
                </button>
                <button
                  v-if="trajectory.config"
                  @click="activeTab = 'config'"
                  :class="['px-4 py-3 text-sm font-medium transition-colors', activeTab === 'config' ? 'text-indigo-600 border-b-2 border-indigo-600 bg-white' : 'text-slate-500 hover:text-slate-700 bg-slate-50']"
                >
                  Configuration
                </button>
                <button
                  v-if="trajectory.logs?.trial"
                  @click="activeTab = 'trial_log'"
                  :class="['px-4 py-3 text-sm font-medium transition-colors', activeTab === 'trial_log' ? 'text-indigo-600 border-b-2 border-indigo-600 bg-white' : 'text-slate-500 hover:text-slate-700 bg-slate-50']"
                >
                  Trial Log
                </button>
                <button
                  v-if="trajectory.logs?.verifier_stdout || trajectory.logs?.verifier_stderr"
                  @click="activeTab = 'verifier'"
                  :class="['px-4 py-3 text-sm font-medium transition-colors', activeTab === 'verifier' ? 'text-indigo-600 border-b-2 border-indigo-600 bg-white' : 'text-slate-500 hover:text-slate-700 bg-slate-50']"
                >
                  Verifier
                </button>
              </div>

              <!-- Tab Content -->
              <div class="p-4">
                <!-- No Trajectory Notice -->
                <div
                  v-if="!trajectory.has_trajectory && activeTab === 'steps'"
                  class="text-center py-8"
                >
                  <div class="text-slate-400 mb-2">
                    <svg class="w-12 h-12 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
                    </svg>
                  </div>
                  <p class="text-slate-500">No trajectory data available</p>
                  <p class="text-sm text-slate-400 mt-1">This trial does not have trajectory.json</p>
                </div>
                <!-- Steps Tab -->
                <div v-if="activeTab === 'steps' && trajectory.has_trajectory" class="space-y-4">
                  <div class="flex flex-col sm:flex-row gap-4">
                    <input
                      v-model="searchQuery"
                      type="text"
                      placeholder="Search messages..."
                      class="flex-1 px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none"
                    />
                    <div
                      class="flex rounded-lg border border-slate-300 overflow-hidden"
                    >
                      <button
                        v-for="f in ['all', 'system', 'user', 'agent']"
                        :key="f"
                        @click="stepFilter = f"
                        :class="['px-3 py-2 text-sm transition-colors', stepFilter === f ? 'bg-indigo-600 text-white' : 'bg-white text-slate-600 hover:bg-slate-50']"
                      >
                        {{ f.charAt(0).toUpperCase() + f.slice(1) }}
                      </button>
                    </div>
                    <button
                      @click="toggleAllSteps"
                      class="px-4 py-2 text-sm bg-white border border-slate-300 rounded-lg hover:bg-slate-50 transition-colors whitespace-nowrap"
                    >
                      {{ allStepsExpanded ? 'Collapse All Steps' : 'Expand All Steps' }}
                    </button>
                  </div>
                  <div class="space-y-3">
                    <div
                      v-for="step in filteredSteps"
                      :key="step.step_id"
                      class="rounded-xl border border-slate-200 shadow-sm overflow-hidden bg-white"
                    >
                      <div
                        @click="toggleStep(step)"
                        :class="['px-4 py-3 flex items-center gap-3 cursor-pointer hover:brightness-95', stepBgClass(step.source)]"
                      >
                        <svg
                          :class="['w-4 h-4 transition-transform text-slate-500', step.expanded ? 'rotate-90' : '']"
                          fill="currentColor"
                          viewBox="0 0 20 20"
                        >
                          <path
                            fill-rule="evenodd"
                            d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
                          />
                        </svg>
                        <span
                          :class="['px-2 py-0.5 rounded text-xs font-medium', stepBadgeClass(step.source)]"
                          >{{ step.source }}</span
                        >
                        <span class="font-medium text-slate-900"
                          >Step {{ step.step_id }}</span
                        >
                        <span class="text-slate-500 text-sm flex-1 truncate"
                          >{{ (step.message || '').substring(0, 80) }}{{
                          step.message?.length > 80 ? '...' : '' }}</span
                        >
                        <span
                          v-if="step.metrics"
                          class="text-xs text-slate-400 hidden sm:inline"
                        >
                          {{ step.metrics.prompt_tokens || 0 }}+{{
                          step.metrics.completion_tokens || 0 }} tokens
                        </span>
                      </div>
                      <div
                        v-if="step.expanded"
                        class="px-4 pb-4 space-y-4 border-t border-slate-200/50"
                      >
                        <!-- Message -->
                        <div v-if="step.message" class="pt-4">
                          <h4
                            class="text-xs font-medium text-slate-500 uppercase mb-2"
                          >
                            Message
                          </h4>
                          <div
                            class="bg-slate-50 p-3 rounded-lg text-sm whitespace-pre-wrap max-h-96 overflow-auto prose prose-sm max-w-none"
                            v-html="renderMarkdown(step.message)"
                          ></div>
                        </div>

                        <!-- Reasoning -->
                        <div v-if="step.reasoning_content">
                          <h4
                            class="text-xs font-medium text-slate-500 uppercase mb-2"
                          >
                            Reasoning
                          </h4>
                          <div
                            class="bg-slate-50 p-3 rounded-lg text-sm whitespace-pre-wrap max-h-96 overflow-auto"
                          >
                            {{ step.reasoning_content }}
                          </div>
                        </div>

                        <!-- Tool Calls -->
                        <div v-if="step.tool_calls?.length">
                          <h4
                            class="text-xs font-medium text-slate-500 uppercase mb-2"
                          >
                            Tool Calls
                          </h4>
                          <div
                            v-for="tc in step.tool_calls"
                            :key="tc.call_id"
                            class="border border-slate-200 rounded-lg mb-2 overflow-hidden"
                          >
                            <div
                              class="px-3 py-2 bg-slate-50 border-b border-slate-200 flex items-center gap-2"
                            >
                              <span class="text-amber-600">&#9881;</span>
                              <code class="text-amber-600 font-medium"
                                >{{ tc.function_name }}</code
                              >
                            </div>
                            <div class="px-3 py-2">
                              <pre
                                class="text-xs overflow-auto bg-slate-900 text-slate-100 p-3 rounded"
                              ><code>{{ tc.arguments }}</code></pre>
                            </div>
                            <div
                              v-for="(obs, idx) in tc.observation_results"
                              :key="idx"
                              class="px-3 py-2 bg-slate-100 border-t border-slate-200"
                            >
                              <div
                                class="text-xs text-slate-500 uppercase mb-1"
                              >
                                Result
                              </div>
                              <pre
                                class="text-xs text-emerald-700 max-h-48 overflow-auto whitespace-pre-wrap"
                              >
{{ obs.content || '(empty)' }}</pre
                              >
                            </div>
                          </div>
                        </div>

                        <!-- Observation -->
                        <div v-if="step.observation?.results?.length">
                          <h4
                            class="text-xs font-medium text-slate-500 uppercase mb-2"
                          >
                            Observation
                          </h4>
                          <div
                            v-for="(result, idx) in step.observation.results"
                            :key="idx"
                            class="bg-slate-50 p-3 rounded-lg mb-2"
                          >
                            <div
                              v-if="result.source_call_id"
                              class="text-xs text-slate-400 mb-1"
                            >
                              {{ result.source_call_id }}
                            </div>
                            <pre
                              v-if="result.content"
                              class="text-xs max-h-48 overflow-auto whitespace-pre-wrap"
                            >
{{ result.content }}</pre
                            >
                          </div>
                        </div>

                        <!-- Subagent Trajectories -->
                        <div v-if="getStepSubagentRefs(step).length > 0">
                          <h4
                            class="text-xs font-medium text-slate-500 uppercase mb-2"
                          >
                            Subagent Trajectories
                          </h4>
                          <div class="space-y-2">
                            <div
                              v-for="ref in getStepSubagentRefs(step)"
                              :key="ref.session_id"
                              class="border border-indigo-200 rounded-lg overflow-hidden"
                            >
                              <div
                                @click="toggleSubagent(ref.session_id)"
                                class="px-3 py-2 bg-indigo-50 cursor-pointer hover:bg-indigo-100 flex items-center gap-2"
                              >
                                <svg
                                  :class="['w-4 h-4 transition-transform text-indigo-500', expandedSubagents[ref.session_id] ? 'rotate-90' : '']"
                                  fill="currentColor"
                                  viewBox="0 0 20 20"
                                >
                                  <path
                                    fill-rule="evenodd"
                                    d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
                                  />
                                </svg>
                                <span class="text-indigo-600 font-medium text-sm">
                                  {{ getSubagentDisplayName(ref) }}
                                </span>
                                <span
                                  v-if="trajectory.subagent_trajectories?.[ref.session_id]"
                                  class="text-xs text-indigo-400"
                                >
                                  ({{ trajectory.subagent_trajectories[ref.session_id].steps?.length || 0 }} steps)
                                </span>
                                <span
                                  v-if="ref.extra?.summary"
                                  class="text-xs text-slate-500 ml-2"
                                >
                                  - {{ ref.extra.summary }}
                                </span>
                              </div>
                              <!-- Expanded subagent content -->
                              <div
                                v-if="expandedSubagents[ref.session_id] && trajectory.subagent_trajectories?.[ref.session_id]"
                                class="p-3 bg-slate-50 border-t border-indigo-100"
                              >
                                <div class="text-xs text-slate-500 mb-3 flex items-center gap-4">
                                  <span>Agent: <strong>{{ trajectory.subagent_trajectories[ref.session_id].agent_name }}</strong></span>
                                  <span v-if="trajectory.subagent_trajectories[ref.session_id].model_name">
                                    Model: {{ trajectory.subagent_trajectories[ref.session_id].model_name }}
                                  </span>
                                </div>
                                <!-- Subagent steps - same UI as main agent -->
                                <div class="space-y-3 max-h-[600px] overflow-auto">
                                  <div
                                    v-for="subStep in trajectory.subagent_trajectories[ref.session_id].steps"
                                    :key="subStep.step_id"
                                    class="rounded-xl border border-slate-200 shadow-sm overflow-hidden bg-white"
                                  >
                                    <!-- Step header (clickable) -->
                                    <div
                                      @click="toggleSubagentStep(ref.session_id, subStep.step_id)"
                                      :class="['px-4 py-3 flex items-center gap-3 cursor-pointer hover:brightness-95', stepBgClass(subStep.source)]"
                                    >
                                      <svg
                                        :class="['w-4 h-4 transition-transform text-slate-500', isSubagentStepExpanded(ref.session_id, subStep.step_id) ? 'rotate-90' : '']"
                                        fill="currentColor"
                                        viewBox="0 0 20 20"
                                      >
                                        <path
                                          fill-rule="evenodd"
                                          d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
                                        />
                                      </svg>
                                      <span
                                        :class="['px-2 py-0.5 rounded text-xs font-medium', stepBadgeClass(subStep.source)]"
                                      >{{ subStep.source }}</span>
                                      <span class="font-medium text-slate-900">Step {{ subStep.step_id }}</span>
                                      <span class="text-slate-500 text-sm flex-1 truncate">
                                        {{ (subStep.message || '').substring(0, 60) }}{{ subStep.message?.length > 60 ? '...' : '' }}
                                      </span>
                                      <span
                                        v-if="subStep.metrics"
                                        class="text-xs text-slate-400 hidden sm:inline"
                                      >
                                        {{ subStep.metrics.prompt_tokens || 0 }}+{{ subStep.metrics.completion_tokens || 0 }} tokens
                                      </span>
                                    </div>
                                    <!-- Step expanded content -->
                                    <div
                                      v-if="isSubagentStepExpanded(ref.session_id, subStep.step_id)"
                                      class="px-4 pb-4 space-y-4 border-t border-slate-200/50"
                                    >
                                      <!-- Message -->
                                      <div v-if="subStep.message" class="pt-4">
                                        <h4 class="text-xs font-medium text-slate-500 uppercase mb-2">Message</h4>
                                        <div
                                          class="bg-slate-50 p-3 rounded-lg text-sm whitespace-pre-wrap max-h-96 overflow-auto prose prose-sm max-w-none"
                                          v-html="renderMarkdown(subStep.message)"
                                        ></div>
                                      </div>
                                      <!-- Reasoning -->
                                      <div v-if="subStep.reasoning_content">
                                        <h4 class="text-xs font-medium text-slate-500 uppercase mb-2">Reasoning</h4>
                                        <div class="bg-slate-50 p-3 rounded-lg text-sm whitespace-pre-wrap max-h-96 overflow-auto">
                                          {{ subStep.reasoning_content }}
                                        </div>
                                      </div>
                                      <!-- Tool Calls -->
                                      <div v-if="subStep.tool_calls?.length">
                                        <h4 class="text-xs font-medium text-slate-500 uppercase mb-2">Tool Calls</h4>
                                        <div
                                          v-for="tc in subStep.tool_calls"
                                          :key="tc.tool_call_id"
                                          class="border border-slate-200 rounded-lg mb-2 overflow-hidden"
                                        >
                                          <div class="px-3 py-2 bg-slate-50 border-b border-slate-200 flex items-center gap-2">
                                            <span class="text-amber-600">&#9881;</span>
                                            <code class="text-amber-600 font-medium">{{ tc.function_name }}</code>
                                          </div>
                                          <div class="px-3 py-2">
                                            <pre class="text-xs overflow-auto bg-slate-900 text-slate-100 p-3 rounded"><code>{{ tc.arguments }}</code></pre>
                                          </div>
                                          <div
                                            v-for="(obs, idx) in tc.observation_results"
                                            :key="idx"
                                            class="px-3 py-2 bg-slate-100 border-t border-slate-200"
                                          >
                                            <div class="text-xs text-slate-500 uppercase mb-1">Result</div>
                                            <pre class="text-xs text-emerald-700 max-h-48 overflow-auto whitespace-pre-wrap">{{ obs.content || '(empty)' }}</pre>
                                          </div>
                                        </div>
                                      </div>
                                      <!-- Observation -->
                                      <div v-if="subStep.observation?.results?.length">
                                        <h4 class="text-xs font-medium text-slate-500 uppercase mb-2">Observation</h4>
                                        <div
                                          v-for="(result, idx) in subStep.observation.results"
                                          :key="idx"
                                          class="bg-slate-50 p-3 rounded-lg mb-2"
                                        >
                                          <div v-if="result.source_call_id" class="text-xs text-slate-400 mb-1">{{ result.source_call_id }}</div>
                                          <pre v-if="result.content" class="text-xs max-h-48 overflow-auto whitespace-pre-wrap">{{ result.content }}</pre>
                                        </div>
                                      </div>
                                      <!-- Metrics -->
                                      <div v-if="subStep.metrics">
                                        <h4 class="text-xs font-medium text-slate-500 uppercase mb-2">Metrics</h4>
                                        <div class="flex flex-wrap gap-4 text-sm">
                                          <div v-if="subStep.metrics.prompt_tokens != null">
                                            <span class="text-slate-500">Prompt:</span> {{ subStep.metrics.prompt_tokens.toLocaleString() }}
                                          </div>
                                          <div v-if="subStep.metrics.completion_tokens != null">
                                            <span class="text-slate-500">Completion:</span> {{ subStep.metrics.completion_tokens.toLocaleString() }}
                                          </div>
                                          <div v-if="subStep.metrics.cached_tokens">
                                            <span class="text-slate-500">Cached:</span>
                                            <span class="text-emerald-600">{{ subStep.metrics.cached_tokens.toLocaleString() }}</span>
                                          </div>
                                          <div v-if="subStep.metrics.cost_usd != null">
                                            <span class="text-slate-500">Cost:</span> ${{ subStep.metrics.cost_usd.toFixed(6) }}
                                          </div>
                                        </div>
                                      </div>
                                    </div>
                                  </div>
                                </div>
                              </div>
                            </div>
                          </div>
                        </div>

                        <!-- Step Metrics -->
                        <div v-if="step.metrics">
                          <h4
                            class="text-xs font-medium text-slate-500 uppercase mb-2"
                          >
                            Metrics
                          </h4>
                          <div class="flex flex-wrap gap-4 text-sm">
                            <div v-if="step.metrics.prompt_tokens != null">
                              <span class="text-slate-500">Prompt:</span> {{
                              step.metrics.prompt_tokens.toLocaleString() }}
                            </div>
                            <div v-if="step.metrics.completion_tokens != null">
                              <span class="text-slate-500">Completion:</span> {{
                              step.metrics.completion_tokens.toLocaleString() }}
                            </div>
                            <div v-if="step.metrics.cached_tokens">
                              <span class="text-slate-500">Cached:</span>
                              <span class="text-emerald-600"
                                >{{ step.metrics.cached_tokens.toLocaleString()
                                }}</span
                              >
                            </div>
                            <div v-if="step.metrics.cost_usd != null">
                              <span class="text-slate-500">Cost:</span> ${{
                              step.metrics.cost_usd.toFixed(6) }}
                            </div>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>

                <!-- Configuration Tab -->
                <div v-if="activeTab === 'config'">
                  <pre
                    class="text-sm overflow-auto max-h-[70vh] bg-slate-50 rounded-lg p-4"
                  ><code class="language-json" ref="trajectoryConfigCodeRef">{{ trajectoryConfigText }}</code></pre>
                </div>

                <!-- Trial Log Tab -->
                <div
                  v-if="activeTab === 'trial_log'"
                  class="bg-slate-900 rounded-lg p-4 max-h-[70vh] overflow-auto"
                >
                  <pre
                    class="text-xs text-slate-100 whitespace-pre-wrap font-mono"
                  >
{{ trajectory.logs.trial }}</pre
                  >
                </div>

                <!-- Verifier Tab -->
                <div v-if="activeTab === 'verifier'" class="space-y-4">
                  <!-- Stdout Section -->
                  <div v-if="trajectory.logs?.verifier_stdout">
                    <h4 class="text-xs font-medium text-slate-500 uppercase mb-2">Stdout</h4>
                    <div class="bg-slate-900 rounded-lg p-4 max-h-[35vh] overflow-auto">
                      <pre class="text-xs text-slate-100 whitespace-pre-wrap font-mono">{{ trajectory.logs.verifier_stdout }}</pre>
                    </div>
                  </div>
                  <!-- Stderr Section -->
                  <div v-if="trajectory.logs?.verifier_stderr">
                    <h4 class="text-xs font-medium text-slate-500 uppercase mb-2">Stderr</h4>
                    <div class="bg-slate-900 rounded-lg p-4 max-h-[35vh] overflow-auto">
                      <pre class="text-xs text-slate-100 whitespace-pre-wrap font-mono">{{ trajectory.logs.verifier_stderr }}</pre>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </template>
        </div>
      </main>
    </div>

    <script>
      const { createApp, ref, computed, onMounted, watch, nextTick } = Vue;

      createApp({
        setup() {
          // === State ===
          const currentView = ref("jobs");
          const jobs = ref([]);
          const trials = ref([]);
          const trajectory = ref(null);
          const config = ref(null);
          const configCodeRef = ref(null);
          const trajectoryConfigCodeRef = ref(null);
          const selectedJobId = ref(null);
          const selectedTrialId = ref(null);
          const loading = ref(false);
          const autoRefresh = ref(true);
          const searchQuery = ref("");
          const stepFilter = ref("all");
          const activeTab = ref("steps");
          const jobLog = ref(null);
          const activeJobTab = ref("config");
          const expandedSubagents = ref({});
          const expandedSubagentSteps = ref({});
          let refreshTimer = null;
          let lastMtime = null;

          // Timing phase definitions
          const timingPhases = {
            environment_setup: { label: "Environment", color: "bg-blue-500" },
            agent_setup: { label: "Agent Setup", color: "bg-amber-500" },
            agent_execution: { label: "Agent Run", color: "bg-emerald-500" },
            verifier: { label: "Verifier", color: "bg-purple-500" },
          };

          // === Computed ===
          const totalStats = computed(() => ({
            jobs: jobs.value.length,
            trials: jobs.value.reduce(
              (sum, j) => sum + (j.trial_count || 0),
              0
            ),
            success: jobs.value.reduce(
              (sum, j) => sum + (j.success_count || 0),
              0
            ),
            failed: jobs.value.reduce(
              (sum, j) => sum + (j.failed_count || 0),
              0
            ),
          }));

          const trialsCompleted = computed(
            () => trials.value.filter((t) => t.status === "completed").length
          );
          const rewardKeys = computed(() => {
            const keys = new Set();
            trials.value.forEach((t) => {
              if (t.rewards) Object.keys(t.rewards).forEach((k) => keys.add(k));
            });
            return Array.from(keys).sort();
          });
          const trialsSuccess = computed(
            () =>
              trials.value.filter(
                (t) => t.rewards && Object.values(t.rewards).some((v) => v === 1)
              ).length
          );
          const trialsFailed = computed(
            () =>
              trials.value.filter(
                (t) =>
                  t.status === "completed" &&
                  (!t.rewards || !Object.values(t.rewards).some((v) => v === 1))
              ).length
          );

          const filteredSteps = computed(() => {
            if (!trajectory.value?.steps) return [];
            return trajectory.value.steps.filter((step) => {
              const matchesFilter =
                stepFilter.value === "all" || step.source === stepFilter.value;
              const matchesSearch =
                !searchQuery.value ||
                (step.message || "")
                  .toLowerCase()
                  .includes(searchQuery.value.toLowerCase());
              return matchesFilter && matchesSearch;
            });
          });

          const trajectoryConfigText = computed(() => {
            const cfg = trajectory.value?.config;
            if (cfg === null || cfg === undefined) return "";
            if (typeof cfg === "string") return cfg;
            try {
              return JSON.stringify(cfg, null, 2);
            } catch (e) {
              return String(cfg);
            }
          });

          const allStepsExpanded = computed(() => {
            if (!trajectory.value?.steps?.length) return false;
            return trajectory.value.steps.every((step) => step.expanded);
          });

          // === API Methods ===
          async function loadJobs() {
            loading.value = true;
            try {
              const res = await fetch("/api/jobs");
              jobs.value = await res.json();
            } catch (e) {
              console.error("Failed to load jobs:", e);
            } finally {
              loading.value = false;
            }
          }

          async function loadTrials(jobId) {
            loading.value = true;
            try {
              const [trialsRes, configRes, logRes] = await Promise.all([
                fetch(`/api/jobs/${encodeURIComponent(jobId)}/trials`),
                fetch(`/api/jobs/${encodeURIComponent(jobId)}/config`),
                fetch(`/api/jobs/${encodeURIComponent(jobId)}/log`),
              ]);
              trials.value = await trialsRes.json();
              config.value = await configRes.json();
              const logData = await logRes.json();
              jobLog.value = logData.content;
            } catch (e) {
              console.error("Failed to load trials:", e);
            } finally {
              loading.value = false;
            }
          }

          async function loadTrajectory(jobId, trialId) {
            loading.value = true;
            try {
              const res = await fetch(
                `/api/trajectories/${encodeURIComponent(
                  jobId
                )}/${encodeURIComponent(trialId)}`
              );
              const data = await res.json();
              if (data.error) throw new Error(data.error);
              // Add expanded property to steps
              if (data.steps) {
                data.steps = data.steps.map((s) => ({ ...s, expanded: false }));
              }
              trajectory.value = data;
              // Set default tab based on trajectory availability
              if (!data.has_trajectory) {
                // No trajectory: default to config or first available tab
                if (data.config) {
                  activeTab.value = "config";
                } else if (data.logs?.trial) {
                  activeTab.value = "trial_log";
                } else {
                  activeTab.value = "steps"; // Will show "no trajectory" notice
                }
              } else {
                activeTab.value = "steps";
              }
              // Get initial mtime
              const mtimeRes = await fetch(
                `/api/trajectories/${encodeURIComponent(
                  jobId
                )}/${encodeURIComponent(trialId)}/mtime`
              );
              const mtimeData = await mtimeRes.json();
              lastMtime = mtimeData.mtime;
            } catch (e) {
              console.error("Failed to load trajectory:", e);
              trajectory.value = null;
            } finally {
              loading.value = false;
            }
          }

          // === Navigation ===
          function goToJobs() {
            currentView.value = "jobs";
            selectedJobId.value = null;
            selectedTrialId.value = null;
            trajectory.value = null;
            config.value = null;
            trials.value = [];
            loadJobs();
          }

          function goToTrials(jobId) {
            currentView.value = "trials";
            selectedJobId.value = jobId;
            selectedTrialId.value = null;
            trajectory.value = null;
            loadTrials(jobId);
          }

          function goToTrajectory(jobId, trialId) {
            currentView.value = "trajectory";
            selectedJobId.value = jobId;
            selectedTrialId.value = trialId;
            searchQuery.value = "";
            stepFilter.value = "all";
            activeTab.value = "steps";
            loadTrajectory(jobId, trialId);
          }

          function goBack() {
            if (currentView.value === "trajectory") {
              goToTrials(selectedJobId.value);
            } else if (currentView.value === "trials") {
              goToJobs();
            }
          }

          function refresh() {
            if (currentView.value === "jobs") {
              loadJobs();
            } else if (currentView.value === "trials") {
              loadTrials(selectedJobId.value);
            } else if (currentView.value === "trajectory") {
              loadTrajectory(selectedJobId.value, selectedTrialId.value);
            }
          }

          // === Auto Refresh ===
          function startAutoRefresh() {
            if (refreshTimer) clearInterval(refreshTimer);
            refreshTimer = setInterval(async () => {
              if (!autoRefresh.value) return;
              try {
                if (
                  currentView.value === "trajectory" &&
                  selectedJobId.value &&
                  selectedTrialId.value
                ) {
                  const res = await fetch(
                    `/api/trajectories/${encodeURIComponent(
                      selectedJobId.value
                    )}/${encodeURIComponent(selectedTrialId.value)}/mtime`
                  );
                  const data = await res.json();
                  if (data.mtime !== lastMtime) {
                    lastMtime = data.mtime;
                    await loadTrajectory(
                      selectedJobId.value,
                      selectedTrialId.value
                    );
                  }
                } else if (currentView.value === "jobs") {
                  await loadJobs();
                } else if (
                  currentView.value === "trials" &&
                  selectedJobId.value
                ) {
                  await loadTrials(selectedJobId.value);
                }
              } catch (e) {
                console.error("Auto-refresh error:", e);
              }
            }, 5000);
          }

          // === UI Helpers ===
          function statusDotClass(status) {
            if (status === "running") return "bg-amber-500 animate-pulse-dot";
            if (status === "completed") return "bg-emerald-500";
            return "bg-slate-400";
          }

          function statusTextClass(status) {
            if (status === "running") return "text-amber-600";
            if (status === "completed") return "text-emerald-600";
            return "text-slate-500";
          }

          function statusText(status) {
            if (status === "running") return "Running";
            if (status === "completed") return "Done";
            return "Unknown";
          }

          function rewardDotClass(trial) {
            if (trial.rewards && Object.values(trial.rewards).some((v) => v === 1))
              return "bg-emerald-500";
            if (trial.rewards && Object.values(trial.rewards).every((v) => v === 0))
              return "bg-red-500";
            if (trial.status === "running")
              return "bg-amber-500 animate-pulse-dot";
            return "bg-slate-400";
          }

          function stepBgClass(source) {
            if (source === "system") return "bg-amber-50 border-amber-200";
            if (source === "user") return "bg-blue-50 border-blue-200";
            if (source === "agent") return "bg-emerald-50 border-emerald-200";
            return "bg-white border-slate-200";
          }

          function stepBadgeClass(source) {
            if (source === "system") return "bg-amber-100 text-amber-700";
            if (source === "user") return "bg-blue-100 text-blue-700";
            if (source === "agent") return "bg-emerald-100 text-emerald-700";
            return "bg-slate-100 text-slate-700";
          }

          function toggleStep(step) {
            step.expanded = !step.expanded;
          }

          function toggleAllSteps() {
            if (!trajectory.value?.steps?.length) return;
            const shouldExpand = !allStepsExpanded.value;
            trajectory.value.steps.forEach((step) => {
              step.expanded = shouldExpand;
            });
          }

          function renderMarkdown(text) {
            if (!text) return "";
            try {
              return DOMPurify.sanitize(marked.parse(text));
            } catch (e) {
              return text;
            }
          }

          // === Timing Helpers ===
          function formatDuration(timing) {
            if (!timing?.started_at || !timing?.finished_at) return "-";
            const start = new Date(timing.started_at);
            const end = new Date(timing.finished_at);
            const seconds = (end - start) / 1000;
            if (seconds < 60) return `${seconds.toFixed(1)}s`;
            if (seconds < 3600) return `${(seconds / 60).toFixed(1)}m`;
            return `${(seconds / 3600).toFixed(1)}h`;
          }

          function getTimingDuration(timing) {
            if (!timing?.started_at || !timing?.finished_at) return 0;
            const start = new Date(timing.started_at);
            const end = new Date(timing.finished_at);
            return (end - start) / 1000;
          }

          function getTimingPercent(key) {
            if (!trajectory.value?.timing) return 0;
            const timing = trajectory.value.timing;
            const phaseDuration = getTimingDuration(timing[key]);
            const totalDuration = getTimingDuration(timing.total);
            if (totalDuration === 0) return 0;
            return Math.min(100, (phaseDuration / totalDuration) * 100);
          }

          function formatDateTime(isoString) {
            if (!isoString) return "-";
            return new Date(isoString).toLocaleString();
          }

          async function loadJobLog(jobId) {
            try {
              const res = await fetch(
                `/api/jobs/${encodeURIComponent(jobId)}/log`
              );
              const data = await res.json();
              jobLog.value = data.content;
            } catch (e) {
              console.error("Failed to load job log:", e);
              jobLog.value = null;
            }
          }
          
          function formatReward(value) {
            if (typeof value === 'number' && !Number.isInteger(value)) {
              return parseFloat(value.toFixed(4));
            }
            return value;
          }

          // === Subagent Helpers ===
          function getStepSubagentRefs(step) {
            if (!step.observation?.results) return [];
            const refs = [];
            for (const result of step.observation.results) {
              if (result.subagent_trajectory_ref) {
                refs.push(...result.subagent_trajectory_ref);
              }
            }
            return refs;
          }

          function toggleSubagent(sessionId) {
            expandedSubagents.value[sessionId] = !expandedSubagents.value[sessionId];
          }

          function getSubagentDisplayName(ref) {
            // Try to extract a meaningful name from trajectory_path or session_id
            if (ref.trajectory_path) {
              const match = ref.trajectory_path.match(/trajectory\.(.+)\.json$/);
              if (match) return match[1];
            }
            // Fall back to last part of session_id
            const parts = ref.session_id.split('-');
            return parts.slice(-2).join('-');
          }

          function toggleSubagentStep(sessionId, stepId) {
            const key = `${sessionId}_${stepId}`;
            expandedSubagentSteps.value[key] = !expandedSubagentSteps.value[key];
          }

          function isSubagentStepExpanded(sessionId, stepId) {
            const key = `${sessionId}_${stepId}`;
            return !!expandedSubagentSteps.value[key];
          }

          // === Lifecycle ===
          onMounted(() => {
            loadJobs();
            startAutoRefresh();
          });

          // Highlight job config JSON when it changes
          watch(config, async (val) => {
            if (!val) return;
            await nextTick();
            if (configCodeRef.value && window.hljs) {
              window.hljs.highlightElement(configCodeRef.value);
            }
          });

          watch(trajectoryConfigText, async (val) => {
            if (!val) return;
            await nextTick();
            if (trajectoryConfigCodeRef.value && window.hljs) {
              window.hljs.highlightElement(trajectoryConfigCodeRef.value);
            }
          });

          return {
            currentView,
            jobs,
            trials,
            trajectory,
            config,
            configCodeRef,
            trajectoryConfigCodeRef,
            selectedJobId,
            selectedTrialId,
            loading,
            autoRefresh,
            searchQuery,
            stepFilter,
            activeTab,
            jobLog,
            activeJobTab,
            timingPhases,
            totalStats,
            trialsCompleted,
            rewardKeys,
            trialsSuccess,
            trialsFailed,
            filteredSteps,
            trajectoryConfigText,
            allStepsExpanded,
            goToJobs,
            goToTrials,
            goToTrajectory,
            goBack,
            refresh,
            statusDotClass,
            statusTextClass,
            statusText,
            rewardDotClass,
            stepBgClass,
            stepBadgeClass,
            toggleStep,
            toggleAllSteps,
            renderMarkdown,
            formatDuration,
            getTimingPercent,
            formatDateTime,
            formatReward,
            expandedSubagents,
            expandedSubagentSteps,
            getStepSubagentRefs,
            toggleSubagent,
            getSubagentDisplayName,
            toggleSubagentStep,
            isSubagentStepExpanded,
            JSON,
          };
        },
      }).mount("#app");
    </script>
  </body>
</html>
