<script setup lang="ts">
import {
  Activity,
  AlertTriangle,
  Bot,
  CheckCircle2,
  ChevronRight,
  CircleStop,
  Clock3,
  ExternalLink,
  GitBranch,
  History,
  KeyRound,
  Loader2,
  Network,
  Play,
  Plus,
  RefreshCw,
  ShieldCheck,
  Sparkles,
  TimerReset,
  Workflow,
  X,
} from '@lucide/vue'
import { computed, nextTick, onMounted, ref, watch } from 'vue'
import { RouterLink, useRoute, useRouter } from 'vue-router'
import {
  useOpenClawCronJobs,
  useOpenClawCronMutations,
} from '../api/openClawCron'
import {
  useOpenClawRunMutations,
  useOpenClawRuns,
} from '../api/openClawRuns'
import { useOpenClawOverviewQuery } from '../api/openclawRuntime'
import { useAuthStore } from '../stores/auth'
import { useMissionControlUiStore } from '../stores/missionControlUi'
import { useOpenClawStore } from '../stores/openclaw'
import AsyncStatePanel from '../components/mission-control/AsyncStatePanel.vue'
import type {
  OpenClawActivity,
  OpenClawApproval,
  OpenClawCronJob,
} from '../types/openclaw'

type Selection =
  | { kind: 'agent'; id: string }
  | { kind: 'task'; id: string }
  | { kind: 'session'; id: string }

type PendingAction =
  | { kind: 'cancel-task'; id: string; label: string }
  | { kind: 'abort-session'; id: string; runId: string | null; label: string }
  | { kind: 'approval'; approval: OpenClawApproval; decision: string; label: string }
  | { kind: 'cron'; job: OpenClawCronJob; label: string }

const store = useOpenClawStore()
const missionControlUi = useMissionControlUiStore()
const auth = useAuthStore()
const route = useRoute()
const router = useRouter()
const selection = ref<Selection | null>(null)
const selectedEvent = ref<OpenClawActivity | null>(null)
const pendingAction = ref<PendingAction | null>(null)
const actionBusy = ref(false)
const actionError = ref('')
const announcement = ref('')
const dialogClose = ref<HTMLButtonElement | null>(null)
const dialogReturnFocus = ref<HTMLElement | null>(null)
const newRunOpen = ref(false)
const newRunTitle = ref('')
const newRunPrompt = ref('')
const newRunAgentId = ref('')
const newRunSessionKey = ref('')
const newRunTaskId = ref('')
const newRunProjectId = ref('')
const runsQuery = useOpenClawRuns({ limit: 50 })
const { startMutation: startRunMutation } = useOpenClawRunMutations()
const { collection: cronCollection } = useOpenClawCronJobs({
  includeDisabled: true,
  limit: 50,
})
const { runMutation: runCronMutation } = useOpenClawCronMutations()
const overviewQuery = useOpenClawOverviewQuery()

const overview = computed(() => overviewQuery.data.value ?? null)
const connection = computed(() => overview.value?.connection ?? null)
const overviewError = computed(() => {
  const error = overviewQuery.error.value
  return error instanceof Error ? error.message : ''
})
const canOperate = computed(() => auth.user?.role === 'owner')
const selectedTask = computed(() =>
  selection.value?.kind === 'task'
    ? overview.value?.tasks.items.find(item => item.id === selection.value?.id) ?? null
    : null,
)
const selectedSession = computed(() =>
  selection.value?.kind === 'session'
    ? overview.value?.sessions.items.find(item => item.key === selection.value?.id) ?? null
    : null,
)
const selectedAgent = computed(() =>
  selection.value?.kind === 'agent'
    ? overview.value?.agents.items.find(item => item.id === selection.value?.id) ?? null
    : null,
)
const approvals = computed(() => overview.value?.approvals.items ?? [])
const pendingApprovals = computed(() =>
  approvals.value.filter(item => item.status === 'pending'),
)
const requestedApprovalId = computed(() =>
  typeof route.query.approval === 'string' ? route.query.approval : '',
)
const orderedPendingApprovals = computed(() => {
  const requested = requestedApprovalId.value
  const items = [...pendingApprovals.value]
  const requestedItem = approvals.value.find(item => item.id === requested)
  if (requestedItem && !items.some(item => item.id === requestedItem.id)) {
    items.unshift(requestedItem)
  }
  return items.sort((left, right) => {
    if (left.id === requested) return -1
    if (right.id === requested) return 1
    return 0
  })
})
const activeTasks = computed(() =>
  overview.value?.tasks.items.filter(item => item.status === 'queued' || item.status === 'running') ?? [],
)
const activeSessions = computed(() =>
  overview.value?.sessions.items.filter(item => item.status === 'running' || item.status === 'active') ?? [],
)
const workNodes = computed(() => {
  const sessions = activeSessions.value.slice(0, 4).map(item => ({
    kind: 'session' as const,
    id: item.key,
    title: item.title,
    subtitle: `${item.agentId} · ${item.model || 'OpenClaw model'}`,
    status: item.status,
    detail: item.runId ? `Run ${shortId(item.runId)}` : 'Active session',
  }))
  const tasks = activeTasks.value.slice(0, Math.max(0, 6 - sessions.length)).map(item => ({
    kind: 'task' as const,
    id: item.id,
    title: item.title,
    subtitle: `${item.agentId || 'OpenClaw'} · ${item.kind || 'task'}`,
    status: item.status,
    detail: item.progress === null ? shortId(item.id) : `${Math.round(Number(item.progress))}%`,
  }))
  if (sessions.length || tasks.length) return [...sessions, ...tasks]
  return (overview.value?.agents.items ?? []).slice(0, 6).map(item => ({
    kind: 'agent' as const,
    id: item.id,
    title: item.name,
    subtitle: `${item.id} · ${item.model || 'OpenClaw default'}`,
    status: item.status,
    detail: item.description || 'Configured agent',
  }))
})
const readyCapabilityCount = computed(() =>
  overview.value?.capabilities.filter(item => item.available).length ?? 0,
)
const capabilityCount = computed(() => overview.value?.capabilities.length ?? 0)
const durableRuns = computed(() =>
  [...(runsQuery.data.value?.items ?? [])]
    .sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt))
    .slice(0, 6),
)
const activeRunCount = computed(() =>
  (runsQuery.data.value?.items ?? [])
    .filter(run => ['dispatching', 'running', 'stopping'].includes(run.status))
    .length,
)
const runActionBusy = computed(() => startRunMutation.isPending.value)
const runActionError = computed(() => {
  const error = startRunMutation.error.value
  return error instanceof Error ? error.message : ''
})
const runsQueryError = computed(() => {
  const error = runsQuery.error.value
  return error instanceof Error ? error.message : ''
})

function shortId(value: string): string {
  return value.length <= 18 ? value : `${value.slice(0, 8)}…${value.slice(-6)}`
}

function formatDate(value: string | null): string {
  if (!value) return 'Unknown'
  const date = new Date(value)
  if (Number.isNaN(date.getTime())) return value
  return date.toLocaleString('de-DE', {
    day: '2-digit',
    month: 'short',
    hour: '2-digit',
    minute: '2-digit',
  })
}

function statusTone(status: string): string {
  if (['running', 'active', 'ready', 'connected', 'ok', 'succeeded'].includes(status)) return 'positive'
  if (['queued', 'pending', 'idle', 'reconnecting', 'skipped'].includes(status)) return 'warning'
  if (['failed', 'error', 'cancelled', 'timed_out', 'disconnected'].includes(status)) return 'danger'
  return 'neutral'
}

function selectNode(node: { kind: Selection['kind']; id: string; title: string }) {
  selection.value = { kind: node.kind, id: node.id } as Selection
  announcement.value = `${node.title} selected.`
}

function rememberDialogTrigger(trigger?: Event) {
  const candidate = trigger?.currentTarget
  dialogReturnFocus.value = candidate instanceof HTMLElement && candidate !== document.body
    ? candidate
    : null
}

async function restoreDialogFocus() {
  const target = dialogReturnFocus.value
  dialogReturnFocus.value = null
  await nextTick()
  if (target?.isConnected) target.focus({ preventScroll: true })
}

async function dismissDialog() {
  selectedEvent.value = null
  pendingAction.value = null
  newRunOpen.value = false
  actionError.value = ''
  await restoreDialogFocus()
}

async function openEvent(event: OpenClawActivity, trigger?: Event) {
  rememberDialogTrigger(trigger)
  selectedEvent.value = event
  await nextTick()
  dialogClose.value?.focus()
}

async function askConfirmation(action: PendingAction, trigger?: Event) {
  rememberDialogTrigger(trigger)
  actionError.value = ''
  pendingAction.value = action
  await nextTick()
  dialogClose.value?.focus()
}

async function closeDialog() {
  if (actionBusy.value || runActionBusy.value) return
  await dismissDialog()
}

async function openNewRun(trigger?: Event) {
  if (!canOperate.value) return
  // Route-driven auto-open has no explicit trigger. In that case no fallback
  // target is stored, so the document body never becomes a return destination.
  rememberDialogTrigger(trigger)
  const requestedAgent = typeof route.query.agentId === 'string' ? route.query.agentId : ''
  newRunAgentId.value = requestedAgent ||
    overview.value?.agents.items[0]?.id ||
    'iris'
  newRunSessionKey.value = `agent:${newRunAgentId.value}:main`
  newRunTaskId.value = typeof route.query.taskId === 'string' ? route.query.taskId : ''
  newRunProjectId.value = typeof route.query.projectId === 'string' ? route.query.projectId : ''
  newRunTitle.value = ''
  newRunPrompt.value = ''
  startRunMutation.reset()
  newRunOpen.value = true
  await nextTick()
  dialogClose.value?.focus()
}

async function submitNewRun() {
  if (!canOperate.value) return
  const prompt = newRunPrompt.value.trim()
  const agentId = newRunAgentId.value.trim()
  const sessionKey = newRunSessionKey.value.trim()
  if (!prompt || !agentId || !sessionKey) return

  try {
    const result = await startRunMutation.mutateAsync({
      prompt,
      agentId,
      sessionKey,
      title: newRunTitle.value.trim() || null,
      taskId: newRunTaskId.value.trim() || null,
      projectId: newRunProjectId.value.trim() || null,
    })
    await dismissDialog()
    await router.push(`/runs/${result.resultRun?.id ?? result.run.id}`)
  } catch {
    // The store exposes the server-provided recovery message in the dialog.
  }
}

watch(newRunAgentId, agentId => {
  const previousDefault = /^agent:[^:]+:main$/.test(newRunSessionKey.value)
  if (!newRunSessionKey.value || previousDefault) {
    newRunSessionKey.value = `agent:${agentId || 'iris'}:main`
  }
})

async function executeAction() {
  const action = pendingAction.value
  if (!action || actionBusy.value) return
  actionBusy.value = true
  actionError.value = ''
  try {
    if (action.kind === 'cancel-task') await store.cancelTask(action.id)
    if (action.kind === 'abort-session') await store.abortSession(action.id, action.runId)
    if (action.kind === 'approval') await store.resolveApproval(action.approval, action.decision)
    if (action.kind === 'cron') {
      await runCronMutation.mutateAsync({
        jobId: action.job.id,
        expectedHash: action.job.resourceHash ?? '',
      })
      await store.refreshOverview({ quiet: true })
    }
    announcement.value = `${action.label} completed.`
    await dismissDialog()
  } catch (error) {
    actionError.value = error instanceof Error ? error.message : 'OpenClaw action failed'
  } finally {
    actionBusy.value = false
  }
}

function actionTitle(action: PendingAction): string {
  if (action.kind === 'cancel-task') return 'OpenClaw task stoppen?'
  if (action.kind === 'abort-session') return 'Aktiven Run abbrechen?'
  if (action.kind === 'cron') return 'Zeitplan jetzt starten?'
  return action.decision === 'deny' ? 'Aktion ablehnen?' : 'Aktion freigeben?'
}

function actionDescription(action: PendingAction): string {
  if (action.kind === 'abort-session') {
    return 'OpenClaw stoppt den aktiven Run und Nexus verwirft wartende Follow-ups dieser Session.'
  }
  if (action.kind === 'cancel-task') {
    return 'OpenClaw erhält einen echten Cancel-Request für diesen Ledger-Task.'
  }
  if (action.kind === 'cron') {
    return 'Der Job wird mit mode=force eingereiht. Seine fachlichen Side Effects bleiben aktiv.'
  }
  if (action.decision === 'allow-always') {
    return 'Diese Entscheidung kann dauerhafte Vertrauenswirkung haben. Nur verwenden, wenn der Approval-Text das ausdrücklich trägt.'
  }
  return action.decision === 'deny'
    ? 'OpenClaw blockiert die angefragte Aktion.'
    : 'OpenClaw erlaubt genau diese angefragte Aktion einmalig.'
}

function syncSelectionFromQuery() {
  const selectedSessionKey = typeof route.query.session === 'string' ? route.query.session : null
  const selectedTaskId = typeof route.query.task === 'string' ? route.query.task : null
  if (selectedSessionKey) selection.value = { kind: 'session', id: selectedSessionKey }
  else if (selectedTaskId) selection.value = { kind: 'task', id: selectedTaskId }
}

