feat: add UI for capture time override

This commit is contained in:
William Valentin
2026-02-04 08:57:27 -08:00
parent 6030581429
commit 8eae0c7c97
2 changed files with 272 additions and 0 deletions

View File

@@ -0,0 +1,138 @@
# 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**
```ts
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**
```ts
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**
```ts
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**
```tsx
<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**
```bash
git add apps/web/app/components/MediaPanel.tsx
git commit -m "feat: add UI for capture time override"
```