Skip to content

feat(engine): usenet support - #133

Merged
YueMiyuki merged 10 commits into
masterfrom
next-dev
Aug 8, 2026
Merged

feat(engine): usenet support#133
YueMiyuki merged 10 commits into
masterfrom
next-dev

Conversation

@YueMiyuki

@YueMiyuki YueMiyuki commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Summary by cubic

Adds native Usenet (NZB) support end-to-end: provider profiles, secure credentials, bounded TLS NNTP, yEnc with resume, PAR2 verify/repair, safe archive handling, and NZB import (dialog, drag-and-drop, OS association). Adds an “NZB body timeout” setting and localized, actionable repair errors in the UI.

  • New Features

    • Engine: NNTP with TLS 1.2 via rustls/tokio-rustls, provider connection pooling/limits, yEnc assembly with resume, PAR2 verify/repair via rust-par2, archive path/resource safety with platform-aware limits and cleanup, NZB URL/file fetch with a 16 MB cap and configurable nzb-body-timeout (default 30s), new error codes 550–554, RPC risuko.addNzb.
    • Credentials: Vault-backed resolver with a local file fallback; wired into embedded, headless, and Tauri app startup; Tauri commands to save/remove/test credentials, check presence, and test profiles.
    • App: NZB import in Add Task (dialog, drag-and-drop) and .nzb file association; Advanced → HTTP adds “NZB body timeout”; Usenet Preferences for provider profiles (priority, security mode incl. implicit TLS/STARTTLS/plain, max connections), cleanup mode (keep all/delete PAR2/delete PAR2+archives), archive limits; keychain-backed credential save/remove/test; synced settings via usenet-profiles, usenet-archive-limits, usenet-cleanup-mode, usenet-limits-adjusted, nzb-body-timeout; Stats shows “Usenet”; batch add and UI copy updated for NZB; localized repair messages across languages and terminal Usenet errors render inline with actionable text.
  • Migration

    • Create at least one provider profile and save credentials to the keychain before importing NZBs.
    • Review archive limits and cleanup mode in Preferences → Usenet.

Written for commit 782d20f. Summary will update on new commits.

Review in cubic

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Summary by CodeRabbit

  • New Features
    • Added Usenet downloading with NZB file and URL support.
    • Added provider profiles, secure credential management, connection testing, failover, and retry handling.
    • Added resumable downloads with progress tracking and cancellation.
    • Added yEnc decoding, archive safeguards, PAR2 verification and repair, and configurable cleanup.
    • Added detailed task status, warnings, repair results, and localized error messages.
    • Added Usenet preferences, archive safety settings, batch NZB submission, synchronization, and .nzb file support.
  • Bug Fixes
    • Prevented automatic retries for terminal Usenet failures.
    • Preserved existing file metadata during task completion.

Walkthrough

This PR adds end-to-end Usenet support. It adds provider configuration, NZB ingestion, NNTP transport, resumable yEnc assembly, PAR2 repair, archive safety checks, task integration, credential commands, synchronization, and frontend controls.

Changes

Usenet download stack

