src/devtools/tabs/record-tab.tsx

Total Symbols
11
Lines of Code
252
Avg Complexity
2.8
Symbol Types
1

File Relationships

graph LR startRecording["startRecording"] renderRecordTab["renderRecordTab"] stopRecording["stopRecording"] pauseRecording["pauseRecording"] resumeRecording["resumeRecording"] clearRecording["clearRecording"] removeRecordStep["removeRecordStep"] updateRecordStep["updateRecordStep"] refreshRecordPreview["refreshRecordPreview"] exportRecording["exportRecording"] renderRecordStepsTable["renderRecordStepsTable"] startRecording -->|calls| renderRecordTab stopRecording -->|calls| renderRecordTab pauseRecording -->|calls| renderRecordTab resumeRecording -->|calls| renderRecordTab clearRecording -->|calls| renderRecordTab removeRecordStep -->|calls| renderRecordTab updateRecordStep -->|calls| renderRecordTab refreshRecordPreview -->|calls| renderRecordTab exportRecording -->|calls| renderRecordTab renderRecordTab -->|calls| clearRecording renderRecordTab -->|calls| exportRecording renderRecordTab -->|calls| renderRecordTab renderRecordTab -->|calls| removeRecordStep renderRecordTab -->|calls| updateRecordStep renderRecordStepsTable -->|calls| renderRecordTab click startRecording "../symbols/951da195ca6b9271.html" click renderRecordTab "../symbols/55c03c4dd1c7d324.html" click stopRecording "../symbols/d85adc1465d21faf.html" click pauseRecording "../symbols/2bbe3bc67dec8040.html" click resumeRecording "../symbols/ed6b9b4a8af56037.html" click clearRecording "../symbols/f6f301d293745036.html" click removeRecordStep "../symbols/21541bd72fefd844.html" click updateRecordStep "../symbols/fef88f8a2b53a954.html" click refreshRecordPreview "../symbols/20683e205cfecb97.html" click exportRecording "../symbols/6871701ae65ee93c.html" click renderRecordStepsTable "../symbols/9f395ef38e3699a2.html"

Symbols by Kind

function 11

All Symbols

Name Kind Visibility Status Lines Signature
startRecording function exported- 23-33 startRecording(): : Promise<void>
stopRecording function exported- 35-44 stopRecording(): : Promise<void>
pauseRecording function exported- 46-55 pauseRecording(): : Promise<void>
resumeRecording function exported- 57-66 resumeRecording(): : Promise<void>
clearRecording function exported- 68-78 clearRecording(): : Promise<void>
removeRecordStep function - 82-95 removeRecordStep(index: number): : Promise<void>
updateRecordStep function - 97-110 updateRecordStep(index: number, value: string): : Promise<void>
refreshRecordPreview function exported- 112-132 refreshRecordPreview(): : Promise<void>
exportRecording function exported- 136-201 exportRecording(framework: string): : Promise<void>
renderRecordTab function exported- 205-243 renderRecordTab(): : void
renderRecordStepsTable function exporteddeprecated- 249-251 renderRecordStepsTable(): : void

Full Source

/**
 * Record Tab — E2E recording controls, step preview, and script export.
 *
 * Responsibilities:
 * - Start / stop / pause / resume recording
 * - Display recorded steps in real-time
 * - Allow inline editing / removal of steps
 * - Export recorded script (Playwright, Cypress, Pest/Dusk) with optional AI optimisation
 */

import { t } from "@/lib/i18n";
import { panelState } from "../panel-state";
import {
  sendToPage,
  sendToBackground,
  getInspectedPageInfo,
} from "../panel-messaging";
import { addLog } from "../panel-utils";
import { renderTo, RecordTabView } from "@/lib/ui/components";

// ── Recording Controls ────────────────────────────────────────────────────────

export async function startRecording(): Promise<void> {
  try {
    await sendToPage({ type: "START_RECORDING" });
    panelState.recordingState = "recording";
    panelState.recordedStepsPreview = [];
    addLog(t("logRecordStarted"), "success");
    renderRecordTab();
  } catch (err) {
    addLog(`${t("logRecordError")}: ${err}`, "error");
  }
}

export async function stopRecording(): Promise<void> {
  try {
    await sendToPage({ type: "STOP_RECORDING" });
    panelState.recordingState = "stopped";
    addLog(t("logRecordStopped"), "info");
    renderRecordTab();
  } catch (err) {
    addLog(`${t("logRecordError")}: ${err}`, "error");
  }
}

export async function pauseRecording(): Promise<void> {
  try {
    await sendToPage({ type: "PAUSE_RECORDING" });
    panelState.recordingState = "paused";
    addLog(t("logRecordPaused"), "info");
    renderRecordTab();
  } catch (err) {
    addLog(`${t("logRecordError")}: ${err}`, "error");
  }
}

export async function resumeRecording(): Promise<void> {
  try {
    await sendToPage({ type: "RESUME_RECORDING" });
    panelState.recordingState = "recording";
    addLog(t("logRecordResumed"), "info");
    renderRecordTab();
  } catch (err) {
    addLog(`${t("logRecordError")}: ${err}`, "error");
  }
}

