Skip to content

feat(captcha): implement MAT-140 CAPTCHA pipeline#181

Merged
mpiton merged 4 commits into
mainfrom
feat/mat-140-captcha-pipeline
Jul 20, 2026
Merged

feat(captcha): implement MAT-140 CAPTCHA pipeline#181
mpiton merged 4 commits into
mainfrom
feat/mat-140-captcha-pipeline

Conversation

@mpiton

@mpiton mpiton commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Summary

  • add a persistent CAPTCHA challenge model, SQLite log, queue coordination, startup recovery, and safe timeout handling
  • propagate hoster CAPTCHA requirements through the engine, CQRS commands and queries, Tauri events, and five IPC commands
  • add the manual solver UI, queue/history view, timeout settings, and EN/FR translations
  • keep answers transient, redact terminal challenge payloads, bound raster inputs, and cover rollback and concurrency failure paths

Verification

  • cargo test --manifest-path src-tauri/Cargo.toml --workspace
  • npx vitest run
  • cargo clippy --manifest-path src-tauri/Cargo.toml --workspace -- -D warnings
  • npm run build
  • npx oxlint .
  • targeted oxfmt check for every touched frontend file
  • full pre-push gate: knip, cargo-deny, TypeScript typecheck/tests, Rust tests

Result: 734 frontend tests passed; 1,728 Rust unit tests and 4 Rust integration tests passed. Five OS/format-dependent unit tests and two doctests remain ignored.

Scope note

reCAPTCHA/hCaptcha solving and a plugin ABI for submitting a manual answer remain out of scope for MAT-140. The current official 1fichier flow surfaces these challenge types as unsupported so the user can skip or retry; image/text challenges are handled by the manual solver.

Linear: MAT-140


Summary by cubic

Implements the end-to-end manual CAPTCHA pipeline with a separate queue, CQRS/Tauri events, UI, timeout with automatic download resumption, compact base64 image transport, and persistent SQLite captcha_log (with migration). Also hardens recovery and removal flows. Satisfies Linear MAT-140 acceptance.

  • New Features

    • Separate CAPTCHA queue that doesn’t block download slots; solving resumes, skipping fails with a clear reason. Emits download-queued when re-queued.
    • Commands: captcha_solve, captcha_skip, captcha_retry; Queries: captcha_list, captcha_get_pending; Tauri events: captcha-pending, captcha-solved, captcha-skipped, captcha-timed-out.
    • Hoster plugin contract surfaces CAPTCHA (requires_captcha, type, optional image); engine emits typed internal CaptchaRequired.
    • React UI: solver panel, queue/history, countdown, solver settings, EN/FR translations; compact base64 image + MIME type; redacted history and bounded plugin payloads.
    • Config: captcha_timeout_seconds with safe defaults and bounds; clean scheduling on CAPTCHA and startup recovery.
  • Bug Fixes

    • Startup recovery re-arms pending CAPTCHA timeouts; cancel/remove now atomically coordinate engine cancellation with challenge cleanup.
    • Event forwarding filters internal captcha-required to avoid frontend races; download logging includes download-queued and CAPTCHA lifecycle entries.

Written for commit 4075849. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added a persistent CAPTCHA queue for protected downloads with solve/skip/retry actions, automatic timeout handling, and capped history.
    • Introduced SQLite-backed CAPTCHA persistence, new CAPTCHA Tauri commands/queries, and React UI for the queue, challenge panel, and solver settings.
    • Added configurable CAPTCHA timeout with settings/UI support, input safety limits, and updated English/French translations.
    • Exposed new lifecycle events, including download-queued, plus CAPTCHA pending/solved/skipped/timed-out.
  • Bug Fixes
    • Improved CAPTCHA-aware recovery and cancellation for downloads waiting on challenges, plus safer event forwarding and enhanced CAPTCHA lifecycle logging.

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@github-actions github-actions Bot added documentation Improvements or additions to documentation rust frontend ui labels Jul 19, 2026
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds a persistent CAPTCHA workflow spanning hoster extraction, domain state transitions, SQLite storage, timeout and retry handling, Tauri IPC, frontend queue controls, event propagation, configuration, and redacted challenge history.

Changes

CAPTCHA pipeline