watch(
  [() => route.query.session, () => route.query.task],
  syncSelectionFromQuery,
  { immediate: true },
)

watch(requestedApprovalId, async approvalId => {
  if (!approvalId) return
  await nextTick()
  const target = document.getElementById(`approval-${approvalId}`)
  target?.scrollIntoView({ block: 'center', behavior: 'smooth' })
  target?.focus({ preventScroll: true })
}, { immediate: true })

onMounted(() => {
  if (route.query.new === '1' && canOperate.value) void openNewRun()
})
</script>

<template>
  <div class="run-control nexus-page nexus-page--workspace">
    <header class="run-header nexus-page-header">
      <div>
        <span class="eyebrow">OPENCLAW CONTROL PLANE</span>
        <h1>Run Control</h1>
        <p>Live tasks, sessions, approvals and recovery boundaries in one operator surface.</p>
      </div>
      <div class="run-actions">
        <span :class="['connection-badge', store.syncMode === 'live' ? 'positive' : 'warning']">
          <span class="connection-dot" aria-hidden="true"></span>
          {{ store.syncMode === 'live' ? 'Event stream' : 'Polling fallback' }}
        </span>
        <span :class="['connection-badge', statusTone(connection?.state || 'disconnected')]">
          <span class="connection-dot" aria-hidden="true"></span>
          {{ connection?.connected ? 'Protocol live' : connection?.state || 'Loading' }}
        </span>
        <button
          type="button"
          class="nexus-button"
          :disabled="overviewQuery.isFetching.value"
          @click="overviewQuery.refetch()"
        >
          <RefreshCw :size="14" :class="{ spin: overviewQuery.isFetching.value }" />
          Refresh
        </button>
        <button
          type="button"
          class="nexus-button nexus-button--primary"
          :disabled="!canOperate || !connection?.connected"
          @click="openNewRun($event)"
        >
          <Plus :size="14" />
          New run
        </button>
        <button
          type="button"
          class="nexus-button nexus-button--primary"
          @click="missionControlUi.openIris"
        >
          <Sparkles :size="14" />
          Ask Iris
        </button>
      </div>
    </header>

    <AsyncStatePanel
      v-if="overviewQuery.isPending.value && !overview"
      state="loading"
      title="OpenClaw Control Plane wird geladen"
      message="Gateway-Handshake und Capability-Aushandlung laufen."
    />

    <AsyncStatePanel
      v-else-if="overviewError && !overview"
      state="error"
      title="Run Control ist nicht erreichbar"
      :problem="overviewQuery.error.value"
      @action="overviewQuery.refetch()"
    />

    <AsyncStatePanel
      v-else-if="connection && !connection.connected"
      state="offline"
      :title="`OpenClaw Gateway ist ${connection.state}`"
      :message="connection.recovery || connection.message || 'Gateway-Konfiguration und Zugang müssen geprüft werden.'"
      action-label="Integration prüfen"
      @action="router.push('/settings')"
    />

    <template v-else-if="overview">
      <section class="run-strip" aria-label="OpenClaw control-plane status">
        <div>
          <Network :size="15" />
          <span><small>Gateway</small><strong>{{ connection?.gatewayVersion || 'unknown' }}</strong></span>
        </div>
        <div>
          <Workflow :size="15" />
          <span><small>Protocol</small><strong>v{{ connection?.protocolVersion || '—' }}</strong></span>
        </div>
        <div>
          <ShieldCheck :size="15" />
          <span><small>Capabilities</small><strong>{{ readyCapabilityCount }}/{{ capabilityCount }}</strong></span>
        </div>
        <div>
          <Activity :size="15" />
          <span><small>Active work</small><strong>{{ activeTasks.length + activeSessions.length }}</strong></span>
        </div>
        <div>
          <KeyRound :size="15" />
          <span><small>Pending approvals</small><strong>{{ pendingApprovals.length }}</strong></span>
        </div>
      </section>

      <section class="durable-run-strip" aria-labelledby="durable-runs-title">
        <header>
          <span>
            <History :size="14" aria-hidden="true" />
            <strong id="durable-runs-title">Durable runs</strong>
          </span>
          <small>{{ activeRunCount }} active · persisted by Nexus</small>
        </header>
        <div v-if="runsQuery.isPending.value && !durableRuns.length" class="durable-run-empty">
          <Loader2 :size="14" class="spin" /> Loading run ledger…
        </div>
        <div v-else-if="runsQueryError && !durableRuns.length" class="durable-run-empty danger">
          {{ runsQueryError }}
        </div>
        <div v-else-if="durableRuns.length" class="durable-run-list">
          <RouterLink
            v-for="run in durableRuns"
            :key="run.id"
            :to="`/runs/${run.id}`"
            class="durable-run"
          >
            <span :class="['event-dot', statusTone(run.status)]" aria-hidden="true"></span>
            <span><strong>{{ run.title }}</strong><small>{{ run.agentId }} · {{ formatDate(run.updatedAt) }}</small></span>
            <code>{{ run.status }}</code>
            <ChevronRight :size="13" aria-hidden="true" />
          </RouterLink>
        </div>
        <div v-else class="durable-run-empty">No durable Nexus run has been dispatched yet.</div>
      </section>

      <div class="run-layout">
        <main class="run-primary">
          <section class="orchestration-panel" aria-labelledby="live-orchestration-title">
            <header class="panel-head">
              <div>
                <span class="eyebrow">LIVE ORCHESTRATION</span>
                <h2 id="live-orchestration-title">Gateway work graph</h2>
              </div>
              <span class="panel-meta"><GitBranch :size="14" /> {{ workNodes.length }} visible nodes</span>
            </header>

            <div class="orchestration-canvas">
              <button
                type="button"
                :class="['orchestrator-node', { selected: selection?.kind === 'agent' && selection.id === 'iris' }]"
                :aria-pressed="selection?.kind === 'agent' && selection.id === 'iris'"
                @click="selectNode({ kind: 'agent', id: 'iris', title: 'Iris' })"
              >
                <span class="node-icon"><Sparkles :size="18" /></span>
                <span><strong>Iris</strong><small>OpenClaw orchestrator</small></span>
                <span :class="['node-state', statusTone(connection?.state || 'disconnected')]">{{ connection?.state }}</span>
              </button>

              <div class="branch-label" aria-hidden="true"><GitBranch :size="17" /> Active branches</div>

              <div v-if="workNodes.length" class="work-grid">
                <button
                  v-for="node in workNodes"
                  :key="`${node.kind}:${node.id}`"
                  type="button"
                  :class="['work-node', { selected: selection?.kind === node.kind && selection.id === node.id }]"
                  :aria-pressed="selection?.kind === node.kind && selection.id === node.id"
                  @click="selectNode(node)"
                >
                  <span class="node-head">
                    <span class="node-icon"><Bot v-if="node.kind === 'agent'" :size="16" /><Activity v-else :size="16" /></span>
                    <span><strong>{{ node.title }}</strong><small>{{ node.subtitle }}</small></span>
                    <i :class="statusTone(node.status)" :aria-label="node.status"></i>
                  </span>
                  <span class="node-detail">{{ node.detail }}</span>
                  <span class="node-foot"><span>{{ node.kind }}</span><strong>{{ node.status }}</strong></span>
                </button>
              </div>

              <div v-else class="canvas-empty">
                <CheckCircle2 :size="24" />
                <strong>No active OpenClaw work</strong>
                <span>The Gateway is connected and has no visible task or session nodes.</span>
              </div>

              <button
                v-if="pendingApprovals.length"
                type="button"
                class="approval-boundary"
                @click="selection = null"
              >
                <KeyRound :size="18" />
                <span><small>OWNER BOUNDARY</small><strong>{{ pendingApprovals.length }} approval{{ pendingApprovals.length === 1 ? '' : 's' }} waiting</strong></span>
                <ChevronRight :size="15" />
              </button>
            </div>
          </section>

          <section class="activity-panel" aria-labelledby="run-activity-title">
            <header class="panel-head">
              <div>
                <span class="eyebrow">AUTHORITATIVE ACTIVITY</span>
                <h2 id="run-activity-title">Recent events</h2>
              </div>
              <RouterLink to="/activity" class="panel-link">Full activity <ExternalLink :size="13" /></RouterLink>
            </header>
            <div v-if="overview.activity.items.length" class="event-list">
              <button
                v-for="event in overview.activity.items.slice(0, 8)"
                :key="event.id"
                type="button"
                class="event-row"
                @click="openEvent(event, $event)"
              >
                <time>{{ formatDate(event.occurredAt) }}</time>
                <span :class="['event-dot', statusTone(event.status)]"></span>
                <span><strong>{{ event.action }}</strong><small>{{ event.message }}</small></span>
                <ChevronRight :size="14" />
              </button>
            </div>
            <div v-else class="compact-empty">{{ overview.activity.message || 'No OpenClaw activity recorded.' }}</div>
          </section>

          <section class="automation-panel" aria-labelledby="run-automation-title">
            <header class="panel-head">
              <div>
                <span class="eyebrow">AUTOMATION</span>
                <h2 id="run-automation-title">Scheduled jobs</h2>
              </div>
              <RouterLink to="/calendar" class="panel-link">Calendar <ExternalLink :size="13" /></RouterLink>
            </header>
            <div v-if="cronCollection?.items.length" class="cron-grid">
              <article v-for="job in cronCollection.items.slice(0, 4)" :key="job.id">
                <span :class="['event-dot', statusTone(job.status)]"></span>
                <div><strong>{{ job.name }}</strong><small>{{ job.schedule }} · {{ job.nextRunAt ? formatDate(job.nextRunAt) : 'No next run' }}</small></div>
                <button
                  type="button"
                  class="icon-action"
                  :disabled="!canOperate || !job.canRun"
                  :title="job.canRun ? 'Run now' : 'operator.admin and cron.run are required'"
                  :aria-label="`Run ${job.name} now`"
                  @click="askConfirmation({ kind: 'cron', job, label: job.name }, $event)"
                ><Play :size="14" /></button>
              </article>
            </div>
            <div v-else class="compact-empty">{{ cronCollection?.message || 'No scheduled jobs.' }}</div>
          </section>
        </main>

        <aside class="run-inspector" aria-label="Run inspector and approvals">
          <section class="inspector-selection">
            <header>
              <span class="inspector-icon"><Bot v-if="selectedAgent" :size="18" /><Activity v-else :size="18" /></span>
              <span>
                <strong>{{ selectedTask?.title || selectedSession?.title || selectedAgent?.name || 'Select a node' }}</strong>
                <small>{{ selectedTask?.status || selectedSession?.status || selectedAgent?.status || 'Inspect live context' }}</small>
              </span>
            </header>

            <template v-if="selectedTask">
              <dl>
                <div><dt>Task ID</dt><dd>{{ shortId(selectedTask.id) }}</dd></div>
                <div><dt>Agent</dt><dd>{{ selectedTask.agentId || 'unassigned' }}</dd></div>
                <div><dt>Run</dt><dd>{{ selectedTask.runId ? shortId(selectedTask.runId) : '—' }}</dd></div>
                <div><dt>Updated</dt><dd>{{ formatDate(selectedTask.updatedAt) }}</dd></div>
              </dl>
              <p v-if="selectedTask.error" class="inspector-error">{{ selectedTask.error }}</p>
              <button
                type="button"
                class="nexus-button nexus-button--danger inspector-action"
                :disabled="!canOperate || !selectedTask.canCancel"
                @click="askConfirmation({ kind: 'cancel-task', id: selectedTask.id, label: selectedTask.title }, $event)"
              ><CircleStop :size="14" /> Cancel task</button>
            </template>

            <template v-else-if="selectedSession">
              <dl>
                <div><dt>Session</dt><dd>{{ shortId(selectedSession.key) }}</dd></div>
                <div><dt>Agent</dt><dd>{{ selectedSession.agentId }}</dd></div>
                <div><dt>Model</dt><dd>{{ selectedSession.model || 'default' }}</dd></div>
                <div><dt>Tokens</dt><dd>{{ selectedSession.totalTokens?.toLocaleString('de-DE') || '—' }}</dd></div>
              </dl>
              <button
                type="button"
                class="nexus-button nexus-button--danger inspector-action"
                :disabled="!canOperate || !selectedSession.canAbort"
                @click="askConfirmation({ kind: 'abort-session', id: selectedSession.key, runId: selectedSession.runId, label: selectedSession.title }, $event)"
              ><CircleStop :size="14" /> Stop run</button>
            </template>

            <template v-else-if="selectedAgent">
              <dl>
                <div><dt>Agent ID</dt><dd>{{ selectedAgent.id }}</dd></div>
                <div><dt>Model</dt><dd>{{ selectedAgent.model || 'default' }}</dd></div>
                <div><dt>Provider</dt><dd>{{ selectedAgent.provider || 'OpenClaw' }}</dd></div>
                <div><dt>Workspace</dt><dd>{{ selectedAgent.workspace ? shortId(selectedAgent.workspace) : 'Gateway-owned' }}</dd></div>
              </dl>
              <RouterLink :to="`/agents/${encodeURIComponent(selectedAgent.id)}`" class="nexus-button inspector-action">
                Agent details <ChevronRight :size="14" />
              </RouterLink>
            </template>

            <p v-else class="selection-hint">Choose an agent, task, or active session from the orchestration surface.</p>
          </section>

          <section class="approval-queue">
            <header>
              <div><span class="eyebrow">OWNER QUEUE</span><h2>Approvals</h2></div>
              <span class="count-badge">{{ pendingApprovals.length }}</span>
            </header>
            <div v-if="pendingApprovals.length" class="approval-list">
              <article
                v-for="approval in orderedPendingApprovals.slice(0, 4)"
                :id="`approval-${approval.id}`"
                :key="approval.id"
                :class="{ 'is-deep-linked': approval.id === requestedApprovalId }"
                tabindex="-1"
              >
                <div class="approval-title">
                  <span :class="['approval-icon', approval.severity]"><KeyRound :size="15" /></span>
                  <span><strong>{{ approval.title }}</strong><small>{{ approval.kind }} · {{ approval.severity }}</small></span>
                </div>
                <code v-if="approval.command">{{ approval.command }}</code>
                <p v-if="approval.description">{{ approval.description }}</p>
                <div class="approval-actions">
                  <button
                    type="button"
                    class="nexus-button"
                    :disabled="!canOperate || !approval.canResolve"
                    @click="askConfirmation({ kind: 'approval', approval, decision: 'deny', label: approval.title }, $event)"
                  >Deny</button>
                  <button
                    v-if="approval.allowedDecisions.includes('allow-once')"
                    type="button"
                    class="nexus-button nexus-button--primary"
                    :disabled="!canOperate || !approval.canResolve"
                    @click="askConfirmation({ kind: 'approval', approval, decision: 'allow-once', label: approval.title }, $event)"
                  >Allow once</button>
                </div>
              </article>
            </div>
            <div v-else class="compact-empty">{{ overview.approvals.message || 'No approval is waiting.' }}</div>
          </section>

          <section class="capability-summary">
            <header><span class="eyebrow">GATEWAY CONTRACT</span><h2>Capabilities</h2></header>
            <div>
              <span v-for="capability in overview.capabilities" :key="capability.id" :class="statusTone(capability.state)">
                <i></i>{{ capability.label }}
              </span>
            </div>
            <RouterLink to="/settings">OpenClaw diagnostics <ChevronRight :size="13" /></RouterLink>
          </section>
        </aside>
      </div>
    </template>

    <p class="sr-only" aria-live="polite">{{ announcement }}</p>

    <Teleport to="body">
      <div
        v-if="selectedEvent || pendingAction || newRunOpen"
        class="run-dialog-overlay"
        @click.self="closeDialog"
        @keydown.esc.stop.prevent="closeDialog"
      >
        <section
          class="run-dialog"
          role="dialog"
          aria-modal="true"
          :aria-labelledby="newRunOpen ? 'new-run-dialog-title' : pendingAction ? 'action-dialog-title' : 'event-dialog-title'"
        >
          <header>
            <div>
              <span class="eyebrow">{{ newRunOpen ? 'DURABLE DISPATCH' : pendingAction ? 'OPERATOR CONFIRMATION' : 'ACTIVITY DETAIL' }}</span>
              <h2 v-if="newRunOpen" id="new-run-dialog-title">Start OpenClaw run</h2>
              <h2 v-else-if="pendingAction" id="action-dialog-title">{{ actionTitle(pendingAction) }}</h2>
              <h2 v-else id="event-dialog-title">{{ selectedEvent?.action }}</h2>
            </div>
            <button ref="dialogClose" type="button" aria-label="Close dialog" :disabled="actionBusy || runActionBusy" @click="closeDialog"><X :size="18" /></button>
          </header>

          <form v-if="newRunOpen" class="dialog-body run-create-form" @submit.prevent="submitNewRun">
            <p>
              Nexus persists this run, its actor, correlation, lifecycle transitions and OpenClaw event cursor.
            </p>
            <label>
              <span>Title <small>optional</small></span>
              <input v-model="newRunTitle" type="text" maxlength="160" placeholder="e.g. Review task and propose next actions" />
            </label>
            <label>
              <span>Prompt</span>
              <textarea v-model="newRunPrompt" rows="5" maxlength="50000" required placeholder="Give the agent a bounded objective, constraints and expected output."></textarea>
            </label>
            <div class="run-create-grid">
              <label>
                <span>Agent</span>
                <select v-model="newRunAgentId" required>
                  <option
                    v-for="agent in overview?.agents.items ?? []"
                    :key="agent.id"
                    :value="agent.id"
                  >{{ agent.name }} · {{ agent.id }}</option>
                  <option v-if="!(overview?.agents.items.length)" value="iris">Iris · iris</option>
                </select>
              </label>
              <label>
                <span>Session key</span>
                <input v-model="newRunSessionKey" type="text" maxlength="500" required />
              </label>
              <label>
                <span>Task ID <small>optional</small></span>
                <input v-model="newRunTaskId" type="text" placeholder="Nexus task UUID" />
              </label>
              <label>
                <span>Project ID <small>optional</small></span>
                <input v-model="newRunProjectId" type="text" placeholder="Nexus project UUID" />
              </label>
            </div>
            <p v-if="runActionError" class="dialog-error" role="alert">{{ runActionError }}</p>
            <div class="dialog-actions">
              <button type="button" class="nexus-button" :disabled="runActionBusy" @click="closeDialog">Cancel</button>
              <button
                type="submit"
                class="nexus-button nexus-button--primary"
                :disabled="!canOperate || runActionBusy || !newRunPrompt.trim() || !newRunAgentId.trim() || !newRunSessionKey.trim()"
              >
                <Loader2 v-if="runActionBusy" :size="14" class="spin" />
                <Play v-else :size="14" />
                Dispatch run
              </button>
            </div>
          </form>

          <div v-else-if="pendingAction" class="dialog-body">
            <div class="dialog-warning"><AlertTriangle :size="18" /><p>{{ actionDescription(pendingAction) }}</p></div>
            <dl>
              <div><dt>Target</dt><dd>{{ pendingAction.label }}</dd></div>
              <div><dt>Authority</dt><dd>Authenticated Nexus owner</dd></div>
              <div><dt>Transport</dt><dd>Backend → OpenClaw protocol v4</dd></div>
            </dl>
            <p v-if="actionError" class="dialog-error" role="alert">{{ actionError }}</p>
            <div class="dialog-actions">
              <button type="button" class="nexus-button" :disabled="actionBusy" @click="closeDialog">Cancel</button>
              <button type="button" class="nexus-button nexus-button--danger" :disabled="actionBusy" @click="executeAction">
                <Loader2 v-if="actionBusy" :size="14" class="spin" />
                <ShieldCheck v-else :size="14" />
                Confirm
              </button>
            </div>
          </div>

          <div v-else-if="selectedEvent" class="dialog-body">
            <dl>
              <div><dt>Event ID</dt><dd><code>{{ selectedEvent.id }}</code></dd></div>
              <div><dt>Source</dt><dd>{{ selectedEvent.source }}</dd></div>
              <div><dt>Status</dt><dd>{{ selectedEvent.status }}</dd></div>
              <div><dt>Actor</dt><dd>{{ selectedEvent.actor || selectedEvent.agentId || 'OpenClaw' }}</dd></div>
              <div><dt>Session</dt><dd>{{ selectedEvent.sessionKey || '—' }}</dd></div>
              <div><dt>Run</dt><dd>{{ selectedEvent.runId || '—' }}</dd></div>
              <div><dt>Occurred</dt><dd>{{ formatDate(selectedEvent.occurredAt) }}</dd></div>
            </dl>
            <p class="event-message">{{ selectedEvent.message }}</p>
          </div>
        </section>
      </div>
    </Teleport>
  </div>
</template>

<style scoped>
.run-control {
  width: min(100%, 1440px);
  margin-inline: auto;
  font-size: 12px;
}

.run-control :where(
  small,
  code,
  time,
  .eyebrow,
  .panel-meta,
  .node-state,
  .node-detail,
  .node-foot,
  .count-badge,
  dt,
  dd
) {
  font-size: 11px !important;
}

.run-control :where(
  .event-row strong,
  .cron-grid strong,
  .approval-title strong,
  .approval-boundary strong,
  .canvas-empty span,
  .dialog-warning p,
  .dialog-error
) {
  font-size: 12px !important;
}

.run-header h1 {
  margin: 3px 0 2px;
}

.run-header p {
  margin: 0;
  color: var(--tx-2);
  font-size: 12px;
}

.run-actions,
.connection-badge,
.panel-meta,
.panel-link {
  display: flex;
  align-items: center;
}

.run-actions {
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px;
}

.connection-badge {
  min-height: 34px;
  gap: 7px;
  padding: 0 10px;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  background: var(--accent-wash);
  color: var(--tx-2);
  font-family: var(--font-mono-v2);
  font-size: 10px;
  text-transform: uppercase;
}

.connection-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
}

.connection-badge.positive { color: var(--st-work); }
.connection-badge.warning { color: var(--st-queue); }
.connection-badge.danger { color: var(--st-block); }

.disconnected-state {
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
}

.disconnected-state code {
  display: inline-block;
  margin-top: 8px;
  color: var(--tx-3);
  font-family: var(--font-mono-v2);
  font-size: 11px;
}

.run-strip {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--glass);
  backdrop-filter: blur(12px);
}

.run-strip > div {
  min-width: 0;
  min-height: 54px;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 9px 12px;
  border-right: 1px solid var(--line);
  color: var(--a-mid);
}

.run-strip > div:last-child { border-right: 0; }
.run-strip span { min-width: 0; }
.run-strip small,
.run-strip strong { display: block; }
.run-strip small { color: var(--tx-3); font-size: 9px; }
.run-strip strong {
  margin-top: 2px;
  overflow: hidden;
  color: var(--tx);
  font-family: var(--font-mono-v2);
  font-size: 10px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.durable-run-strip {
  display: grid;
  grid-template-columns: 190px minmax(0, 1fr);
  min-height: 62px;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--glass);
  backdrop-filter: blur(12px);
}

.durable-run-strip > header {
  display: grid;
  align-content: center;
  gap: 4px;
  padding: 10px 13px;
  border-right: 1px solid var(--line);
}

.durable-run-strip > header span {
  display: flex;
  align-items: center;
  gap: 7px;
  color: var(--a-mid);
}