export async function clearRecording(): Promise<void> {
  try {
    await sendToPage({ type: "CLEAR_RECORDING" });
    panelState.recordedStepsPreview = [];
    panelState.recordingState = "idle";
    addLog(t("logRecordStopped"), "info");
    renderRecordTab();
  } catch (err) {
    addLog(`${t("logRecordError")}: ${err}`, "error");
  }
}

// ── Step Editing ──────────────────────────────────────────────────────────────

async function removeRecordStep(index: number): Promise<void> {
  try {
    const result = (await sendToPage({
      type: "REMOVE_RECORDING_STEP",
      payload: { index },
    })) as { success?: boolean };
    if (result?.success) {
      panelState.recordedStepsPreview.splice(index, 1);
      renderRecordTab();
    }
  } catch (err) {
    addLog(`${t("logRecordError")}: ${err}`, "error");
  }
}

async function updateRecordStep(index: number, value: string): Promise<void> {
  try {
    const result = (await sendToPage({
      type: "UPDATE_RECORDING_STEP",
      payload: { index, patch: { value } },
    })) as { success?: boolean };
    if (result?.success) {
      panelState.recordedStepsPreview[index].value = value;
      renderRecordTab();
    }
  } catch (err) {
    addLog(`${t("logRecordError")}: ${err}`, "error");
  }
}

export async function refreshRecordPreview(): Promise<void> {
  try {
    const result = (await sendToPage({
      type: "GET_RECORDING_STEPS",
    })) as {
      steps?: Array<{
        type: string;
        selector?: string;
        value?: string;
        waitMs?: number;
        url?: string;
      }>;
    };
    if (result?.steps) {
      panelState.recordedStepsPreview = result.steps;
    }
  } catch {
    // silent
  }
  renderRecordTab();
}

// ── Export ────────────────────────────────────────────────────────────────────

export async function exportRecording(framework: string): Promise<void> {
  if (panelState.isOptimizing) return;

  try {
    const result = (await sendToPage({
      type: "EXPORT_RECORDING",
      payload: {
        framework,
        options: {
          includeAssertions: true,
          smartSelectors: true,
          smartWaits: true,
        },
      },
    })) as { script?: string; error?: string };

    if (!result?.script) {
      addLog(result?.error ?? t("logRecordExportError"), "error");
      return;
    }

    let finalScript = result.script;
    let wasOptimized = false;

    if (panelState.optimizeWithAI) {
      panelState.isOptimizing = true;
      renderRecordTab();
      addLog(t("logRecordOptimizing"), "info");

      try {
        const [pageUrl, pageTitle] = await getInspectedPageInfo();

        const optimized = (await sendToBackground({
          type: "AI_OPTIMIZE_SCRIPT",
          payload: {
            script: result.script,
            framework,
            pageUrl,
            pageTitle,
            pageContext: undefined,
          },
        })) as string | null;

        if (optimized) {
          finalScript = optimized;
          wasOptimized = true;
          addLog(`${t("logRecordOptimized")} (${framework})`, "success");
        } else {
          addLog(t("logRecordOptimizeFailed"), "warn");
        }
      } finally {
        panelState.isOptimizing = false;
      }
    }

    panelState.readyScript = {
      script: finalScript,
      framework: `${framework}${wasOptimized ? " ✨" : ""}`,
    };
    renderRecordTab();
  } catch (err) {
    panelState.isOptimizing = false;
    renderRecordTab();
    addLog(`${t("logRecordExportError")}: ${err}`, "error");
  }
}

// ── Render ────────────────────────────────────────────────────────────────────

export function renderRecordTab(): void {
  const content = document.getElementById("content");
  renderTo(
    content,
    <RecordTabView
      recordingState={panelState.recordingState}
      steps={panelState.recordedStepsPreview}
      optimizeWithAI={panelState.optimizeWithAI}
      isOptimizing={panelState.isOptimizing}
      readyScript={panelState.readyScript}
      onStart={() => void startRecording()}
      onStop={() => void stopRecording()}
      onPause={() => void pauseRecording()}
      onResume={() => void resumeRecording()}
      onClear={() => void clearRecording()}
      onExport={(fw) => void exportRecording(fw)}
      onCopyScript={() => {
        if (!panelState.readyScript) return;
        void navigator.clipboard
          .writeText(panelState.readyScript.script)
          .then(() => {
            addLog(
              `${t("logRecordExported")} (${panelState.readyScript!.framework})`,
              "success",
            );
          });
      }}
      onDismissScript={() => {
        panelState.readyScript = null;
        renderRecordTab();
      }}
      onToggleOptimizeAI={(checked) => {
        panelState.optimizeWithAI = checked;
      }}
      onRemoveStep={(index) => void removeRecordStep(index)}
      onUpdateStep={(index, value) => void updateRecordStep(index, value)}
    />,
  );
}

/**
 * @deprecated Use renderRecordTab() — Preact handles partial updates.
 * Kept for backward-compatible callers in panel.ts.
 */
export function renderRecordStepsTable(): void {
  renderRecordTab();
}