Layer / File(s) Summary
Contracts and input validation
src/shared/types/*, src-tauri/risuko-engine/src/engine/usenet.rs, src-tauri/risuko-engine/src/engine/archive_safety.rs
Adds Usenet contracts, NZB parsing, provider validation, filename normalization, and archive-limit checks.
Archive assembly and PAR2 repair
src-tauri/risuko-engine/src/engine/archive_pipeline.rs, src-tauri/risuko-engine/src/engine/usenet_pipeline.rs, src-tauri/risuko-engine/src/engine/usenet_par2.rs
Adds yEnc decoding, resumable assembly, safe extraction, PAR2 verification and repair, transactional promotion, and cleanup handling.
NNTP transport and provider pooling
src-tauri/risuko-engine/src/engine/usenet_transport.rs
Adds TLS modes, authentication, article retrieval, connection capacity, provider rotation, cooldowns, and failover.
Usenet worker and task orchestration
src-tauri/risuko-engine/src/engine/usenet_worker.rs, src-tauri/risuko-engine/src/engine/manager.rs, src-tauri/risuko-engine/src/engine/task.rs
Connects provider selection, article assembly, PAR2 processing, task scheduling, progress reporting, cleanup, and structured repair failures.
Native NZB and credential bridge
src-tauri/src/commands/*, src-tauri/src/lib.rs, src/renderer/api/Api.ts, src/renderer/store/task.ts
Adds NZB path, URL, batch, and RPC submission. Adds credential commands and renderer integration.
Preferences, synchronization, and status UI
src/renderer/components/Preference/*, src/renderer/components/Task/*, src/renderer/store/*, src/shared/locales/*
Adds Usenet preferences, NZB queue handling, synchronization merging, repair messages, routes, labels, and translations.
Tooling and dependency updates
biome.json, package.json, src-tauri/Cargo.toml, src-tauri/risuko-engine/Cargo.toml
Updates tool versions and adds Rust dependencies and TLS features.
Startup and resolver setup
src-tauri/risuko-cli/src/commands.rs, src-tauri/src/cli/headless.rs, src-tauri/risuko-napi/src/lib.rs, src-tauri/risuko-engine/src/engine/mod.rs
Initializes file-backed Usenet credential resolution during engine startup.

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

Possibly related PRs

  • YueMiyuki/Risuko#54: The credential resolver and credential-management changes overlap with the vault-backed credential functionality.
  • YueMiyuki/Risuko#60: Both PRs extend engine task, RPC, manager, and error-classification code for protocol support.
  • YueMiyuki/Risuko#92: Both PRs extend task routing, task models, manager, RPC, API, and error classification for download protocols.

Suggested labels: next

Poem

A rabbit checks each NZB line,
While PAR2 makes the bytes align.
TLS guards the articles in flight,
Resume files preserve the night.
Usenet tasks now hop just right.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.50% 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 The description accurately summarizes the end-to-end Usenet and NZB support added by the changeset.
Title check ✅ Passed The title clearly identifies the primary change: adding Usenet support to the engine.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

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.

@YueMiyuki YueMiyuki linked an issue Aug 6, 2026 that may be closed by this pull request
@coderabbitai coderabbitai Bot added the next The "next" steps label Aug 6, 2026

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

8 issues found across 46 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src-tauri/src/commands/engine_cmds.rs">

<violation number="1" location="src-tauri/src/commands/engine_cmds.rs:595">
P1: An NZB URL can consume unbounded memory despite the intended 16 MiB limit when the server omits `Content-Length`; the entire response is collected before `bytes.len()` is checked. Reading `bytes_stream()` incrementally and aborting as soon as the accumulated size exceeds the limit would enforce the cap for chunked responses too.</violation>
</file>

<file name="src/renderer/store/batchQueue.ts">

<violation number="1" location="src/renderer/store/batchQueue.ts:96">
P2: Double-clicking or opening an `.nzb` with the app will not reach this new batch flow because Tauri does not register `.nzb` as an associated file type; registering the extension in the native file-association configuration would make this support work consistently with drag-and-drop and the file picker.</violation>
</file>

<file name="src-tauri/risuko-engine/src/engine/usenet_par2.rs">

<violation number="1" location="src-tauri/risuko-engine/src/engine/usenet_par2.rs:134">
P2: Cancellation and the active-time limit are not enforced during PAR2 verification: a large set runs to completion before the checks at lines 135-136 execute. A cancellable/bounded verification path would keep cancellation responsive and prevent the worker from consuming resources past its configured deadline.</violation>
</file>

<file name="src/renderer/router/index.ts">

<violation number="1" location="src/renderer/router/index.ts:69">
P3: The new /preference/usenet route makes the Usenet tab reachable, but the `preferences.usenet` label (and the `preferences.usenet-*` strings used inside Usenet.vue) only exist in en-US; the other ~20 locale files under src/shared/locales don't define them, so non-English users see either the English fallback or the raw key depending on the i18n fallback config. Suggest adding the `usenet` keys to the remaining locale preference files to keep translations complete.</violation>
</file>

<file name="package.json">

<violation number="1" location="package.json:70">
P2: This feature PR also carries unrelated major-version dependency upgrades across multiple package boundaries (chalk 5→6, pinia 3→4, TypeScript 6→7, cnfast 0.0→0.1). These can silently change runtime and type behavior across the whole app; since the PR has no description or migration notes, worth splitting the bumps into a dedicated chore PR and validating with `pnpm typecheck` plus a runtime smoke test before merging.</violation>
</file>

<file name="src-tauri/risuko-engine/src/engine/manager.rs">

<violation number="1" location="src-tauri/risuko-engine/src/engine/manager.rs:1017">
P2: Configured global Usenet archive limits are ignored for NZB tasks unless the caller redundantly sends them as per-task options, causing the worker to use platform defaults instead. Capture the effective merged limits when creating the task or make `archive_limits_for_task` fall back to its merged `options` argument.</violation>
</file>

<file name="src-tauri/risuko-engine/Cargo.toml">

<violation number="1" location="src-tauri/risuko-engine/Cargo.toml:45">
P3: `rust-par2 = "=0.1.3"` is the only dependency in this Cargo.toml pinned to an exact version; the rest use caret ranges. The reason for the lock (early 0.1.x API churn) is reasonable, but it silently freezes the crate at 0.1.3 forever, blocking bug/security fixes, and a future reader won't know if the pin is deliberate. Consider a brief comment explaining why, or a `~0.1.3` range if only the minor is meant to be locked.</violation>
</file>

<file name="src-tauri/risuko-engine/src/engine/archive_pipeline.rs">

<violation number="1" location="src-tauri/risuko-engine/src/engine/archive_pipeline.rs:47">
P2: The new yEnc decoder is not on the Usenet download path, so its tests provide no coverage of the decoder actually used by the worker and this implementation will drift from `usenet_pipeline::decode_yenc_part`. Wiring one implementation into the worker or removing the duplicate would make the feature behavior match the code under test.</violation>
</file>

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

Re-trigger cubic

Comment thread src-tauri/risuko-engine/src/engine/usenet_transport.rs Outdated
Comment thread src-tauri/risuko-engine/src/engine/usenet_transport.rs Outdated
Comment thread src-tauri/src/commands/engine_cmds.rs Outdated
return Err("NZB URL payload too large".to_string());
}
let bytes = response
.bytes()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: An NZB URL can consume unbounded memory despite the intended 16 MiB limit when the server omits Content-Length; the entire response is collected before bytes.len() is checked. Reading bytes_stream() incrementally and aborting as soon as the accumulated size exceeds the limit would enforce the cap for chunked responses too.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src-tauri/src/commands/engine_cmds.rs, line 595:

<comment>An NZB URL can consume unbounded memory despite the intended 16 MiB limit when the server omits `Content-Length`; the entire response is collected before `bytes.len()` is checked. Reading `bytes_stream()` incrementally and aborting as soon as the accumulated size exceeds the limit would enforce the cap for chunked responses too.</comment>

<file context>
@@ -526,6 +526,110 @@ async fn add_metalink_by_path_inner(path: &str, options: Option<Value>) -> Resul
+        return Err("NZB URL payload too large".to_string());
+    }
+    let bytes = response
+        .bytes()
+        .await
+        .map_err(|e| format!("NZB URL body read failed: {e}"))?
</file context>

Comment thread src-tauri/risuko-engine/src/engine/archive_safety.rs Outdated
Comment thread src-tauri/risuko-engine/src/engine/mod.rs
Comment thread src-tauri/risuko-engine/src/engine/archive_pipeline.rs
Comment thread src-tauri/risuko-engine/src/engine/task.rs Outdated
Comment thread src/renderer/components/Native/EngineClient.vue
},
{
path: "usenet",
component: () => import("@/components/Preference/Usenet.vue"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The new /preference/usenet route makes the Usenet tab reachable, but the preferences.usenet label (and the preferences.usenet-* strings used inside Usenet.vue) only exist in en-US; the other ~20 locale files under src/shared/locales don't define them, so non-English users see either the English fallback or the raw key depending on the i18n fallback config. Suggest adding the usenet keys to the remaining locale preference files to keep translations complete.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/renderer/router/index.ts, line 69:

<comment>The new /preference/usenet route makes the Usenet tab reachable, but the `preferences.usenet` label (and the `preferences.usenet-*` strings used inside Usenet.vue) only exist in en-US; the other ~20 locale files under src/shared/locales don't define them, so non-English users see either the English fallback or the raw key depending on the i18n fallback config. Suggest adding the `usenet` keys to the remaining locale preference files to keep translations complete.</comment>

<file context>
@@ -64,6 +64,10 @@ export default createRouter({
 						},
+						{
+							path: "usenet",
+							component: () => import("@/components/Preference/Usenet.vue"),
+						},
 						{
</file context>

async-trait = "0.1"
bytes = "1"
tempfile = "3"
rust-par2 = "=0.1.3"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: rust-par2 = "=0.1.3" is the only dependency in this Cargo.toml pinned to an exact version; the rest use caret ranges. The reason for the lock (early 0.1.x API churn) is reasonable, but it silently freezes the crate at 0.1.3 forever, blocking bug/security fixes, and a future reader won't know if the pin is deliberate. Consider a brief comment explaining why, or a ~0.1.3 range if only the minor is meant to be locked.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src-tauri/risuko-engine/Cargo.toml, line 45:

<comment>`rust-par2 = "=0.1.3"` is the only dependency in this Cargo.toml pinned to an exact version; the rest use caret ranges. The reason for the lock (early 0.1.x API churn) is reasonable, but it silently freezes the crate at 0.1.3 forever, blocking bug/security fixes, and a future reader won't know if the pin is deliberate. Consider a brief comment explaining why, or a `~0.1.3` range if only the minor is meant to be locked.</comment>

<file context>
@@ -41,6 +41,9 @@ risuko-http = { workspace = true }
 async-trait = "0.1"
 bytes = "1"
+tempfile = "3"
+rust-par2 = "=0.1.3"
+fs4 = "1"
 url = "2"
</file context>

@YueMiyuki

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 24 minutes.

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

🤖 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/risuko-engine/src/engine/archive_pipeline.rs`:
- Line 51: Remove the unused begin_header binding and its corresponding
discarded parse_header call in the archive pipeline, leaving find_line as the
presence check and preserving the surrounding parsing flow.
- Around line 292-302: Update the permission-setting logic in the archive
extraction flow to apply permissions through the already-open file handle rather
than calling fs::set_permissions with path. Preserve the existing metadata
lookup and executable-bit masking, and use the handle’s permission API before it
is released.
- Around line 515-533: Update UsenetResumeState::save_atomic to create the
temporary file with fs::File::create, write the serialized bytes using
write_all, and call sync_all before renaming it into place. Preserve the
existing directory creation, temporary-path construction, and atomic rename
behavior.

In `@src-tauri/risuko-engine/src/engine/archive_safety.rs`:
- Around line 78-113: Update validate_limits_override to include
free_space_reserve_bytes in the confirmation comparison, requiring confirmed
when the requested reserve is below defaults.free_space_reserve_bytes, including
zero. Do not add it to the hard-ceiling checks unless an existing ceiling
contract requires it, and add a regression test covering an unconfirmed zero
reserve returning ArchiveSafetyError::OverrideConfirmation.

In `@src-tauri/risuko-engine/src/engine/error_code.rs`:
- Around line 226-248: Update the "usenet" classification logic in the
error-mapping function to recognize worker messages prefixed with "archive
safety:". Map "UnsafePath" to USENET_ARCHIVE_UNSAFE and archive resource-limit
variants such as "ExpandedBytes" to USENET_ARCHIVE_LIMIT, preserving the
existing precedence. Add or update tests using the actual formatted
archive_pipeline messages to verify both mappings.

In `@src-tauri/risuko-engine/src/engine/manager.rs`:
- Around line 1605-1620: Make the worker-to-manager output pairing explicit
instead of relying on vector position: update
run_usenet_download_with_resolver_and_capacity and its caller so each output is
keyed by the manifest file name or index, then use that key when constructing
task.files in the metadata.files mapping. Preserve the existing safe_filename
fallback for missing outputs and ensure UI and files_for_upload receive the path
associated with the correct manifest entry.
- Around line 936-939: Update the DownloadFile construction in the manager flow
to sanitize file.name with crate::engine::util::safe_filename and build the path
using Path::join with dir, matching the worker and completion-handler path
handling.
- Around line 953-973: Replace the credential-removal logic in the options
persistence path with an explicit allowlist for non-secret fields in
usenet-profiles, or normalize each raw profile through UsenetProviderProfile
before storing it. Ensure only approved non-sensitive profile fields and options
are persisted, so newly added secret keys cannot survive unnoticed.
- Around line 1717-1723: Update the Usenet task flow around
spawn_usenet_download so usenet_stage reflects each active phase: retain
“connecting” before spawning, set the appropriate fetch/assembly stage during
segment processing, and update it again when PAR2 verification or repair begins
and completes, including the existing “complete” and “error” outcomes.

In `@src-tauri/risuko-engine/src/engine/mod.rs`:
- Around line 110-115: Update save_fallback and remove_fallback to enforce
owner-only access on usenet-credentials.json after filesystem writes or
creation, using Unix permissions and equivalent Windows user-only ACL handling.
Keep the existing usenet_credential_fallback_path location and credential
persistence behavior unchanged.

In `@src-tauri/risuko-engine/src/engine/usenet_par2.rs`:
- Around line 172-175: In the affected execution flow, remove one of the
consecutive duplicate check_cancel(cancel) and check_active_time(request) pairs,
leaving a single cancellation and active-time check before subsequent work.
- Around line 775-777: Update the missing-entry branch in the PAR2 processing
function around data_by_name.get to return a recoverable error variant instead
of Par2Error::UnsafePath, while preserving the existing filename validation.
Ensure parse_index can skip this absent source file and continue processing
remaining parity files.
- Around line 646-652: Update the buffer handling in
verify_or_repair_with_cancel’s packet-processing loop to heap-allocate one
SPARSE_COPY_BUFFER_BYTES-sized buffer before the loop, then reuse it for each
iteration instead of using the stack-allocated array. Match sparse_copy’s
existing vec!-based allocation while preserving the current read_exact,
hasher.update, and remaining logic.
- Line 605: Replace the is_multiple_of calls in the body-length validation at
the visible condition and its corresponding use near line 740 with equivalent
modulo-based checks compatible with Rust 1.85; do not change the workspace
toolchain.
- Around line 360-408: Add a nearby comment above validate_repair_resources
documenting that its memory estimate matches rust-par2 version =0.1.3 and
briefly identifying the assumed internal allocations represented by the matrix,
recovery, repair-buffer, and verification-buffer calculations. Keep the
implementation unchanged.

In `@src-tauri/risuko-engine/src/engine/usenet_pipeline.rs`:
- Around line 486-496: Update validate_existing_receipts to open the part file
once before iterating over sidecar.segment_receipts, then pass a mutable
reference to that handle into each hash operation. Adjust hash_file_range and
its call sites as needed to seek and hash through the provided file instead of
reopening the path for every receipt, preserving existing validation and error
behavior.
- Around line 852-872: Reduce per-segment I/O in the assembly loop by hoisting
resize_assembled_part out of the loop and only rerunning it when expected_size
changes. Replace unconditional sidecar.save_atomic and file.sync_data calls with
checkpoint persistence on a segment/time interval, while ensuring the latest
sidecar and assembled-file data are flushed on all exit paths. Preserve receipt
validation so resume replays only segments after the last checkpoint.
- Around line 451-464: Centralize the CRC-32 implementation used by the engine.
In src-tauri/risuko-engine/src/engine/usenet_pipeline.rs lines 451-464, retain
the crc32 helper as the shared exported definition or replace it with crc32fast;
in src-tauri/risuko-engine/src/engine/archive_pipeline.rs lines 177-190, remove
the duplicate and update decode_yenc and its test module to call the shared
helper. Also update the third duplicate in the usenet_par2 test module to use
the same helper.
- Around line 961-965: In the PAR2 repair completion flow, clear the stale
segment_receipts on the ResumeSidecar before saving it. Update the sidecar
mutation alongside expected_size, completed_bytes, and repaired so the persisted
receipt state is empty and consistent with the rewritten file.
- Around line 142-147: Update the resume metadata loading logic around the
serde_json parse in load so parse failures return a fresh sidecar via
Self::new(manifest_sha256.to_string()), matching the existing version and
manifest fingerprint mismatch behavior. Do not propagate the parse error for
corrupted or truncated metadata files.

In `@src-tauri/risuko-engine/src/engine/usenet_transport.rs`:
- Around line 389-404: Update read_multiline to bound the number of received
lines in addition to total_bytes, ensuring streams of empty lines cannot grow
lines without limit. Enforce the existing ArticleTooLarge error once the
line-count or equivalent per-line overhead limit is exceeded, while preserving
normal "." termination and successful collection behavior.
- Around line 446-458: Update tls_connector to lazily initialize and cache a
single TlsConnector, while preserving one-time crypto provider installation and
the existing root certificate/configuration setup. Return a cheap clone of the
cached connector on subsequent calls so NntpConnection::connect and reconnects
do not rebuild the root store or ClientConfig.
- Around line 229-240: Update `upgrade_tls` to inspect `self.reader.buffer()`
before calling `into_inner()` and return an appropriate connection error when
buffered plaintext remains. Only proceed with the TLS handshake when the buffer
is empty, preserving the existing timeout and TLS error handling.

In `@src-tauri/risuko-engine/src/engine/usenet_worker.rs`:
- Around line 709-712: Replace the hardcoded archive_extraction_verified value
in the cleanup flow with the actual archive extraction verification result used
by the worker, so cleanup_inputs can populate archive_inputs and
CleanupMode::DeletePar2AndVolumes removes archive volumes when verification
succeeds. If no verification result exists in this path, explicitly mark the
mode unsupported and remove or disable it in the preferences UI rather than
presenting it as functional.
- Line 195: Remove the unnecessary profile_sessions.fetch_lock acquisition from
fetch after confirming no shared invariant requires it. Update the assembly loop
in usenet_pipeline.rs to initiate and await source.fetch operations
concurrently, while preserving result ordering and allowing the existing
capacity registry and per-profile max_connections limits to bound concurrency.
- Around line 877-894: The OutputReservation lifecycle currently leaves
.risuko-usenet-locks lock files behind after release. Update OutputReservation’s
Drop/cleanup behavior to remove its associated lock_path when the reservation is
dropped, while preserving the held file lock until cleanup completes;
alternatively, explicitly document the directory as intentionally persistent if
removal is not feasible.

In `@src-tauri/src/commands/engine_cmds.rs`:
- Around line 797-806: Update is_plain_http_mirror_uri to exclude any URI for
which is_nzb_url returns true, ensuring NZB URLs reach the existing
fetch_nzb_url branch rather than HTTP mirror grouping. Add a test covering two
NZB URLs and verify each manifest is fetched and converted into its own Usenet
task.

In `@src-tauri/src/commands/usenet_cmds.rs`:
- Around line 83-90: Update save_fallback so Usenet credentials are never
written as raw JSON; route persistence through the OS vault, or an encrypted
storage implementation that explicitly enforces owner-only permissions on every
supported platform. Remove or replace the current serde_json serialization and
std::fs::write path while preserving the existing Result error propagation.
- Around line 83-105: Update save_fallback and remove_fallback to coordinate
through a shared lock covering the entire load-modify-write operation,
preventing concurrent credential commands from overwriting entries. Replace
direct std::fs::write calls with writing serialized data to a temporary file in
the same directory, then atomically replace the fallback file; preserve
empty-file removal behavior in remove_fallback and clean up temporary files on
failure.

In `@src/renderer/components/Preference/Usenet.vue`:
- Around line 868-890: Update saveProfile and removeProfile so profile
preference changes and Usenet credential changes are failure-atomic across both
stores. Use a native transaction if available; otherwise apply verified
compensating rollback for whichever store was changed when the other operation
fails. Ensure rollback failures are handled and never report completion while
either store retains a partial change, including the credential-removal path
around the referenced lines.
- Around line 624-644: Update mergeUsenetCategory to clamp synced
usenetArchiveLimits against ANDROID_USENET_ARCHIVE_LIMITS before saving when
running on Android. Preserve the existing native validation as the final check,
and leave non-Android handling unchanged.

In `@src/renderer/store/sync.ts`:
- Around line 39-44: Add and persist a profile-level updatedAt timestamp in the
profile creation and mutation paths used by UsenetProviderProfile, ensuring it
changes whenever profile data changes and survives synchronization. Update the
merge logic around currentAt, previousAt, and byId.set to compare these
timestamps so the newer profile wins, rather than letting equal missing
timestamps cause remote profiles to replace local ones.
- Around line 68-75: Update the archive-limit normalization logic around
clamped[key] to accept only finite positive safe integers. Store the normalized
Number(clamped[key]) value for valid inputs, and assign fallback with adjusted
set for invalid or unsafe values; retain the existing upper-bound clamping
behavior for valid values exceeding fallback * 4.

In `@src/shared/locales/en-US/preferences.ts`:
- Around line 15-16: Update the "usenet-profile-dialog-tips" localization to use
conditional UI text based on vault availability: mention secure keychain storage
only when the vault is available, and otherwise state that credentials remain
local to the device without promising encryption.

In `@src/shared/utils/usenet.ts`:
- Around line 16-24: Add the missing task.usenet-repair-insufficient,
task.usenet-repair-partials-retained, and
task.usenet-repair-partials-unavailable translations to every registered task
locale that lacks them, preserving the existing interpolation keys needed by the
summary and matching the established locale translation structure.
🪄 Autofix

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: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: db629dc0-7e49-4a38-bff3-2001eab08f51

📥 Commits

Reviewing files that changed from the base of the PR and between 7af2ad2 and a6e7712.

⛔ Files ignored due to path filters (2)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • src-tauri/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (50)
  • biome.json
  • package.json
  • src-tauri/Cargo.toml
  • src-tauri/risuko-cli/src/commands.rs
  • src-tauri/risuko-engine/Cargo.toml
  • src-tauri/risuko-engine/src/engine/archive_pipeline.rs
  • src-tauri/risuko-engine/src/engine/archive_safety.rs
  • src-tauri/risuko-engine/src/engine/error_code.rs
  • src-tauri/risuko-engine/src/engine/http.rs
  • src-tauri/risuko-engine/src/engine/manager.rs
  • src-tauri/risuko-engine/src/engine/mod.rs
  • src-tauri/risuko-engine/src/engine/options.rs
  • src-tauri/risuko-engine/src/engine/rpc.rs
  • src-tauri/risuko-engine/src/engine/task.rs
  • src-tauri/risuko-engine/src/engine/usenet.rs
  • src-tauri/risuko-engine/src/engine/usenet_par2.rs
  • src-tauri/risuko-engine/src/engine/usenet_pipeline.rs
  • src-tauri/risuko-engine/src/engine/usenet_transport.rs
  • src-tauri/risuko-engine/src/engine/usenet_worker.rs
  • src-tauri/risuko-napi/src/lib.rs
  • src-tauri/src/cli/headless.rs
  • src-tauri/src/commands/engine_cmds.rs
  • src-tauri/src/commands/mod.rs
  • src-tauri/src/commands/usenet_cmds.rs
  • src-tauri/src/lib.rs
  • src-tauri/tauri.conf.json
  • src/renderer/api/Api.ts
  • src/renderer/components/Native/EngineClient.vue
  • src/renderer/components/Preference/Index.vue
  • src/renderer/components/Preference/Usenet.vue
  • src/renderer/components/Stats/StatsPage.vue
  • src/renderer/components/Task/AddTask.vue
  • src/renderer/components/Task/BatchItemCard.vue
  • src/renderer/components/TaskDetail/TaskGeneral.vue
  • src/renderer/router/index.ts
  • src/renderer/store/batchQueue.ts
  • src/renderer/store/sync.ts
  • src/renderer/store/task.ts
  • src/shared/configKeys.ts
  • src/shared/locales/en-US/preferences.ts
  • src/shared/locales/en-US/sync.ts
  • src/shared/locales/en-US/task.ts
  • src/shared/locales/zh-TW/sync.ts
  • src/shared/locales/zh-TW/task.ts
  • src/shared/syncCategories.ts
  • src/shared/types/config.ts
  • src/shared/types/task.ts
  • src/shared/types/usenet.ts
  • src/shared/utils/index.ts
  • src/shared/utils/usenet.ts

Comment thread src-tauri/risuko-engine/src/engine/archive_pipeline.rs Outdated
Comment thread src-tauri/risuko-engine/src/engine/archive_pipeline.rs Outdated
Comment thread src-tauri/risuko-engine/src/engine/archive_pipeline.rs
Comment thread src-tauri/risuko-engine/src/engine/archive_safety.rs
Comment thread src-tauri/risuko-engine/src/engine/error_code.rs
Comment thread src/renderer/components/Preference/Usenet.vue
Comment thread src/renderer/store/sync.ts
Comment thread src/renderer/store/sync.ts
Comment thread src/shared/locales/en-US/preferences.ts
Comment on lines +16 to +24
const summary = translate("task.usenet-repair-insufficient", {
neededBlocks: failure.neededBlocks,
availableBlocks: failure.availableBlocks,
});
const nextStep = translate(
failure.partialsRetained
? "task.usenet-repair-partials-retained"
: "task.usenet-repair-partials-unavailable",
);

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -p 'task.ts' src/shared/locales -x sh -c '
  printf "\n%s\n" "$1"
  rg -n "\"usenet-repair-(insufficient|partials-retained|partials-unavailable)\"" "$1"
' sh {}

Repository: YueMiyuki/Risuko

Length of output: 1208


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Locale task files:\n'
fd -a 'task.ts$' src/shared/locales | sort

printf '\nUsenet-repair key matches:\n'
rg -n '"usenet-repair-(insufficient|partials-retained|partials-unavailable)"' src/shared/locales || true

printf '\nFile excerpts around matches:\n'
while IFS= read -r file; do
  [ -z "$file" ] && continue
  printf '\n--- %s ---\n' "$file"
  rg -n -C 2 '"usenet-repair-(insufficient|partials-retained|partials-unavailable)"' "$file" || true
done < <(fd -a 'task.ts$' src/shared/locales | sort)

Repository: YueMiyuki/Risuko

Length of output: 2005


Add the missing usenet-repair-* translations.

Most registered task locales only include task.usenet-repair-insufficient, task.usenet-repair-partials-retained, and task.usenet-repair-partials-unavailable in en-US and zh-TW. Repair failures will display the translation identifiers in the other locales.

🤖 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/shared/utils/usenet.ts` around lines 16 - 24, Add the missing
task.usenet-repair-insufficient, task.usenet-repair-partials-retained, and
task.usenet-repair-partials-unavailable translations to every registered task
locale that lacks them, preserving the existing interpolation keys needed by the
summary and matching the established locale translation structure.

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

1 issue found across 26 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src-tauri/risuko-engine/src/engine/usenet_worker.rs">

<violation number="1" location="src-tauri/risuko-engine/src/engine/usenet_worker.rs:722">
P2: Cleanup modes that delete PAR2 files now persist those deleted paths as completed task outputs, leaving completed Usenet task metadata pointing at files that no longer exist. Return output metadata that excludes cleaned files or preserve manifest-to-output mapping while omitting deleted entries from `task.files`.</violation>
</file>

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

Re-trigger cubic

Comment thread src-tauri/risuko-engine/src/engine/http.rs Outdated
Comment thread src-tauri/src/commands/usenet_cmds.rs Outdated
Comment thread src-tauri/risuko-engine/src/engine/usenet_transport.rs Outdated
Comment thread src-tauri/risuko-engine/src/engine/usenet_par2.rs
Comment thread src-tauri/risuko-engine/src/engine/http.rs Outdated
Comment thread src-tauri/risuko-engine/src/engine/usenet_worker.rs Outdated
Comment thread src/renderer/components/Preference/Usenet.vue
Comment thread src/renderer/api/Api.ts
Comment thread src-tauri/src/commands/usenet_cmds.rs Outdated
Comment thread src-tauri/risuko-engine/src/engine/mod.rs Outdated

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src-tauri/risuko-engine/src/engine/usenet_worker.rs (1)

686-715: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Make PAR2 verification and repair cancel cooperatively.

Cancellation is only checked outside rust_par2::verify and rust_par2::repair_from_verify. When PAR2 processes a large set, verify, repair_from_verify, and the final reverify blocks the parent task until they return, so pause/remove/active-time expiry can remain unresponsive through the blocking job. Add cooperative cancellation inside the long PAR2 operations, or switch to a cancellable PAR2 implementation. Add a test that cancels during a deliberately slow PAR2 operation.

🤖 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/risuko-engine/src/engine/usenet_worker.rs` around lines 686 - 715,
Update verify_or_repair_with_cancel so cancellation is checked cooperatively
throughout rust_par2 verification, repair_from_verify, and final reverify
operations rather than only around the blocking task. Use a cancellable PAR2
implementation or cancellation-aware processing that promptly stops on the
provided token while preserving existing error handling. Add a test that cancels
during a deliberately slow PAR2 operation and verifies it exits promptly.
🤖 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/risuko-engine/src/engine/usenet_par2.rs`:
- Around line 935-957: Update copy_file_with_cancel to remove destination
whenever copying, syncing, or cancellation fails after the destination is
created, while preserving the original error result. Follow the cleanup pattern
used by write_fallback_atomic, and add a test that cancels after the first
buffer write and verifies destination is absent.

In `@src-tauri/risuko-engine/src/engine/usenet_pipeline.rs`:
- Around line 847-955: Eliminate the per-segment linear scans in the assembly
loop: change the fetch pipeline to carry the manifest segment reference (or
equivalent) in each fetched tuple so the `ordered.iter().find(...)` lookup is
unnecessary. Maintain running receipt-byte and article-byte totals initialized
from resume state, incrementing them after each successful write using the
candidate receipt length and segment byte count, then assign
`sidecar.completed_bytes` and publish progress from those counters instead of
calling `completed_receipt_bytes` and `completed_article_bytes` on every
iteration.

In `@src-tauri/risuko-engine/src/engine/usenet_worker.rs`:
- Around line 495-499: Update OutputReservation::drop to stop removing the lock
file path; only release the held lock by dropping or closing self.lock_file,
leaving the deterministic lock file in place.

In `@src/shared/locales/ar/task.ts`:
- Around line 135-140: Translate the three new repair messages, identified by
usenet-repair-insufficient, usenet-repair-partials-retained, and
usenet-repair-partials-unavailable, in src/shared/locales/ar/task.ts lines
135-140, src/shared/locales/it/task.ts lines 142-147,
src/shared/locales/ja/task.ts lines 139-144, src/shared/locales/ko/task.ts lines
136-141, src/shared/locales/nb/task.ts lines 141-146,
src/shared/locales/nl/task.ts lines 143-148, and src/shared/locales/pl/task.ts
lines 142-147. Preserve the existing interpolation keys {{neededBlocks}} and
{{availableBlocks}} in every locale.

In `@src/shared/locales/bg/task.ts`:
- Around line 139-144: Translate the usenet-repair-insufficient,
usenet-repair-partials-retained, and usenet-repair-partials-unavailable messages
in src/shared/locales/bg/task.ts (139-144), src/shared/locales/ca/task.ts
(144-149), src/shared/locales/de/task.ts (143-148),
src/shared/locales/el/task.ts (144-149), src/shared/locales/es/task.ts
(142-147), src/shared/locales/fa/task.ts (136-141),
src/shared/locales/fr/task.ts (138-143), src/shared/locales/hu/task.ts
(134-139), and src/shared/locales/id/task.ts (138-143) into each locale’s
language, preserving the {{neededBlocks}} and {{availableBlocks}} placeholders
in every translation.

In `@src/shared/locales/pt-BR/task.ts`:
- Around line 142-147: Translate the three repair message values in the task
locale entries, preserving the {{neededBlocks}} and {{availableBlocks}}
placeholders and the original meaning. Apply the reviewed native translations in
src/shared/locales/pt-BR/task.ts lines 142-147, src/shared/locales/ro/task.ts
lines 143-148, src/shared/locales/ru/task.ts lines 140-145,
src/shared/locales/th/task.ts lines 131-136, src/shared/locales/tr/task.ts lines
140-145, src/shared/locales/uk/task.ts lines 140-145,
src/shared/locales/vi/task.ts lines 139-144, and
src/shared/locales/zh-CN/task.ts lines 304-309.

---

Outside diff comments:
In `@src-tauri/risuko-engine/src/engine/usenet_worker.rs`:
- Around line 686-715: Update verify_or_repair_with_cancel so cancellation is
checked cooperatively throughout rust_par2 verification, repair_from_verify, and
final reverify operations rather than only around the blocking task. Use a
cancellable PAR2 implementation or cancellation-aware processing that promptly
stops on the provided token while preserving existing error handling. Add a test
that cancels during a deliberately slow PAR2 operation and verifies it exits
promptly.
🪄 Autofix

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: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6622b492-e5b0-4a4c-9930-4b74bf840450

📥 Commits

Reviewing files that changed from the base of the PR and between a6e7712 and a29c9fb.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (43)
  • package.json
  • src-tauri/Cargo.toml
  • src-tauri/risuko-engine/src/engine/archive_pipeline.rs
  • src-tauri/risuko-engine/src/engine/archive_safety.rs
  • src-tauri/risuko-engine/src/engine/error_code.rs
  • src-tauri/risuko-engine/src/engine/http.rs
  • src-tauri/risuko-engine/src/engine/manager.rs
  • src-tauri/risuko-engine/src/engine/mod.rs
  • src-tauri/risuko-engine/src/engine/usenet.rs
  • src-tauri/risuko-engine/src/engine/usenet_par2.rs
  • src-tauri/risuko-engine/src/engine/usenet_pipeline.rs
  • src-tauri/risuko-engine/src/engine/usenet_transport.rs
  • src-tauri/risuko-engine/src/engine/usenet_worker.rs
  • src-tauri/src/commands/engine_cmds.rs
  • src-tauri/src/commands/usenet_cmds.rs
  • src/renderer/api/Api.ts
  • src/renderer/components/Preference/Usenet.vue
  • src/renderer/store/sync.ts
  • src/shared/locales/ar/task.ts
  • src/shared/locales/bg/task.ts
  • src/shared/locales/ca/task.ts
  • src/shared/locales/de/task.ts
  • src/shared/locales/el/task.ts
  • src/shared/locales/en-US/preferences.ts
  • src/shared/locales/es/task.ts
  • src/shared/locales/fa/task.ts
  • src/shared/locales/fr/task.ts
  • src/shared/locales/hu/task.ts
  • src/shared/locales/id/task.ts
  • src/shared/locales/it/task.ts
  • src/shared/locales/ja/task.ts
  • src/shared/locales/ko/task.ts
  • src/shared/locales/nb/task.ts
  • src/shared/locales/nl/task.ts
  • src/shared/locales/pl/task.ts
  • src/shared/locales/pt-BR/task.ts
  • src/shared/locales/ro/task.ts
  • src/shared/locales/ru/task.ts
  • src/shared/locales/th/task.ts
  • src/shared/locales/tr/task.ts
  • src/shared/locales/uk/task.ts
  • src/shared/locales/vi/task.ts
  • src/shared/locales/zh-CN/task.ts

Comment thread src-tauri/risuko-engine/src/engine/usenet_par2.rs
Comment thread src-tauri/risuko-engine/src/engine/usenet_pipeline.rs Outdated
Comment thread src-tauri/risuko-engine/src/engine/usenet_worker.rs
Comment thread src/shared/locales/ar/task.ts Outdated
Comment thread src/shared/locales/bg/task.ts Outdated
Comment thread src/shared/locales/pt-BR/task.ts Outdated

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

1 issue found across 44 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src-tauri/risuko-engine/src/engine/usenet_pipeline.rs">

<violation number="1" location="src-tauri/risuko-engine/src/engine/usenet_pipeline.rs:148">
P2: Malformed resume metadata still aborts normal Usenet downloads before this fresh-sidecar fallback runs, because output-path reservation parses the same file first. Apply the same malformed-sidecar recovery policy in the reservation/matching path.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src-tauri/risuko-engine/src/engine/usenet_worker.rs Outdated
Comment thread src-tauri/risuko-engine/src/engine/usenet_pipeline.rs
Comment thread src/shared/locales/tr/task.ts
Comment thread src/shared/locales/ca/task.ts
Comment thread src/shared/locales/zh-CN/task.ts
Comment thread src/shared/locales/hu/task.ts
Comment thread src/shared/locales/ar/task.ts
Comment thread src/shared/locales/it/task.ts
Comment thread src/shared/locales/ko/task.ts
Comment thread src/shared/locales/ro/task.ts Outdated

@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 44 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src-tauri/risuko-engine/src/engine/usenet_pipeline.rs Outdated
Comment thread src-tauri/risuko-engine/src/engine/http.rs Outdated
Comment thread src-tauri/src/commands/usenet_cmds.rs Outdated
Comment thread src-tauri/risuko-engine/src/engine/manager.rs

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src-tauri/risuko-engine/src/engine/usenet_worker.rs (1)

955-981: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the positional index == 2 check with an explicit sidecar branch.

The sidecar-specific handling is selected by the array position 2. Any reordering or insertion in the array literal silently applies sidecar parsing to the wrong path, or applies plain existence checks to the sidecar. Bind the sidecar path separately so the intent is explicit.

A secondary edge case: if the sidecar path is a directory or a broken symlink, tokio::fs::read fails and the whole reservation returns an error instead of treating the slot as occupied and moving to the next candidate name.

♻️ Proposed refactor
 async fn output_slot_is_available(output: &Path) -> Result<bool, String> {
-    for (index, path) in [
-        output.to_path_buf(),
-        partial_path(output),
-        resume_sidecar_path(output),
-    ]
-    .into_iter()
-    .enumerate()
-    {
-        match tokio::fs::symlink_metadata(&path).await {
-            Ok(_) if index == 2 => match tokio::fs::read(&path).await {
-                Ok(bytes) if serde_json::from_slice::<ResumeSidecar>(&bytes).is_err() => continue,
-                Ok(_) => return Ok(false),
-                Err(error) => {
-                    return Err(format!(
-                        "read Usenet resume metadata {}: {error}",
-                        path.display()
-                    ))
-                }
-            },
+    for path in [output.to_path_buf(), partial_path(output)] {
+        match tokio::fs::symlink_metadata(&path).await {
             Ok(_) => return Ok(false),
             Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
             Err(error) => return Err(format!("inspect Usenet output {}: {error}", path.display())),
         }
     }
+    // A malformed sidecar carries no resumable state, so the slot stays free.
+    let sidecar = resume_sidecar_path(output);
+    match tokio::fs::read(&sidecar).await {
+        Ok(bytes) => Ok(serde_json::from_slice::<ResumeSidecar>(&bytes).is_err()),
+        Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(true),
+        Err(_) => Ok(false),
+    }
-    Ok(true)
 }
🤖 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/risuko-engine/src/engine/usenet_worker.rs` around lines 955 - 981,
Update output_slot_is_available to bind and handle resume_sidecar_path(output)
explicitly instead of selecting sidecar behavior through enumerate and index ==
2. Preserve invalid JSON as available, valid sidecar content as occupied, and
treat sidecar directories or broken symlinks as occupied rather than returning
an error; continue reporting other genuine read failures.
🤖 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/risuko-engine/src/engine/http.rs`:
- Around line 500-524: Add a separate body-read deadline in fetch_for_nzb around
the response.bytes_stream() loop, independent of header_timeout, so stalled or
excessively slow streams terminate instead of waiting indefinitely. Preserve the
existing chunk error handling, CAP enforcement, and successful byte collection
while mapping body-timeout failures to a clear NZB response-body timeout error.

In `@src-tauri/risuko-engine/src/engine/manager.rs`:
- Around line 209-225: Update usenet_stage_rank and its callers so unrecognised
stage names fail loudly rather than mapping to rank 0 and being silently
rejected; prefer reusing a shared enum for Usenet stage values, or at minimum
emit a warning from the fallback arm while preserving should_update_usenet_stage
ordering behavior.

In `@src-tauri/risuko-engine/src/engine/usenet_par2.rs`:
- Around line 328-348: Update the slice-reading loop around file.read in the
damaged-slice checker to accumulate reads until the entire buffer is filled or
EOF is reached, preserving the slice boundary for subsequent iterations. Hash
the accumulated bytes with the existing zero-padding behavior only when EOF
produces a partial final slice, and mark remaining slices damaged when EOF
occurs before the expected data is available.
- Around line 1954-1992: Replace the timing-based 128 MiB fixture in
cancellation_during_par2_verification_is_observed_between_hash_chunks with a
small file and a pre-cancelled CancellationToken before calling
verify_with_cancel. Remove the spawned canceller, elapsed-time assertion, and
related synchronization, while retaining the assertion that verify_with_cancel
returns Par2Error::Cancelled. If preserving between-chunk coverage, add a
separate deterministic small-file test using a few buffer lengths.

In `@src-tauri/risuko-engine/src/engine/usenet_pipeline.rs`:
- Around line 1412-1414: Update the concurrency assertions in the test
bounds_fetches_when_manifest_counts_are_too_small to require max_active to be
greater than one while retaining the existing upper-bound check against
FETCH_CONCURRENCY, ensuring the test detects serialized fetch execution.
- Around line 19-22: The fixed FETCH_CONCURRENCY of 4 prevents the pipeline from
using the provider’s configured connection capacity. Derive fetch concurrency
from the effective budget exposed by ProviderConnectionCapacityRegistry and
per-profile max_connections, while retaining YencAssemblyLimits as the separate
bound for decoded payload memory.

In `@src/shared/locales/de/task.ts`:
- Around line 145-146: Update the German translation for the
"usenet-repair-partials-retained" locale key to replace the unnatural “Teilweise
Dateien” wording with an idiomatic phrase such as “Unvollständige Dateien” or
“Teilweise heruntergeladene Dateien”, while preserving the rest of the message.

In `@src/shared/locales/ru/task.ts`:
- Around line 140-141: Update the “usenet-repair-insufficient” Russian
translation so the required count appears directly before “блоков” and the
wording clearly contrasts the required count with the available block count,
while preserving the existing interpolation keys.

In `@src/shared/locales/tr/task.ts`:
- Around line 142-145: Update the translations for
"usenet-repair-partials-retained" and "usenet-repair-partials-unavailable" to
replace the unnatural provider-completeness wording with idiomatic Turkish using
“tamamlanma oranı daha yüksek” in both retry messages.

In `@src/shared/locales/uk/task.ts`:
- Around line 140-141: Update the “usenet-repair-insufficient” Ukrainian
translation so the neededBlocks placeholder appears before “блоків” and the
sentence includes “але” for clearer block-count wording, while preserving both
placeholders and the existing meaning.

In `@src/shared/locales/vi/task.ts`:
- Around line 143-144: Update the translation value for
"usenet-repair-partials-unavailable" so it states that partial files are
unavailable, not that no partial files exist, while preserving the existing
Vietnamese guidance about trying another NZB or provider.

---

Outside diff comments:
In `@src-tauri/risuko-engine/src/engine/usenet_worker.rs`:
- Around line 955-981: Update output_slot_is_available to bind and handle
resume_sidecar_path(output) explicitly instead of selecting sidecar behavior
through enumerate and index == 2. Preserve invalid JSON as available, valid
sidecar content as occupied, and treat sidecar directories or broken symlinks as
occupied rather than returning an error; continue reporting other genuine read
failures.
🪄 Autofix

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: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 588b8a4f-b812-453d-b8f5-e2ef73b080e6

📥 Commits

Reviewing files that changed from the base of the PR and between a29c9fb and 3ec6109.

📒 Files selected for processing (30)
  • src-tauri/risuko-engine/src/engine/http.rs
  • src-tauri/risuko-engine/src/engine/manager.rs
  • src-tauri/risuko-engine/src/engine/usenet_par2.rs
  • src-tauri/risuko-engine/src/engine/usenet_pipeline.rs
  • src-tauri/risuko-engine/src/engine/usenet_worker.rs
  • src-tauri/src/commands/usenet_cmds.rs
  • src/shared/locales/ar/task.ts
  • src/shared/locales/bg/task.ts
  • src/shared/locales/ca/task.ts
  • src/shared/locales/de/task.ts
  • src/shared/locales/el/task.ts
  • src/shared/locales/es/task.ts
  • src/shared/locales/fa/task.ts
  • src/shared/locales/fr/task.ts
  • src/shared/locales/hu/task.ts
  • src/shared/locales/id/task.ts
  • src/shared/locales/it/task.ts
  • src/shared/locales/ja/task.ts
  • src/shared/locales/ko/task.ts
  • src/shared/locales/nb/task.ts
  • src/shared/locales/nl/task.ts
  • src/shared/locales/pl/task.ts
  • src/shared/locales/pt-BR/task.ts
  • src/shared/locales/ro/task.ts
  • src/shared/locales/ru/task.ts
  • src/shared/locales/th/task.ts
  • src/shared/locales/tr/task.ts
  • src/shared/locales/uk/task.ts
  • src/shared/locales/vi/task.ts
  • src/shared/locales/zh-CN/task.ts

Comment thread src-tauri/risuko-engine/src/engine/http.rs
Comment thread src-tauri/risuko-engine/src/engine/manager.rs
Comment thread src-tauri/risuko-engine/src/engine/usenet_par2.rs
Comment thread src-tauri/risuko-engine/src/engine/usenet_par2.rs
Comment thread src-tauri/risuko-engine/src/engine/usenet_pipeline.rs Outdated
Comment thread src/shared/locales/de/task.ts Outdated
Comment thread src/shared/locales/ru/task.ts Outdated
Comment thread src/shared/locales/tr/task.ts Outdated
Comment thread src/shared/locales/uk/task.ts Outdated
Comment thread src/shared/locales/vi/task.ts Outdated

@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 4 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src-tauri/src/commands/usenet_cmds.rs
Comment thread src-tauri/risuko-engine/src/engine/http.rs Outdated
Comment thread src-tauri/risuko-engine/src/engine/manager.rs Outdated

@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 12 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src-tauri/risuko-engine/src/engine/usenet_pipeline.rs Outdated
Comment thread src-tauri/risuko-engine/src/engine/usenet_worker.rs
Comment thread src-tauri/risuko-engine/src/engine/usenet_pipeline.rs Outdated
Comment thread src-tauri/risuko-engine/src/engine/http.rs Outdated
Comment thread src-tauri/risuko-engine/src/engine/usenet_par2.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 5 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src-tauri/risuko-engine/src/engine/http.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.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src-tauri/risuko-engine/src/engine/http.rs">

<violation number="1" location="src-tauri/risuko-engine/src/engine/http.rs:518">
P3: The new `nzb-body-timeout` option read here is not registered in `src/shared/configKeys.ts` (nor its sync category / locales / Advanced.vue), so it can never be set from the settings UI and always falls back to the 30s default. If this timeout was meant to be user-tunable (the previous key `lowest-speed-limit-timeout` was exposed in `low-speed` sync category), add `nzb-body-timeout` to the config keys and its UI entry; otherwise the decoupled option is effectively hardcoded.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

let mut bytes = Vec::with_capacity(response.content_length().unwrap_or(0).min(CAP) as usize);
let mut stream = response.bytes_stream();
let body_timeout = parse_duration_secs_option(
options.get("nzb-body-timeout"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The new nzb-body-timeout option read here is not registered in src/shared/configKeys.ts (nor its sync category / locales / Advanced.vue), so it can never be set from the settings UI and always falls back to the 30s default. If this timeout was meant to be user-tunable (the previous key lowest-speed-limit-timeout was exposed in low-speed sync category), add nzb-body-timeout to the config keys and its UI entry; otherwise the decoupled option is effectively hardcoded.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src-tauri/risuko-engine/src/engine/http.rs, line 518:

<comment>The new `nzb-body-timeout` option read here is not registered in `src/shared/configKeys.ts` (nor its sync category / locales / Advanced.vue), so it can never be set from the settings UI and always falls back to the 30s default. If this timeout was meant to be user-tunable (the previous key `lowest-speed-limit-timeout` was exposed in `low-speed` sync category), add `nzb-body-timeout` to the config keys and its UI entry; otherwise the decoupled option is effectively hardcoded.</comment>

<file context>
@@ -515,7 +515,7 @@ pub async fn fetch_for_nzb(uri: &str, options: &Map<String, Value>) -> Result<Ve
     let mut stream = response.bytes_stream();
     let body_timeout = parse_duration_secs_option(
-        options.get("lowest-speed-limit-timeout"),
+        options.get("nzb-body-timeout"),
         DEFAULT_NZB_BODY_TIMEOUT_SECS,
     );
</file context>

@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 6 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src/shared/syncCategories.ts Outdated
@YueMiyuki
YueMiyuki merged commit 417f50a into master Aug 8, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

next The "next" steps

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FR]: usenet support

1 participant