.durable-run-strip > header strong {
  color: var(--tx);
  font-family: var(--font-display);
  font-size: 12px;
}

.durable-run-strip > header small {
  color: var(--tx-3);
  font-family: var(--font-mono-v2);
  font-size: 9px;
}

.durable-run-list {
  min-width: 0;
  display: flex;
  overflow-x: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--accent-scroll) transparent;
}

.durable-run {
  min-width: 210px;
  max-width: 280px;
  flex: 1 0 210px;
  display: grid;
  grid-template-columns: 7px minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 8px;
  padding: 9px 11px;
  border-right: 1px solid var(--line);
  color: var(--tx);
  text-decoration: none;
}

.durable-run:hover {
  background: var(--accent-wash);
}

.durable-run > span:nth-child(2) {
  min-width: 0;
}

.durable-run strong,
.durable-run small {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.durable-run strong {
  font-size: 10.5px;
}

.durable-run small {
  margin-top: 3px;
  color: var(--tx-3);
  font-size: 8.5px;
}

.durable-run code {
  color: var(--tx-3);
  font-family: var(--font-mono-v2);
  font-size: 8px;
  text-transform: uppercase;
}

.durable-run-empty {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 12px;
  color: var(--tx-3);
  font-size: 10px;
}

.durable-run-empty.danger {
  color: var(--st-block);
}

.run-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 310px;
  gap: 12px;
  align-items: start;
}

.run-primary {
  min-width: 0;
  display: grid;
  gap: 12px;
}

.orchestration-panel,
.activity-panel,
.automation-panel,
.run-inspector > section {
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--glass);
  box-shadow: var(--panel-shadow);
  backdrop-filter: blur(14px);
}

.panel-head {
  min-height: 58px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 11px 14px;
  border-bottom: 1px solid var(--line);
}

.panel-head h2,
.run-inspector h2 {
  margin: 3px 0 0;
  font-family: var(--font-display);
  font-size: 15px;
}

.panel-meta,
.panel-link {
  gap: 6px;
  color: var(--tx-3);
  font-family: var(--font-mono-v2);
  font-size: 10px;
  text-decoration: none;
}

.panel-link:hover { color: var(--tx); }

.orchestration-canvas {
  min-height: 480px;
  display: grid;
  grid-template-rows: auto auto 1fr auto;
  align-content: center;
  gap: 13px;
  padding: 18px;
}

.orchestrator-node,
.work-node,
.approval-boundary {
  border: 1px solid var(--line);
  background: color-mix(in srgb, var(--space-2) 70%, transparent);
  color: var(--tx);
  cursor: pointer;
}

.orchestrator-node {
  width: min(390px, 100%);
  min-height: 64px;
  justify-self: center;
  display: grid;
  grid-template-columns: 36px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  padding: 10px;
  border-radius: 12px;
  text-align: left;
}

