Files
porthole/docs/plans/2026-02-03-capture-time-override-ui.md
2026-02-04 08:57:27 -08:00

3.8 KiB

Capture Time Override UI Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Add a capture-time override form in the MediaPanel viewer to POST override timestamps and display current effective/base timestamps.

Architecture: Extend the existing MediaPanel viewer admin controls with a small form that reads/writes to /api/assets/:id/override-capture-ts using the existing admin token from sessionStorage. Keep UI state local to MediaPanel and refresh the viewer asset timestamps after submit.

Tech Stack: React (Next.js app router), TypeScript, fetch API

Task 1: Add override state and helpers

Files:

  • Modify: apps/web/app/components/MediaPanel.tsx

Step 1: Write the failing test

No tests (user-approved).

Step 2: Add state for override input, status, and effective/base timestamps

const [captureOverrideInput, setCaptureOverrideInput] = useState("");
const [captureOverrideError, setCaptureOverrideError] = useState<string | null>(null);
const [captureOverrideBusy, setCaptureOverrideBusy] = useState(false);

Step 3: Add helper to derive effective/base timestamp

const effectiveTs = viewer?.asset.capture_ts_utc ?? null;
const baseTs = viewer?.asset.capture_ts_utc ?? null; // updated when override applied

Step 4: Commit

No commit yet; continue tasks.

Task 2: Add override POST handler

Files:

  • Modify: apps/web/app/components/MediaPanel.tsx

Step 1: Write the failing test

No tests (user-approved).

Step 2: Implement submit handler

async function handleOverrideCaptureTs() {
  if (!viewer) return;
  setCaptureOverrideError(null);
  setCaptureOverrideBusy(true);
  try {
    const token = sessionStorage.getItem("porthole_admin_token") ?? "";
    if (!token) throw new Error("missing_admin_token");
    const res = await fetch(`/api/assets/${viewer.asset.id}/override-capture-ts`, {
      method: "POST",
      headers: {
        "X-Porthole-Admin-Token": token,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ capture_ts_utc: captureOverrideInput || null }),
    });
    if (!res.ok) throw new Error(`override_failed:${res.status}`);
    // refresh viewer asset timestamps (re-fetch list or update local)
  } catch (err) {
    setCaptureOverrideError(err instanceof Error ? err.message : String(err));
  } finally {
    setCaptureOverrideBusy(false);
  }
}

Step 3: Commit

No commit yet; continue tasks.

Task 3: Add UI above Tags & Albums

Files:

  • Modify: apps/web/app/components/MediaPanel.tsx

Step 1: Write the failing test

No tests (user-approved).

Step 2: Add form UI

<div style={{ display: "grid", gap: 6 }}>
  <strong style={{ fontSize: 13 }}>Capture time override</strong>
  <div style={{ color: "#666", fontSize: 12 }}>
    Effective: {effectiveTs ?? "(none)"}
  </div>
  <div style={{ color: "#999", fontSize: 12 }}>
    Base: {baseTs ?? "(unknown)"}
  </div>
  <div style={{ display: "flex", gap: 8 }}>
    <input
      type="text"
      placeholder="2026-01-01T00:00:00.000Z"
      value={captureOverrideInput}
      onChange={(e) => setCaptureOverrideInput(e.target.value)}
      style={{ flex: 1, padding: 6 }}
      disabled={captureOverrideBusy}
    />
    <button type="button" onClick={handleOverrideCaptureTs}>
      Save
    </button>
  </div>
  {captureOverrideError ? (
    <div style={{ color: "#b00", fontSize: 12 }}>{captureOverrideError}</div>
  ) : null}
</div>

Step 3: Commit

No commit yet; continue tasks.

Task 4: Finalize, verify, and commit

Files:

  • Modify: apps/web/app/components/MediaPanel.tsx

Step 1: Quick manual check

Run: npm test (skip) Expected: (skipped per user)

Step 2: Commit

git add apps/web/app/components/MediaPanel.tsx
git commit -m "feat: add UI for capture time override"