From bdd0d87223b20b51c92029782f00437746912475 Mon Sep 17 00:00:00 2001 From: Kresna Date: Fri, 31 Jul 2026 09:53:15 +0700 Subject: [PATCH 1/4] docs(spec): camera capture design (getUserMedia + native fallback) Reusable CameraCapture in OcrWorkbench (OCR + Receipt Scanner) plus a standalone Camera Capture tool. Rear default + switch; JPEG; Download/Copy/Edit-in-Annotator. Co-Authored-By: Claude Opus 4.8 --- .../specs/2026-07-31-camera-capture-design.md | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-31-camera-capture-design.md diff --git a/docs/superpowers/specs/2026-07-31-camera-capture-design.md b/docs/superpowers/specs/2026-07-31-camera-capture-design.md new file mode 100644 index 0000000..5518119 --- /dev/null +++ b/docs/superpowers/specs/2026-07-31-camera-capture-design.md @@ -0,0 +1,108 @@ +# Camera Capture — Design Spec + +**Status:** Approved (brainstorming complete) +**Date:** 2026-07-31 +**Author:** brainstormed with Kresna +**Related:** reuses the OCR pipeline (`OcrWorkbench`) from `2026-07-30-image-ocr-design.md`. + +## Goal + +Let users capture an image from their **webcam or phone camera** as an input alongside upload/paste. Surface it three ways from one reusable component: +1. a **"Use camera"** option in the shared `OcrWorkbench` (so both **Image → Text (OCR)** and **Receipt Scanner** gain it), and +2. a dedicated **"Camera Capture"** tool that snaps a photo and offers Download / Copy / Edit in Annotator. + +Everything is client-side; the camera frame never leaves the browser. + +## Locked Decisions + +1. **Access method:** live camera via `getUserMedia`, **with a native `` fallback** when the live camera is unavailable/denied. +2. **Camera control:** default to the **rear camera** (`facingMode: 'environment'`), with a **Switch camera** button when more than one camera exists. +3. **Reusable component:** a single `CameraCapture` component + `useCamera` hook, consumed by `OcrWorkbench` and the new tool. It hands back a `File` via callback; the host decides what to do with it. +4. **Standalone tool output:** show the captured photo with **Download + Copy + Edit in Annotator** (mirrors the Screenshot tool). +5. **Capture UI:** an **in-page panel** (not a full-screen modal) that replaces the input area while active. +6. **Capture format:** **JPEG** (`image/jpeg`, quality ~0.92) — smaller for photographs. + +## Architecture + +A `useCamera` hook owns the `MediaStream` lifecycle. A `CameraCapture` component renders the live preview + controls and produces a `File`. Two hosts consume it: + +``` + ┌─ OcrWorkbench → onCapture(file) → onDrop([file]) → existing OCR pipeline +CameraCapture ─(File)────┤ + (useCamera hook) └─ CameraTool → onCapture(file) → result: Download / Copy / Edit in Annotator +``` + +### Components + +| File | Responsibility | Tested by | +|------|----------------|-----------| +| `src/hooks/useCamera.ts` (create) | Own `getUserMedia`: `start(facingMode)`, `stop()`, `switchCamera()`; expose `stream`, `error` (reason-typed), `hasMultiple`, `facingMode`. Stop all tracks on `stop()` and unmount. Detect >1 camera via `enumerateDevices`. | Mock `navigator.mediaDevices` | +| `src/tools/image/camera.lib.ts` (create) | Pure `frameToFile(video: HTMLVideoElement, name?: string): Promise` — draw the current video frame to a canvas → JPEG blob → File. | Mock canvas/video minimally | +| `src/islands/image/CameraCapture.tsx` (create) | Live `