.orchestrator-node:hover,
.orchestrator-node.selected,
.work-node:hover,
.work-node.selected {
  border-color: var(--line-3);
  background: var(--grad-soft);
}

.node-icon,
.inspector-icon,
.approval-icon {
  display: grid;
  place-items: center;
  border: 1px solid var(--line-2);
  background: var(--grad-soft);
  color: var(--a-mid);
}

.node-icon {
  width: 34px;
  height: 34px;
  border-radius: 9px;
}

.orchestrator-node strong,
.orchestrator-node small {
  display: block;
}

.orchestrator-node strong { font-size: 12px; }
.orchestrator-node small { margin-top: 2px; color: var(--tx-3); font-size: 10px; }
.node-state {
  color: var(--tx-3);
  font-family: var(--font-mono-v2);
  font-size: 9px;
  text-transform: uppercase;
}
.node-state.positive { color: var(--st-work); }

.branch-label {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  color: var(--tx-3);
  font-family: var(--font-mono-v2);
  font-size: 9px;
  text-transform: uppercase;
}

.work-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 9px;
  align-content: center;
}

.work-node {
  min-width: 0;
  min-height: 116px;
  display: grid;
  gap: 9px;
  padding: 11px;
  border-radius: 12px;
  text-align: left;
}

.node-head {
  min-width: 0;
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr) auto;
  align-items: center;
  gap: 8px;
}

.node-head > span:nth-child(2) { min-width: 0; }
.node-head strong,
.node-head small { display: block; }
.node-head strong {
  overflow: hidden;
  font-size: 11px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.node-head small {
  margin-top: 2px;
  overflow: hidden;
  color: var(--tx-3);
  font-size: 9px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.node-head i,
.event-dot,
.capability-summary i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--st-idle);
}
.node-head i.positive,
.event-dot.positive,
.capability-summary span.positive i { background: var(--st-work); }
.node-head i.warning,
.event-dot.warning,
.capability-summary span.warning i { background: var(--st-queue); }
.node-head i.danger,
.event-dot.danger,
.capability-summary span.danger i { background: var(--st-block); }

.node-detail {
  overflow: hidden;
  color: var(--tx-2);
  font-size: 10px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.node-foot {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  color: var(--tx-3);
  font-family: var(--font-mono-v2);
  font-size: 8px;
  text-transform: uppercase;
}
.node-foot strong { color: var(--tx-2); }

.canvas-empty {
  display: grid;
  place-items: center;
  align-content: center;
  gap: 8px;
  min-height: 220px;
  color: var(--st-work);
  text-align: center;
}
.canvas-empty strong { color: var(--tx); font-size: 12px; }
.canvas-empty span { color: var(--tx-3); font-size: 10px; }

.approval-boundary {
  width: min(700px, 100%);
  min-height: 54px;
  justify-self: center;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-color: var(--status-queue-line);
  border-radius: 11px;
  background: var(--status-queue-bg);
  color: var(--st-queue);
  text-align: left;
}
.approval-boundary small,
.approval-boundary strong { display: block; }
.approval-boundary small { font-family: var(--font-mono-v2); font-size: 8px; }
.approval-boundary strong { margin-top: 2px; color: var(--tx); font-size: 10px; }

.event-list { display: grid; }
.event-row {
  min-width: 0;
  display: grid;
  grid-template-columns: 112px 8px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  min-height: 48px;
  padding: 7px 13px;
  border: 0;
  border-bottom: 1px solid var(--line);
  background: transparent;
  color: var(--tx);
  text-align: left;
  cursor: pointer;
}
.event-row:last-child { border-bottom: 0; }
.event-row:hover { background: var(--accent-wash); }
.event-row time { color: var(--tx-3); font-family: var(--font-mono-v2); font-size: 9px; }
.event-row > span:nth-child(3) { min-width: 0; }
.event-row strong,
.event-row small { display: block; }
.event-row strong { font-size: 10px; }
.event-row small {
  margin-top: 2px;
  overflow: hidden;
  color: var(--tx-3);
  font-size: 9px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cron-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.cron-grid article {
  min-width: 0;
  display: grid;
  grid-template-columns: 8px minmax(0, 1fr) auto;
  align-items: center;
  gap: 9px;
  min-height: 60px;
  padding: 9px 13px;
  border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.cron-grid article:nth-child(2n) { border-right: 0; }
.cron-grid article:nth-last-child(-n + 2) { border-bottom: 0; }
.cron-grid strong,
.cron-grid small { display: block; }
.cron-grid strong { font-size: 10px; }
.cron-grid small {
  margin-top: 3px;
  overflow: hidden;
  color: var(--tx-3);
  font-family: var(--font-mono-v2);
  font-size: 8px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.icon-action {
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: transparent;
  color: var(--tx-2);
  cursor: pointer;
}
.icon-action:disabled { opacity: .4; cursor: not-allowed; }

.compact-empty {
  padding: 22px;
  color: var(--tx-3);
  font-size: 10px;
  text-align: center;
}

.run-inspector {
  position: sticky;
  top: 0;
  display: grid;
  gap: 12px;
}

.run-inspector > section {
  overflow: hidden;
}

.inspector-selection > header {
  display: grid;
  grid-template-columns: 38px minmax(0, 1fr);
  align-items: center;
  gap: 9px;
  padding: 12px;
  border-bottom: 1px solid var(--line);
}
.inspector-icon {
  width: 38px;
  height: 38px;
  border-radius: 10px;
}
.inspector-selection header strong,
.inspector-selection header small { display: block; }
.inspector-selection header strong { font-size: 11px; }
.inspector-selection header small { margin-top: 2px; color: var(--tx-3); font-size: 9px; }

.inspector-selection dl,
.run-dialog dl {
  margin: 0;
  padding: 10px 12px;
}
.inspector-selection dl div,
.run-dialog dl div {
  display: grid;
  grid-template-columns: 90px minmax(0, 1fr);
  gap: 9px;
  padding: 7px 0;
  border-bottom: 1px solid var(--line);
}
.inspector-selection dl div:last-child,
.run-dialog dl div:last-child { border-bottom: 0; }
.inspector-selection dt,
.run-dialog dt { color: var(--tx-3); font-size: 9px; }
.inspector-selection dd,
.run-dialog dd {
  margin: 0;
  overflow: hidden;
  color: var(--tx-2);
  font-family: var(--font-mono-v2);
  font-size: 9px;
  text-align: right;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.inspector-action {
  width: calc(100% - 24px);
  margin: 0 12px 12px;
}
.selection-hint,
.inspector-error {
  margin: 0;
  padding: 14px 12px;
  color: var(--tx-3);
  font-size: 10px;
  line-height: 1.5;
}
.inspector-error { color: var(--st-block); }

.approval-queue > header,
.capability-summary > header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 12px;
  border-bottom: 1px solid var(--line);
}
.count-badge {
  min-width: 24px;
  min-height: 24px;
  display: grid;
  place-items: center;
  border: 1px solid var(--status-queue-line);
  border-radius: 999px;
  background: var(--status-queue-bg);
  color: var(--st-queue);
  font-family: var(--font-mono-v2);
  font-size: 10px;
}
.approval-list { display: grid; }
.approval-list article {
  padding: 11px 12px;
  border-bottom: 1px solid var(--line);
}
.approval-list article:last-child { border-bottom: 0; }
.approval-list article.is-deep-linked {
  border-radius: 10px;
  background: color-mix(in srgb, var(--a-blue) 10%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--a-blue) 35%, transparent);
}
.approval-list article:focus-visible { outline: 2px solid var(--a-blue); outline-offset: 2px; }
.approval-title {
  display: grid;
  grid-template-columns: 32px minmax(0, 1fr);
  gap: 8px;
  align-items: center;
}
.approval-icon {
  width: 32px;
  height: 32px;
  border-radius: 9px;
  color: var(--st-queue);
}
.approval-icon.critical { color: var(--st-block); }
.approval-title strong,
.approval-title small { display: block; }
.approval-title strong { font-size: 10px; }
.approval-title small { margin-top: 2px; color: var(--tx-3); font-size: 8px; }
.approval-list code,
.approval-list p {
  display: block;
  margin: 8px 0 0;
  overflow: hidden;
  color: var(--tx-2);
  font-size: 9px;
  line-height: 1.45;
  text-overflow: ellipsis;
}
.approval-list code {
  padding: 6px 7px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: color-mix(in srgb, var(--space-0) 38%, transparent);
  font-family: var(--font-mono-v2);
  white-space: nowrap;
}
.approval-actions {
  display: flex;
  justify-content: flex-end;
  gap: 6px;
  margin-top: 9px;
}

.capability-summary > div {
  display: grid;
  gap: 7px;
  padding: 11px 12px;
}
.capability-summary > div span {
  display: flex;
  align-items: center;
  gap: 7px;
  color: var(--tx-2);
  font-size: 9px;
}
.capability-summary > a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 12px;
  border-top: 1px solid var(--line);
  color: var(--a-mid);
  font-size: 9px;
  text-decoration: none;
}

.run-dialog-overlay {
  position: fixed;
  inset: 0;
  z-index: 700;
  display: grid;
  place-items: center;
  padding: 20px;
  background: color-mix(in srgb, var(--space-0) 78%, transparent);
  backdrop-filter: blur(14px);
}

.run-dialog {
  width: min(560px, 100%);
  max-height: calc(100dvh - 40px);
  overflow-y: auto;
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  background: color-mix(in srgb, var(--space-1) 96%, transparent);
  box-shadow: var(--panel-shadow), var(--glow-purple);
}

.run-dialog > header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 15px 17px;
  border-bottom: 1px solid var(--line);
}
.run-dialog h2 { margin: 3px 0 0; font-family: var(--font-display); font-size: 17px; }
.run-dialog header button {
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: transparent;
  color: var(--tx-2);
  cursor: pointer;
}
.dialog-body { padding: 16px; }
.run-create-form {
  display: grid;
  gap: 12px;
}

.run-create-form > p {
  margin: 0;
  color: var(--tx-2);
  font-size: 10.5px;
  line-height: 1.55;
}

.run-create-form label {
  min-width: 0;
  display: grid;
  gap: 6px;
}

.run-create-form label > span {
  color: var(--tx-2);
  font-family: var(--font-mono-v2);
  font-size: 9.5px;
  font-weight: 700;
  text-transform: uppercase;
}

.run-create-form label small {
  color: var(--tx-3);
  font-size: 8px;
  font-weight: 500;
}

.run-create-form :is(input, select, textarea) {
  width: 100%;
  min-width: 0;
  border: 1px solid var(--line-2);
  border-radius: 10px;
  background: var(--field-surface);
  color: var(--tx);
  font-family: var(--font-body);
  font-size: 11.5px;
}

.run-create-form :is(input, select) {
  min-height: 38px;
  padding: 0 10px;
}

.run-create-form textarea {
  min-height: 110px;
  resize: vertical;
  padding: 10px;
  line-height: 1.5;
}

.run-create-form :is(input, select, textarea):focus {
  outline: none;
  border-color: var(--line-3);
  box-shadow: var(--focus-ring);
}

.run-create-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}
.dialog-warning {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 11px;
  border: 1px solid var(--status-queue-line);
  border-radius: 10px;
  background: var(--status-queue-bg);
  color: var(--st-queue);
}
.dialog-warning p { margin: 0; color: var(--tx-2); font-size: 10px; line-height: 1.5; }
.dialog-error {
  margin: 10px 0 0;
  padding: 9px;
  border: 1px solid var(--status-block-line);
  border-radius: 9px;
  background: var(--status-block-bg);
  color: var(--st-block);
  font-size: 10px;
}
.dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 14px;
}
.event-message {
  margin: 12px 0 0;
  padding: 11px;
  border: 1px solid var(--line);
  border-radius: 9px;
  color: var(--tx-2);
  font-size: 11px;
  line-height: 1.55;
}

.spin { animation: run-spin 1s linear infinite; }
@keyframes run-spin { to { transform: rotate(360deg); } }

@media (max-width: 1180px) {
  .work-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 1024px) {
  .run-layout { grid-template-columns: minmax(0, 1fr); }
  .run-inspector { position: static; grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (max-width: 767px) {
  .run-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .run-strip > div { border-bottom: 1px solid var(--line); }
  .run-strip > div:nth-child(2n) { border-right: 0; }
  .work-grid,
  .cron-grid,
  .run-inspector { grid-template-columns: minmax(0, 1fr); }
  .cron-grid article { border-right: 0; }
  .event-row { grid-template-columns: 8px minmax(0, 1fr) auto; }
  .event-row time { grid-column: 2; grid-row: 2; }
  .event-row .event-dot { grid-row: 1 / span 2; }
  .disconnected-state { grid-template-columns: auto minmax(0, 1fr); }
  .disconnected-state > a { grid-column: 2; justify-self: start; }
  .durable-run-strip { grid-template-columns: minmax(0, 1fr); }
  .durable-run-strip > header { border-right: 0; border-bottom: 1px solid var(--line); }
  .run-create-grid { grid-template-columns: minmax(0, 1fr); }
}
</style>