Layer / File(s) Summary
Domain contracts and state model
src-tauri/src/domain/..., src-tauri/src/application/commands/mod.rs
Adds typed CAPTCHA identifiers, statuses, transitions, lifecycle events, repository and solver ports, validation limits, and CQRS command/query types.
Extraction and persistence
src-tauri/src/adapters/driven/plugin/..., src-tauri/src/adapters/driven/network/..., src-tauri/src/adapters/driven/sqlite/...
Parses CAPTCHA-protected hoster links, validates image data, emits typed challenge events, and persists challenge lifecycle metadata without solutions.
Lifecycle orchestration
src-tauri/src/application/commands/captcha.rs, src-tauri/src/application/services/..., src-tauri/src/application/commands/{cancel_download.rs,remove_download.rs}
Handles enqueue, solve, skip, retry, timeout, rollback, timer restoration, download resumption, cancellation, queue-slot release, and startup recovery.
Application and IPC wiring
src-tauri/src/lib.rs, src-tauri/src/adapters/driving/tauri_ipc.rs, src-tauri/src/application/{command_bus.rs,query_bus.rs,queries/...}
Registers CAPTCHA repositories and handlers, exposes commands and queries, returns CAPTCHA view DTOs, and persists the bounded timeout setting.
Frontend queue
src/hooks/useCaptchaQueue.ts, src/views/Captcha*.tsx, src/types/captcha.ts, src/i18n/locales/*
Replaces the CAPTCHA placeholder with a queue, challenge panel, history, solver settings, event-driven refresh, localized text, and UI tests.
Event and log integration
src-tauri/src/adapters/driven/{event,logging}/..., src/hooks/useDownloadEvents.ts, src/types/events.ts
Maps CAPTCHA and queued-download events to frontend payloads, query invalidation, and download logs.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • mpiton/vortex#172: Both changes modify the hoster plugin wire-format parsing and ExtractedHosterLink construction path.

Suggested labels: dependencies, configuration

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: implementing the MAT-140 CAPTCHA pipeline.

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

@cubic-dev-ai cubic-dev-ai 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.

cubic analysis

All reported issues were addressed across 67 files

Linked issue analysis

Linked issue: MAT-140: [Lot 4] Construire le pipeline CAPTCHA de bout en bout

Status Acceptance criteria Notes
R-01 — A download waiting for CAPTCHA passes into the CAPTCHA queue without occupying a download slot. Queue/slot handling and CAPTCHA pending event are implemented so the queue frees a download slot when a captcha is pending (QueueManager.handle_captcha_pending calls decrement_and_schedule; CommandBus integrates a captcha handler and cancel/remove logic avoids cancelling downloads that are waiting for CAPTCHA).
R-02 — The CAPTCHA view displays the image, remaining time, and allows solve / skip / retry. React UI components render the challenge image, show countdown, and expose solve/skip/retry actions; tests exercise the view.
R-03 — A correct resolution resumes the download automatically; skip marks it failed with an explicit reason. Command handlers and domain transitions emit CaptchaSolved/CaptchaSkipped and drive download state transitions (queue_after_wait / DownloadQueued) so solving resumes the download and skipping records failure.
R-04 — Timeout of an entry triggers the configured fallback (skip by default) and is logged in captcha_log. Config, timeout handling, events, persistence and migration are present; timeouts emit events and are recorded in the captcha_log implementation and tested by the added handler/repo tests.
R-05 — Flow respects architecture: entities/transitions in domain/, orchestration in application/, no state mutated outside Command Handler. New captcha domain model and events live under domain/, orchestration and command handlers live under application/, and adapters produce DomainEvent/DomainError rather than mutating domain state directly.
R-06 — Coverage: domain tests on the CAPTCHA state machine, handler tests with in-memory mocks, frontend test on the view. Comprehensive tests were added across domain, application handlers, sqlite repo, and frontend view covering behavior and edge cases.

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src-tauri/src/domain/model/captcha.rs
Comment thread src-tauri/src/domain/ports/driven/hoster_link.rs
Comment thread src/types/events.ts Outdated
Comment thread src-tauri/src/application/queries/captcha.rs
Comment thread src-tauri/src/domain/error.rs
Comment thread src-tauri/src/application/commands/resolve_links.rs
Comment thread src-tauri/src/adapters/driving/tauri_ipc.rs
Comment thread src-tauri/src/adapters/driven/sqlite/captcha_repo.rs
Comment thread src/views/CaptchaSolverSettings.tsx Outdated
Comment thread src/views/CaptchaChallengePanel.tsx Outdated
@codspeed-hq

codspeed-hq Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will degrade performance by 0.8%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 3 improved benchmarks
❌ 2 regressed benchmarks
✅ 21 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
normalize_link_check_parallelism 120.8 ns 150 ns -19.44%
normalize_max_concurrent 120.8 ns 150 ns -19.44%
detect_md5 745 ns 628.3 ns +18.57%
detect_sha256 821.9 ns 734.4 ns +11.91%
create_valid 281.4 ns 252.2 ns +11.56%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing feat/mat-140-captcha-pipeline (74ad5a7) with main (a375ad7)

Open in CodSpeed

@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: 3

🧹 Nitpick comments (4)
src-tauri/src/domain/model/captcha.rs (1)

329-408: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Hand-rolled PNG/GIF/JPEG sniffer for untrusted hoster bytes.

image_metadata/jpeg_dimensions parse hoster-supplied binary data by hand to bound CAPTCHA image pixels/MIME. The indexing is memory-safe (bounded via .get()/checked_add), but rolling a custom binary-format parser for untrusted input is exactly the kind of task better delegated to an established, audited crate (e.g. imagesize, which reads PNG/GIF/JPEG dimensions from a byte slice without decoding).

♻️ Sketch using the `imagesize` crate
-pub fn captcha_image_mime_type(data: &[u8]) -> Option<&'static str> {
-    let (mime, width, height) = image_metadata(data)?;
-    let pixels = u64::from(width).checked_mul(u64::from(height))?;
-    (width > 0 && height > 0 && pixels <= MAX_CAPTCHA_IMAGE_PIXELS).then_some(mime)
-}
+pub fn captcha_image_mime_type(data: &[u8]) -> Option<&'static str> {
+    let info = imagesize::blob_size(data).ok()?;
+    let pixels = (info.width as u64).checked_mul(info.height as u64)?;
+    if pixels == 0 || pixels > MAX_CAPTCHA_IMAGE_PIXELS {
+        return None;
+    }
+    match detect_format(data)? { // still need format allow-listing to PNG/GIF/JPEG
+        ImageType::Png => Some("image/png"),
+        ImageType::Gif => Some("image/gif"),
+        ImageType::Jpeg => Some("image/jpeg"),
+        _ => None,
+    }
+}
🤖 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 `@src-tauri/src/domain/model/captcha.rs` around lines 329 - 408, Replace the
hand-rolled image format parsing in image_metadata and jpeg_dimensions with the
established imagesize crate’s byte-slice dimension detection. Map supported PNG,
GIF, and JPEG results to their MIME types and preserve captcha_image_mime_type’s
nonzero-dimension and MAX_CAPTCHA_IMAGE_PIXELS validation behavior.
src-tauri/src/adapters/driven/plugin/hoster_contract.rs (1)

68-92: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Image/TextInput CAPTCHA responses can be accepted without any image data.

Image validation only runs if file.captcha_image_data.as_ref().is_some_and(...) — when the field is absent, the check is skipped entirely regardless of challenge_type. For CaptchaType::Image/TextInput (which, per this PR's manual-solver UX, need a rendered image to solve), a plugin response with requires_captcha: true, captcha_type: "image" and no captcha_image_data will pass extraction and surface an unsolvable challenge to the user later, rather than being rejected here as malformed.

🐛 Suggested guard
                 Some(ExtractedCaptchaChallenge {
                     challenge_type,
                     image_data: file.captcha_image_data,
                 })
+            } else if matches!(challenge_type, CaptchaType::Image | CaptchaType::TextInput)
+                && file.captcha_image_data.is_none()
+            {
+                return Err(limit_error());
             } else {
                 None
             };
🤖 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 `@src-tauri/src/adapters/driven/plugin/hoster_contract.rs` around lines 68 -
92, Update the CAPTCHA extraction block around ExtractedCaptchaChallenge so
CaptchaType::Image and CaptchaType::TextInput require present, non-empty,
size-bounded image data with a valid MIME type; reject missing or invalid data
via limit_error(). Preserve the existing optional image-data behavior for
challenge types that do not require a rendered image.
src-tauri/src/adapters/driven/sqlite/captcha_repo.rs (1)

18-21: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consider adding a LIMIT to PENDING_CAPTCHA_METADATA_QUERY.

CAPTCHA_METADATA_QUERY bounds results with LIMIT 200, but PENDING_CAPTCHA_METADATA_QUERY has no limit. While pending captchas should be few in practice, an unbounded query is inconsistent with the list query's defensive approach.

♻️ Suggested fix
 const PENDING_CAPTCHA_METADATA_QUERY: &str = "SELECT id, download_id, challenge_type, \
     '[redacted]' AS challenge_url, NULL AS image_data, status, solver, attempts, created_at, expires_at, \
     resolved_at, duration_ms, failure_reason FROM captcha_log WHERE status = ? \
-    ORDER BY created_at ASC";
+    ORDER BY created_at ASC LIMIT 200";
🤖 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 `@src-tauri/src/adapters/driven/sqlite/captcha_repo.rs` around lines 18 - 21,
Add a defensive LIMIT matching CAPTCHA_METADATA_QUERY to
PENDING_CAPTCHA_METADATA_QUERY, preserving its existing filtering and created_at
ordering.
src-tauri/src/adapters/driven/logging/download_log_bridge.rs (1)

82-88: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

CaptchaSkipped log entry lacks "CAPTCHA" prefix for consistency.

All other CAPTCHA log messages include "CAPTCHA" in the text, but CaptchaSkipped logs only "[WARN] {reason}". If the reason string doesn't itself contain "CAPTCHA", the log entry will be ambiguous in the download log view.

♻️ Suggested fix
         DomainEvent::CaptchaSkipped {
             download_id,
             reason,
             ..
         } => {
-            store.push(download_id.0, format!("[WARN] {reason}"));
+            store.push(download_id.0, format!("[WARN] CAPTCHA skipped: {reason}"));
         }
🤖 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 `@src-tauri/src/adapters/driven/logging/download_log_bridge.rs` around lines 82
- 88, Update the DomainEvent::CaptchaSkipped logging branch to include an
explicit "CAPTCHA" prefix in the formatted warning message before the reason,
matching the wording of other CAPTCHA log entries while preserving the existing
warning level and download ID.
🤖 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 `@src-tauri/src/adapters/driven/plugin/hoster_contract.rs`:
- Around line 37-40: Update the hoster payload sizing around the
captcha_image_data field and MAX_HOSTER_PAYLOAD_BYTES so a maximum-size captcha
image, when JSON-serialized as a byte array, is accepted before the per-image
limit is enforced. Prefer increasing the payload cap with sufficient headroom
while preserving the existing captcha_image_data size validation.

In `@src/types/events.ts`:
- Around line 36-40: Update CaptchaEventPayload and the captcha entries in
TauriEventMap to model each backend event’s actual payload shape: include solver
and durationMs for captcha-solved, reason for captcha-skipped, durationMs for
captcha-timed-out, and retain the shared challengeId/downloadId fields. Keep
captcha events with distinct payload types so consumers can access
event-specific fields.

In `@src/views/CaptchaSolverSettings.tsx`:
- Around line 15-23: Rename the timeout state and setter in
CaptchaSolverSettings from timeout/setTimeout to distinct names such as
timeoutSeconds/setTimeoutSeconds, and update the useEffect and persistTimeout
references accordingly. Ensure no component-local identifier shadows the global
setTimeout.

---

Nitpick comments:
In `@src-tauri/src/adapters/driven/logging/download_log_bridge.rs`:
- Around line 82-88: Update the DomainEvent::CaptchaSkipped logging branch to
include an explicit "CAPTCHA" prefix in the formatted warning message before the
reason, matching the wording of other CAPTCHA log entries while preserving the
existing warning level and download ID.

In `@src-tauri/src/adapters/driven/plugin/hoster_contract.rs`:
- Around line 68-92: Update the CAPTCHA extraction block around
ExtractedCaptchaChallenge so CaptchaType::Image and CaptchaType::TextInput
require present, non-empty, size-bounded image data with a valid MIME type;
reject missing or invalid data via limit_error(). Preserve the existing optional
image-data behavior for challenge types that do not require a rendered image.

In `@src-tauri/src/adapters/driven/sqlite/captcha_repo.rs`:
- Around line 18-21: Add a defensive LIMIT matching CAPTCHA_METADATA_QUERY to
PENDING_CAPTCHA_METADATA_QUERY, preserving its existing filtering and created_at
ordering.

In `@src-tauri/src/domain/model/captcha.rs`:
- Around line 329-408: Replace the hand-rolled image format parsing in
image_metadata and jpeg_dimensions with the established imagesize crate’s
byte-slice dimension detection. Map supported PNG, GIF, and JPEG results to
their MIME types and preserve captcha_image_mime_type’s nonzero-dimension and
MAX_CAPTCHA_IMAGE_PIXELS validation 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c9e04a2d-6dd9-4542-a97b-0d1a0f72ef52

📥 Commits

Reviewing files that changed from the base of the PR and between a375ad7 and 1cfbf1e.

📒 Files selected for processing (67)
  • CHANGELOG.md
  • src-tauri/src/adapters/driven/config/toml_config_store.rs
  • src-tauri/src/adapters/driven/event/tauri_bridge.rs
  • src-tauri/src/adapters/driven/logging/download_log_bridge.rs
  • src-tauri/src/adapters/driven/network/download_engine.rs
  • src-tauri/src/adapters/driven/network/download_engine_tests.rs
  • src-tauri/src/adapters/driven/plugin/hoster_contract.rs
  • src-tauri/src/adapters/driven/plugin/hoster_contract_tests.rs
  • src-tauri/src/adapters/driven/sqlite/captcha_repo.rs
  • src-tauri/src/adapters/driven/sqlite/captcha_repo_tests.rs
  • src-tauri/src/adapters/driven/sqlite/entities/captcha_log.rs
  • src-tauri/src/adapters/driven/sqlite/entities/mod.rs
  • src-tauri/src/adapters/driven/sqlite/migrations/m20260719_000011_create_captcha_log.rs
  • src-tauri/src/adapters/driven/sqlite/migrations/mod.rs
  • src-tauri/src/adapters/driven/sqlite/mod.rs
  • src-tauri/src/adapters/driving/tauri_ipc.rs
  • src-tauri/src/application/command_bus.rs
  • src-tauri/src/application/commands/cancel_download.rs
  • src-tauri/src/application/commands/captcha.rs
  • src-tauri/src/application/commands/captcha_tests.rs
  • src-tauri/src/application/commands/hoster_download_source.rs
  • src-tauri/src/application/commands/mod.rs
  • src-tauri/src/application/commands/remove_download.rs
  • src-tauri/src/application/commands/resolve_links.rs
  • src-tauri/src/application/commands/resolve_links_hoster_tests.rs
  • src-tauri/src/application/commands/resolve_links_tests.rs
  • src-tauri/src/application/commands/resolve_premium_source_test_support.rs
  • src-tauri/src/application/commands/tests_support.rs
  • src-tauri/src/application/queries/captcha.rs
  • src-tauri/src/application/queries/mod.rs
  • src-tauri/src/application/query_bus.rs
  • src-tauri/src/application/read_models/captcha_view.rs
  • src-tauri/src/application/read_models/mod.rs
  • src-tauri/src/application/services/queue_manager.rs
  • src-tauri/src/application/services/startup_recovery.rs
  • src-tauri/src/domain/error.rs
  • src-tauri/src/domain/event.rs
  • src-tauri/src/domain/model/captcha.rs
  • src-tauri/src/domain/model/config.rs
  • src-tauri/src/domain/model/download.rs
  • src-tauri/src/domain/model/mod.rs
  • src-tauri/src/domain/ports/driven/captcha_repository.rs
  • src-tauri/src/domain/ports/driven/captcha_solver.rs
  • src-tauri/src/domain/ports/driven/hoster_link.rs
  • src-tauri/src/domain/ports/driven/mod.rs
  • src-tauri/src/lib.rs
  • src/api/queries.ts
  • src/components/__tests__/ClipboardIndicator.test.tsx
  • src/hooks/__tests__/useAppEffects.test.ts
  • src/hooks/__tests__/useDownloadEvents.test.ts
  • src/hooks/useCaptchaQueue.ts
  • src/hooks/useDownloadEvents.ts
  • src/i18n/__tests__/issue30-ui-fr.test.tsx
  • src/i18n/locales/en.json
  • src/i18n/locales/fr.json
  • src/layouts/__tests__/AppLayout.test.tsx
  • src/stores/__tests__/settingsStore.test.ts
  • src/types/captcha.ts
  • src/types/events.ts
  • src/types/settings.ts
  • src/views/CaptchaChallengePanel.tsx
  • src/views/CaptchaSolverSettings.tsx
  • src/views/CaptchaView.tsx
  • src/views/LinkGrabberView/__tests__/LinkGrabberView.test.tsx
  • src/views/SettingsView/__tests__/Sections.test.tsx
  • src/views/SettingsView/__tests__/SettingsView.test.tsx
  • src/views/__tests__/CaptchaView.test.tsx

Comment thread src-tauri/src/adapters/driven/plugin/hoster_contract.rs
Comment thread src/types/events.ts
Comment thread src/views/CaptchaSolverSettings.tsx Outdated
@mpiton

mpiton commented Jul 20, 2026

Copy link
Copy Markdown
Owner Author

Review follow-up for 74ad5a7:

  • Also addressed the non-inline CodeRabbit findings: the skipped-CAPTCHA log prefix and the pending-query LIMIT 200.
  • Kept the bounded image parser in the domain instead of adding imagesize: the project domain layer is std-only. Valid, zero-dimension, truncated, and malformed PNG/GIF/JPEG cases are now covered.
  • Kept IPC solution validation intentionally because IPC is a trust boundary and it shares the domain size constant.
  • Kept Link Grabber CAPTCHA preview out of MAT-140 because it would expand the ResolvedLink contract beyond the pending-queue acceptance criteria.
  • The docstring coverage notice is informational and broad documentation churn is outside this functional review.
  • The CodSpeed comparison used different runner environments and covers unrelated normalization/status/split benchmarks, so no CAPTCHA code change was warranted.

Local and pre-push gates passed: Rust tests, frontend tests, formatting, lint, typecheck, Clippy, cargo-deny, Knip, and production build.

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

🧹 Nitpick comments (2)
src-tauri/src/adapters/driven/plugin/hoster_contract_tests.rs (1)

101-117: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert that the maximum-size image is preserved.

is_ok() alone would still pass if parsing silently truncated or discarded the image. Assert the parsed CAPTCHA type and exact MAX_CAPTCHA_IMAGE_BYTES length to protect the boundary contract.

Proposed test strengthening
-    assert!(parse_hoster_link(&payload).is_ok());
+    let parsed = parse_hoster_link(&payload).expect("maximum-size image is accepted");
+    let captcha = parsed.captcha.expect("captcha metadata");
+    assert_eq!(captcha.challenge_type, CaptchaType::Image);
+    assert_eq!(
+        captcha.image_data.as_ref().map(|bytes| bytes.len()),
+        Some(MAX_CAPTCHA_IMAGE_BYTES)
+    );
🤖 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 `@src-tauri/src/adapters/driven/plugin/hoster_contract_tests.rs` around lines
101 - 117, Strengthen
test_parse_hoster_link_accepts_the_largest_valid_image_serialization by
capturing the successful parsed result and asserting it contains the image
CAPTCHA variant with exactly MAX_CAPTCHA_IMAGE_BYTES bytes. Preserve the
existing maximum-size payload setup while verifying the image is neither
truncated nor discarded.
src-tauri/src/application/queries/captcha.rs (1)

111-140: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test doesn't actually exercise "oldest" tie-breaking.

Only one pending challenge exists in the fixture (captcha-oldest), so the challenge_id: None fallback path trivially returns it regardless of whether oldest-first ordering is implemented correctly. Add a second, newer pending challenge to make this assertion meaningful.

♻️ Suggested test strengthening
     async fn get_pending_supports_explicit_id_and_oldest_fallback() {
         let mut solved = challenge("captcha-solved", 500);
         solved.solve(600, "manual").unwrap();
-        let bus = bus(vec![challenge("captcha-oldest", 1_000), solved]);
+        let bus = bus(vec![
+            challenge("captcha-oldest", 1_000),
+            challenge("captcha-newer", 2_000),
+            solved,
+        ]);
🤖 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 `@src-tauri/src/application/queries/captcha.rs` around lines 111 - 140,
Strengthen get_pending_supports_explicit_id_and_oldest_fallback by adding a
second pending challenge with a later timestamp to the bus fixture. Keep the
existing assertions and verify that the challenge_id: None fallback still
returns captcha-oldest, thereby exercising oldest-first selection rather than a
single-item result.
🤖 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.

Nitpick comments:
In `@src-tauri/src/adapters/driven/plugin/hoster_contract_tests.rs`:
- Around line 101-117: Strengthen
test_parse_hoster_link_accepts_the_largest_valid_image_serialization by
capturing the successful parsed result and asserting it contains the image
CAPTCHA variant with exactly MAX_CAPTCHA_IMAGE_BYTES bytes. Preserve the
existing maximum-size payload setup while verifying the image is neither
truncated nor discarded.

In `@src-tauri/src/application/queries/captcha.rs`:
- Around line 111-140: Strengthen
get_pending_supports_explicit_id_and_oldest_fallback by adding a second pending
challenge with a later timestamp to the bus fixture. Keep the existing
assertions and verify that the challenge_id: None fallback still returns
captcha-oldest, thereby exercising oldest-first selection rather than a
single-item result.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e45e18f9-7689-4c7f-b74a-e8c460a2ac74

📥 Commits

Reviewing files that changed from the base of the PR and between 1cfbf1e and 74ad5a7.

⛔ Files ignored due to path filters (1)
  • src-tauri/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (31)
  • CHANGELOG.md
  • src-tauri/Cargo.toml
  • src-tauri/src/adapters/driven/config/toml_config_store.rs
  • src-tauri/src/adapters/driven/event/tauri_bridge.rs
  • src-tauri/src/adapters/driven/logging/download_log_bridge.rs
  • src-tauri/src/adapters/driven/network/download_engine.rs
  • src-tauri/src/adapters/driven/network/download_engine_tests.rs
  • src-tauri/src/adapters/driven/plugin/hoster_contract.rs
  • src-tauri/src/adapters/driven/plugin/hoster_contract_tests.rs
  • src-tauri/src/adapters/driven/sqlite/captcha_repo.rs
  • src-tauri/src/adapters/driven/sqlite/captcha_repo_tests.rs
  • src-tauri/src/application/commands/cancel_download.rs
  • src-tauri/src/application/commands/captcha.rs
  • src-tauri/src/application/commands/captcha_tests.rs
  • src-tauri/src/application/commands/remove_download.rs
  • src-tauri/src/application/queries/captcha.rs
  • src-tauri/src/application/read_models/captcha_view.rs
  • src-tauri/src/domain/error.rs
  • src-tauri/src/domain/event.rs
  • src-tauri/src/domain/model/captcha.rs
  • src-tauri/src/domain/model/download.rs
  • src-tauri/src/domain/ports/driven/hoster_link.rs
  • src/hooks/__tests__/useCaptchaQueue.test.tsx
  • src/hooks/useCaptchaQueue.ts
  • src/hooks/useDownloadEvents.ts
  • src/types/captcha.ts
  • src/types/events.ts
  • src/views/CaptchaChallengePanel.tsx
  • src/views/CaptchaSolverSettings.tsx
  • src/views/CaptchaView.tsx
  • src/views/__tests__/CaptchaView.test.tsx
🚧 Files skipped from review as they are similar to previous changes (17)
  • src/types/captcha.ts
  • src-tauri/src/adapters/driven/sqlite/captcha_repo_tests.rs
  • src/hooks/useDownloadEvents.ts
  • src/hooks/useCaptchaQueue.ts
  • src/views/CaptchaSolverSettings.tsx
  • src-tauri/src/domain/model/download.rs
  • src-tauri/src/adapters/driven/logging/download_log_bridge.rs
  • src-tauri/src/adapters/driven/network/download_engine_tests.rs
  • src/views/CaptchaView.tsx
  • src-tauri/src/adapters/driven/network/download_engine.rs
  • src-tauri/src/domain/event.rs
  • src-tauri/src/adapters/driven/event/tauri_bridge.rs
  • src-tauri/src/application/commands/cancel_download.rs
  • src-tauri/src/adapters/driven/sqlite/captcha_repo.rs
  • src-tauri/src/adapters/driven/plugin/hoster_contract.rs
  • src-tauri/src/application/commands/captcha.rs
  • src-tauri/src/domain/model/captcha.rs

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 32 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src-tauri/src/adapters/driven/sqlite/captcha_repo.rs Outdated
Comment thread src-tauri/src/application/commands/cancel_download.rs Outdated
Comment thread src-tauri/src/domain/model/download.rs
Comment thread src-tauri/src/application/queries/captcha.rs
@mpiton
mpiton merged commit 4a2d6fe into main Jul 20, 2026
17 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant