Skip to content

Web UI: FastAPI server + bun/React frontend#26

Open
nedcut wants to merge 2 commits into
mainfrom
feature/web-ui
Open

Web UI: FastAPI server + bun/React frontend#26
nedcut wants to merge 2 commits into
mainfrom
feature/web-ui

Conversation

@nedcut

@nedcut nedcut commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Summary

Overhauls the UI as a local web app while keeping the PyQt5 desktop app working until parity.

  • Shared analysis core: the frame loop and both mask-scan loops move out of the Qt workers into UI-free ecl_analysis/analysis/runner.py and scans.py (progress/cancel via plain callbacks). The Qt workers are now thin adapters; the web server is a second adapter over the same code — one copy of the science.
  • ecl_analysis/server/ (FastAPI, new optional [web] extra): video sessions with server-side JPEG frame decoding (browsers can't decode lab codecs, and this keeps scrubbing frame-accurate), kind-aware threaded cancellable jobs (/api/jobs/*) with polled progress, L* threshold preview overlays, brightest-frame / per-ROI mask scans, audio completion-beep range detection, directory listing for the file picker, and export through the existing CSV/plot pipeline. Exported plot files are served back only from a per-job allowlist; analysis with stale masks 409s instead of silently ignoring them.
  • web/ (bun + Vite + React 19 + TypeScript): dark instrument-style UI (users judge faint ECL glows by eye), canvas ROI draw/move/resize with background-region designation, scrubber with analysis-range selection and 0.25×–4× playback speeds, the desktop app's keyboard shortcuts, a Masks panel (jump-to-brightest, capture with per-region provenance/coverage, auto-invalidated on geometry changes), audio range detection with a multi-beep chooser, interactive results chart with crosshair tooltip, inline export with plot previews. ROI/series colors use a CVD-validated categorical palette.
  • Removes stale working-tree leftovers of the abandoned ecl-v2 spike.

Run it: pip install -e ".[web]", cd web && bun install && bun run build, then python -m ecl_analysis.serverhttp://127.0.0.1:8765. python main.py is unchanged.

Still deferred: drag-and-drop open (browsers hide file paths; needs an upload path).

Test plan

  • pytest — 111 pass (12 API tests: open/frame/analyze/cancel/export/fs/mask-scan/masked-analysis/geometry-409/audio)
  • bun run typecheck && bun run test && bun run build — 11 tests pass
  • Driven end-to-end in a real browser against a synthetic ECL clip: open → draw ROIs → mark background → jump-to-brightest (found frame 70) → capture masks (found each blob's exact peak frame: 70, 95) → masked analysis → chart → export CSVs + plots; silent-clip audio detection reports "no beeps"

https://claude.ai/code/session_01NTGWZgxVNFYjBAcKfZudYj

Overhaul the UI as a local web app while keeping the PyQt5 desktop app
working until parity:

- Extract the analysis frame loop from the Qt AnalysisWorker into UI-free
  ecl_analysis/analysis/runner.py, shared by both UIs via callbacks.
- New ecl_analysis/server/ (FastAPI, optional [web] extra): video sessions
  with server-side JPEG frame decoding, threaded cancellable analysis jobs
  with polled progress, threshold preview overlays, directory browsing for
  the picker, and export through the existing CSV/plot pipeline with
  allowlisted file serving for plot previews.
- New web/ frontend (bun + Vite + React 19 + TS): dark instrument-style
  theme, canvas ROI draw/move/resize with background-region designation,
  frame scrubber with analysis-range selection and desktop keyboard
  shortcuts, live results chart with crosshair tooltip, inline export.
- Remove stale leftovers of the abandoned ecl-v2 spike; ignore its data dir.

Verified end-to-end in a browser against a synthetic ECL clip (open, draw
ROIs, analyze, chart, export). 108 Python + 11 web tests pass.

Claude-Session: https://claude.ai/code/session_01NTGWZgxVNFYjBAcKfZudYj
Copilot AI review requested due to automatic review settings July 20, 2026 01:52

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@nedcut, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 35 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 178687bb-9339-4068-87d8-da9ee7ce46d0

📥 Commits

Reviewing files that changed from the base of the PR and between 06a8d24 and e4ef325.

⛔ Files ignored due to path filters (1)
  • web/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (35)
  • .gitignore
  • README.md
  • ecl_analysis/analysis/__init__.py
  • ecl_analysis/analysis/runner.py
  • ecl_analysis/analysis/scans.py
  • ecl_analysis/server/__init__.py
  • ecl_analysis/server/__main__.py
  • ecl_analysis/server/app.py
  • ecl_analysis/server/jobs.py
  • ecl_analysis/server/videos.py
  • ecl_analysis/workers.py
  • pyproject.toml
  • tests/integration/test_server_api.py
  • tests/integration/test_workers.py
  • web/index.html
  • web/package.json
  • web/src/App.tsx
  • web/src/api.ts
  • web/src/components/AnalyzePanel.tsx
  • web/src/components/FilePicker.tsx
  • web/src/components/MasksPanel.tsx
  • web/src/components/ResultsDrawer.tsx
  • web/src/components/RoiPanel.tsx
  • web/src/components/SettingsPanel.tsx
  • web/src/components/Transport.tsx
  • web/src/components/VideoWell.tsx
  • web/src/hooks.ts
  • web/src/main.tsx
  • web/src/roi.test.ts
  • web/src/roi.ts
  • web/src/styles.css
  • web/src/test/setup.ts
  • web/src/types.ts
  • web/tsconfig.json
  • web/vite.config.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/web-ui

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Close the remaining parity gaps with the desktop app:

- Extract the brightest-frame and per-ROI mask scan loops from the Qt
  workers into UI-free ecl_analysis/analysis/scans.py; the workers now
  delegate, mirroring the earlier runner extraction.
- Generalize the server job layer to kind-aware jobs (/api/jobs/*) and add
  POST /videos/{id}/mask-scan (global + per-ROI modes). Analysis accepts a
  mask_job_id and fails loudly with 409 when region geometry no longer
  matches the captured masks instead of silently ignoring them.
- Add POST /videos/{id}/detect-range wrapping the audio completion-beep
  detector: the beep marks the run's end, counting back the expected
  duration (501 when audio extras are not installed).
- Frontend: Masks panel (jump-to-brightest, capture with provenance and
  coverage readout, use-masks toggle auto-invalidated on any geometry
  change), audio range detection in the Run panel with multi-beep chooser,
  and 0.25x-4x playback speeds (frame-striding above 1x).

Verified in-browser: capture found each synthetic blob's exact peak frame
(70 and 95), masked analysis ran with the masked indicator, silent clip
reports no beeps. 111 Python + 11 web tests pass.

Claude-Session: https://claude.ai/code/session_01NTGWZgxVNFYjBAcKfZudYj
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants