Skip to content

Add picture viewer module#103

Merged
jayvdb merged 1 commit into
mainfrom
pic-viewer
Jul 23, 2026
Merged

Add picture viewer module#103
jayvdb merged 1 commit into
mainfrom
pic-viewer

Conversation

@jayvdb

@jayvdb jayvdb commented Jul 23, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Added a picture viewer that receives image-capture notifications and displays images in the browser.
    • Added support for broadcasting stored eye captures to connected viewers.
    • Added headless Chrome and Firefox test tasks for the picture viewer.
    • Added an option to use temporary connection identities without retaining them locally.
  • Bug Fixes

    • Improved capture failure handling and filename reporting.
    • Restricted displayed image URLs to trusted same-origin storage paths.
  • Tests

    • Added browser tests covering image rendering, error handling, and notification validation.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a WASM pic-viewer that receives pyeye1 capture broadcasts, validates storage URLs, renders images to a canvas, and reports displayed images. It also adds browser tests, workspace tasks, WebSocket identity configuration, and CI coverage collection.

Changes

Pic viewer capture flow

Layer / File(s) Summary
Viewer crate foundation
Cargo.toml, services/ws-modules/pic-viewer/*, .mise/config.rust.toml
Adds the pic-viewer crate, favicon build fixture, dependencies, workspace registration, lint coverage, and wasm build task.
Capture broadcast production
services/ws-modules/pyeye1/pkg/*, services/ws-modules/pyeye1/pyeye1/eye_detection.py
Returns uploaded filenames and broadcasts capture metadata containing the agent, filename, and /storage/ URL.
WebSocket identity configuration
services/ws-wasm-agent/src/lib.rs
Adds optional retained-agent-id behavior across initialization, connection acknowledgement, cloning, and exported configuration.
Viewer runtime and browser validation
services/ws-modules/pic-viewer/src/lib.rs, services/ws-modules/pic-viewer/tests/*
Adds WebSocket polling, capture parsing, lifecycle controls, canvas rendering, status updates, and parser/rendering tests.
Browser test and coverage integration
.mise/config.toml, .mise/config.coverage.toml, .github/workflows/coverage.yaml
Adds headless browser tasks and merges pic-viewer wasm coverage into the shared lcov report.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PyEye1
  participant WebSocket
  participant PicViewer
  participant Canvas
  PyEye1->>WebSocket: Broadcast stored capture
  WebSocket->>PicViewer: Deliver capture notification
  PicViewer->>Canvas: Decode and draw image
  PicViewer->>WebSocket: Report displayed image
Loading

Possibly related PRs

  • edge-toolkit/core#85: Changes shared coverage profraw and merge behavior used by the new pic-viewer coverage task.
  • edge-toolkit/core#88: Adds related wasm-agent coverage integration in the same coverage pipeline.
  • edge-toolkit/core#98: Changes the shared wasm coverage wrapper used to generate pic-viewer coverage.

Suggested reviewers: pierre-tenedero

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: introducing a new picture viewer module.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pic-viewer

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Checkov (3.3.8)
.github/workflows/coverage.yaml

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

config/ast-grep/rules/doc-summary-ends-with-period.yaml

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'


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

@deepsource-io

deepsource-io Bot commented Jul 23, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 83cae82...5b491a4 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Coverage  

Code Review Summary

Analyzer Status Updated (UTC) Details
C# Jul 23, 2026 1:24a.m. Review ↗
C & C++ Jul 23, 2026 1:24a.m. Review ↗
Docker Jul 23, 2026 1:24a.m. Review ↗
Java Jul 23, 2026 1:24a.m. Review ↗
JavaScript Jul 23, 2026 1:24a.m. Review ↗
Python Jul 23, 2026 1:24a.m. Review ↗
Rust Jul 23, 2026 1:24a.m. Review ↗
Secrets Jul 23, 2026 1:24a.m. Review ↗
Code coverage Jul 23, 2026 1:52a.m. Review ↗

Code Coverage Summary

Language Line Coverage (New Code) Line Coverage (Overall)
Aggregate
26.7%
57.8%
[▼ down 1.4% from main]
Python
100%
85.2%
Rust
24.7%
55.6%
[▼ down 1.5% from main]

➟ Additional coverage metrics may have been reported. See full coverage report ↗


Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

@codacy-production

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 1 high · 1 medium · 3 minor

Alerts:
⚠ 5 issues (≤ 0 issues of at least minor severity)

Results:
5 new issues

Category Results
Documentation 1 minor
Security 1 high
Complexity 1 medium
2 minor

View in Codacy

🟢 Metrics 0 complexity · 6 duplication

Metric Results
Complexity 0
Duplication 6

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@jayvdb
jayvdb marked this pull request as ready for review July 23, 2026 01:52
@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 40.37037% with 161 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
services/ws-modules/pic-viewer/src/lib.rs 22.50% 154 Missing and 1 partial ⚠️
services/ws-wasm-agent/src/lib.rs 40.00% 4 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@services/ws-modules/pic-viewer/src/lib.rs`:
- Around line 227-231: Update show_image so image.decode() races against a
bounded timer, returning an error when the deadline expires instead of awaiting
indefinitely. Preserve successful decoding and propagate fetch/decode failures,
allowing the caller to regain control after timeout.
- Around line 181-187: Update queue_capture_notification to bound the pending
VecDeque and retain only the newest capture when the queue reaches its limit.
After parsing a valid notification, discard stale queued captures as needed
before enqueueing the new notification, preserving the existing behavior for
invalid values.
- Around line 207-220: Update the URL validation in the capture-notification
parsing flow around message.get("url") so it accepts only canonical storage
paths, not arbitrary strings with a "/storage/" prefix. Parse and normalize the
path, reject traversal or any non-canonical segments that resolve outside
storage, and construct or retain the URL only after validation so browser
navigation remains confined to storage.
- Around line 12-16: Move the clippy::future_not_send and clippy::single_call_fn
expectations from the crate-level attribute to the specific async functions or
wait_for_* helpers that trigger them. Preserve the existing justification text
and ensure each expectation is scoped only to the item requiring it.

In `@services/ws-modules/pyeye1/pyeye1/eye_detection.py`:
- Around line 171-176: Update the run-workflow test double used by
test_run_workflow so platform.save_eye_capture returns a fixture filename
instead of None. Assert that the successful capture path emits a broadcast
envelope containing that filename, while preserving the existing error-path
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c2a5717e-27ea-4147-992b-664f75b6627f

📥 Commits

Reviewing files that changed from the base of the PR and between 83cae82 and 5b491a4.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • .github/workflows/coverage.yaml
  • .mise/config.coverage.toml
  • .mise/config.rust.toml
  • .mise/config.toml
  • Cargo.toml
  • config/ast-grep/rules/doc-summary-ends-with-period.yaml
  • services/ws-modules/pic-viewer/Cargo.toml
  • services/ws-modules/pic-viewer/build.rs
  • services/ws-modules/pic-viewer/src/lib.rs
  • services/ws-modules/pic-viewer/tests/parse.rs
  • services/ws-modules/pic-viewer/tests/show_image.rs
  • services/ws-modules/pyeye1/pkg/et_ws_pyeye1.js
  • services/ws-modules/pyeye1/pyeye1/eye_detection.py
  • services/ws-wasm-agent/src/lib.rs

Comment on lines +12 to +16
#![expect(
clippy::future_not_send,
clippy::single_call_fn,
reason = "browser WASM module: JsFuture is !Send; module-local helpers like wait_for_* are single-use by design"
)]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Scope the Clippy expectations to the items that need them. The crate-level #[expect] is broader than necessary; move each one to the specific async function or helper it applies to, keeping the existing justification.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/ws-modules/pic-viewer/src/lib.rs` around lines 12 - 16, Move the
clippy::future_not_send and clippy::single_call_fn expectations from the
crate-level attribute to the specific async functions or wait_for_* helpers that
trigger them. Preserve the existing justification text and ensure each
expectation is scoped only to the item requiring it.

Comment on lines +181 to +187
fn queue_capture_notification(pending: &Rc<RefCell<VecDeque<CaptureNotification>>>, value: &JsValue) {
let Some(data) = value.as_string() else {
return;
};
if let Some(notification) = parse_capture_notification(&data) {
pending.borrow_mut().push_back(notification);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound the pending capture queue.

Any connected peer can send captures faster than decoding and drawing completes, so VecDeque grows without
limit and can exhaust the viewer's WASM heap. Cap it and drop or coalesce stale captures; showing the newest
image is the appropriate behavior here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/ws-modules/pic-viewer/src/lib.rs` around lines 181 - 187, Update
queue_capture_notification to bound the pending VecDeque and retain only the
newest capture when the queue reaches its limit. After parsing a valid
notification, discard stale queued captures as needed before enqueueing the new
notification, preserving the existing behavior for invalid values.

Comment on lines +207 to +220
let url = message.get("url").and_then(serde_json::Value::as_str)?;
if !url.starts_with("/storage/") {
return None;
}
let filename = message
.get("filename")
.and_then(serde_json::Value::as_str)
.unwrap_or("")
.to_string();
Some(CaptureNotification {
from_agent_id,
filename,
url: url.to_string(),
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Validate a canonical storage path, not a prefix.

/storage/../private.png passes starts_with("/storage/"), but the browser resolves it outside storage when
assigned to image.src. A peer can therefore steer this authenticated browser to arbitrary same-origin GET
routes. Derive the URL from validated path components, or parse and require canonical storage segments.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/ws-modules/pic-viewer/src/lib.rs` around lines 207 - 220, Update the
URL validation in the capture-notification parsing flow around
message.get("url") so it accepts only canonical storage paths, not arbitrary
strings with a "/storage/" prefix. Parse and normalize the path, reject
traversal or any non-canonical segments that resolve outside storage, and
construct or retain the URL only after validation so browser navigation remains
confined to storage.

Comment on lines +227 to +231
pub async fn show_image(url: &str) -> Result<(), JsValue> {
let image = HtmlImageElement::new()?;
image.set_src(url);
// decode() resolves once the image is fetched and ready to draw, and rejects on a failed fetch/decode.
let _decoded = JsFuture::from(image.decode()).await?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Put a deadline on image decoding.

A stalled storage response leaves image.decode() pending indefinitely. The outer loop cannot observe
stop(), disconnect, or process later captures while this await is blocked. Race loading against a bounded
timer and return an error on timeout.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/ws-modules/pic-viewer/src/lib.rs` around lines 227 - 231, Update
show_image so image.decode() races against a bounded timer, returning an error
when the deadline expires instead of awaiting indefinitely. Preserve successful
decoding and propagate fetch/decode failures, allowing the caller to regain
control after timeout.

Comment on lines +171 to +176
filename = await platform.save_eye_capture()
except Exception as exc:
platform.log(f"eye capture failed: {exc}")
platform.send_event(eye_capture_error_event_json(str(exc)))
return
platform.send_event(capture_broadcast_json(str(platform.agent_id()), str(filename)))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Update the workflow test double for the returned filename.

services/ws-modules/pyeye1/tests/test_run_workflow.py:138-139 still returns None; this path
stringifies it and emits a capture broadcast with "filename": "None". Return a fixture filename and assert
the resulting broadcast envelope.

🧰 Tools
🪛 Ruff (0.15.21)

[warning] 172-172: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/ws-modules/pyeye1/pyeye1/eye_detection.py` around lines 171 - 176,
Update the run-workflow test double used by test_run_workflow so
platform.save_eye_capture returns a fixture filename instead of None. Assert
that the successful capture path emits a broadcast envelope containing that
filename, while preserving the existing error-path behavior.

@jayvdb
jayvdb requested a review from pierre-tenedero July 23, 2026 02:02
@jayvdb
jayvdb merged commit 19c85d7 into main Jul 23, 2026
32 of 35 checks passed
@jayvdb
jayvdb deleted the pic-viewer branch July 23, 2026 02:06
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