Add initial Android build and development support - #83
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
There was a problem hiding this comment.
2 issues found across 35 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="scripts/android.mjs">
<violation number="1" location="scripts/android.mjs:99">
P2: Keystore path written to Java properties file without normalizing separators, which will corrupt paths on Windows due to backslash escape handling in `java.util.Properties`.</violation>
<violation number="2" location="scripts/android.mjs:167">
P2: Signing block injection relies on hardcoded `\n\tbuildTypes {` / `\n buildTypes {` patterns and silently falls back to a warning if neither matches. Different indentation, CRLF, or template changes will skip the release signing config insertion and leave CI builds unsigned.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| if (content.includes("\n\tbuildTypes {")) { | ||
| content = content.replace("\n\tbuildTypes {", `${signingBlock}\n\tbuildTypes {`); | ||
| changed = true; | ||
| } else if (content.includes("\n buildTypes {")) { |
There was a problem hiding this comment.
P2: Signing block injection relies on hardcoded \n\tbuildTypes { / \n buildTypes { patterns and silently falls back to a warning if neither matches. Different indentation, CRLF, or template changes will skip the release signing config insertion and leave CI builds unsigned.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/android.mjs, line 167:
<comment>Signing block injection relies on hardcoded `\n\tbuildTypes {` / `\n buildTypes {` patterns and silently falls back to a warning if neither matches. Different indentation, CRLF, or template changes will skip the release signing config insertion and leave CI builds unsigned.</comment>
<file context>
@@ -0,0 +1,240 @@
+ if (content.includes("\n\tbuildTypes {")) {
+ content = content.replace("\n\tbuildTypes {", `${signingBlock}\n\tbuildTypes {`);
+ changed = true;
+ } else if (content.includes("\n buildTypes {")) {
+ content = content.replace("\n buildTypes {", `${signingBlock}\n buildTypes {`);
+ changed = true;
</file context>
| `keyAlias=${keyAlias}`, | ||
| `keyPassword=${keyPassword}`, | ||
| `storePassword=${storePassword}`, | ||
| `storeFile=${ciKeystorePath}`, |
There was a problem hiding this comment.
P2: Keystore path written to Java properties file without normalizing separators, which will corrupt paths on Windows due to backslash escape handling in java.util.Properties.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/android.mjs, line 99:
<comment>Keystore path written to Java properties file without normalizing separators, which will corrupt paths on Windows due to backslash escape handling in `java.util.Properties`.</comment>
<file context>
@@ -0,0 +1,240 @@
+ `keyAlias=${keyAlias}`,
+ `keyPassword=${keyPassword}`,
+ `storePassword=${storePassword}`,
+ `storeFile=${ciKeystorePath}`,
+ "",
+ ].join("\n"),
</file context>
| `storeFile=${ciKeystorePath}`, | |
| + `storeFile=${ciKeystorePath.replace(/\\/g, '/')}`, |
|
Rebased to next-dev:latest |
There was a problem hiding this comment.
3 issues found across 191 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/config_cmds.rs">
<violation number="1" location="src-tauri/src/commands/config_cmds.rs:200">
P2: Android `save_preference` silently persists `open-at-login` while `get_app_config` hardcodes it to `false`, creating inconsistent config round-trips.</violation>
</file>
<file name="src-tauri/src/commands/health_cmds.rs">
<violation number="1" location="src-tauri/src/commands/health_cmds.rs:242">
P2: The `tools` health check category is silently excluded on Android rather than returning a `Skipped`/`unsupported` check, breaking consistency with how other desktop-only features (autostart, sleep-inhibit) are gracefully handled in `check_system`.</violation>
</file>
<file name="src-tauri/risuko-engine/src/config/defaults.rs">
<violation number="1" location="src-tauri/risuko-engine/src/config/defaults.rs:67">
P2: Android default download path selection uses `parent.exists()` instead of verifying write access, and hardcodes the application package ID in the fallback path. On Android 10+ with scoped storage, `/storage/emulated/0/Download` always exists but may not be writable without `MANAGE_EXTERNAL_STORAGE` being granted by the user, causing silent download failures on first run. The fallback path hardcodes `app.risuko.mobile`, creating a fragile coupling with `build.gradle.kts`.</violation>
</file>
Note: This PR contains a large number of files. cubic only reviews up to 100 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.
On a pro plan you can use ultrareview for larger PRs.
Re-trigger cubic
| } | ||
| } | ||
|
|
||
| fn is_open_at_login_enabled(handle: &AppHandle) -> Result<bool, String> { |
There was a problem hiding this comment.
P2: Android save_preference silently persists open-at-login while get_app_config hardcodes it to false, creating inconsistent config round-trips.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src-tauri/src/commands/config_cmds.rs, line 200:
<comment>Android `save_preference` silently persists `open-at-login` while `get_app_config` hardcodes it to `false`, creating inconsistent config round-trips.</comment>
<file context>
@@ -183,13 +180,33 @@ pub fn prepare_preference_patch(params: Value) -> Result<Value, String> {
+ }
+}
+
+fn is_open_at_login_enabled(handle: &AppHandle) -> Result<bool, String> {
+ #[cfg(target_os = "android")]
+ {
</file context>
| cats.push(HealthCategory::from_checks("logs", check_logs(&log_dir))); | ||
| } | ||
| if want("tools") { | ||
| if want("tools") && !cfg!(target_os = "android") { |
There was a problem hiding this comment.
P2: The tools health check category is silently excluded on Android rather than returning a Skipped/unsupported check, breaking consistency with how other desktop-only features (autostart, sleep-inhibit) are gracefully handled in check_system.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src-tauri/src/commands/health_cmds.rs, line 242:
<comment>The `tools` health check category is silently excluded on Android rather than returning a `Skipped`/`unsupported` check, breaking consistency with how other desktop-only features (autostart, sleep-inhibit) are gracefully handled in `check_system`.</comment>
<file context>
@@ -237,7 +239,7 @@ pub async fn run_health_checks(
cats.push(HealthCategory::from_checks("logs", check_logs(&log_dir)));
}
- if want("tools") {
+ if want("tools") && !cfg!(target_os = "android") {
cats.push(HealthCategory::from_checks("tools", check_tools().await));
}
</file context>
| // file manager. The app needs storage permission to write there | ||
| // on Android 10+; if missing, the user can pick another folder | ||
| // via the directory picker. | ||
| let public_downloads = std::path::PathBuf::from("/storage/emulated/0/Download/Risuko"); |
There was a problem hiding this comment.
P2: Android default download path selection uses parent.exists() instead of verifying write access, and hardcodes the application package ID in the fallback path. On Android 10+ with scoped storage, /storage/emulated/0/Download always exists but may not be writable without MANAGE_EXTERNAL_STORAGE being granted by the user, causing silent download failures on first run. The fallback path hardcodes app.risuko.mobile, creating a fragile coupling with build.gradle.kts.
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/config/defaults.rs, line 67:
<comment>Android default download path selection uses `parent.exists()` instead of verifying write access, and hardcodes the application package ID in the fallback path. On Android 10+ with scoped storage, `/storage/emulated/0/Download` always exists but may not be writable without `MANAGE_EXTERNAL_STORAGE` being granted by the user, causing silent download failures on first run. The fallback path hardcodes `app.risuko.mobile`, creating a fragile coupling with `build.gradle.kts`.</comment>
<file context>
@@ -53,6 +54,44 @@ pub fn system_defaults() -> Map<String, Value> {
+ // file manager. The app needs storage permission to write there
+ // on Android 10+; if missing, the user can pick another folder
+ // via the directory picker.
+ let public_downloads = std::path::PathBuf::from("/storage/emulated/0/Download/Risuko");
+ if let Some(parent) = public_downloads.parent() {
+ if parent.exists() {
</file context>
There was a problem hiding this comment.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src-tauri/Cargo.toml (1)
60-60:⚠️ Potential issue | 🟠 Major | ⚡ Quick winMove
open = "5"under the desktop-only target and cfg-gatecapture_user_agenttoo.
open = "5"is currently global insrc-tauri/Cargo.toml(line 60).- Most
open::that(...)callsites are already compiled out on Android (tray.rs,menu.rs, and the#[cfg(not(target_os = "android"))]branch offile_cmds.rs).- But
open::that(&url)is used in#[tauri::command] capture_user_agentinsrc-tauri/src/commands/cookie_cmds.rswith no Androidcfg, andcommands::cookie_cmds::capture_user_agentis registered unconditionally insrc-tauri/src/lib.rs, so moving the dependency alone would break Android builds (or keep Android exposed to an unlisted platform crate).Suggested patch
-open = "5" @@ [target.'cfg(not(target_os = "android"))'.dependencies] +open = "5" trash = "5" tauri-plugin-nosleep = { git = "https://github.com/pevers/tauri-plugin-nosleep", rev = "6f6ab76ec171d075476b585282290fcf2e1c40b5" } tauri-plugin-autostart = "2" tauri-plugin-single-instance = "2"🤖 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/Cargo.toml` at line 60, The Cargo.toml entry open = "5" is currently global and must be moved under a desktop-only target (e.g. [target.'cfg(not(target_os = "android"))'.dependencies]) and the capture_user_agent command must be cfg-gated so Android builds don't reference open. Relocate the open dependency into the desktop-only target section in Cargo.toml, add #[cfg(not(target_os = "android"))] to the capture_user_agent function in commands::cookie_cmds::capture_user_agent (and any use/imports of open there), and also gate its registration in src-tauri/src/lib.rs so commands::cookie_cmds::capture_user_agent is only registered on non-Android targets.src-tauri/risuko-cookies/src/lib.rs (1)
358-448:⚠️ Potential issue | 🟠 Major | ⚡ Quick winTests will fail to compile on Android.
Three tests call functions that are conditionally compiled out on Android:
registrable_domain()andcookie_domain_matches_host(). This will cause compilation failures when runningcargo teston the Android target, blocking test execution and potentially breaking CI.🔧 Proposed fix to gate Android-incompatible tests
#[test] + #[cfg(not(target_os = "android"))] fn registrable_strips_one_label() { assert_eq!( registrable_domain("a.b.example.com"), Some("example.com".into()) ); assert_eq!( registrable_domain("example.com"), Some("example.com".into()) ); assert_eq!(registrable_domain("localhost"), None); } #[test] + #[cfg(not(target_os = "android"))] fn registrable_widens_to_three_labels_for_known_multi_part_tlds() { assert_eq!( registrable_domain("foo.example.co.uk"), Some("example.co.uk".into()) ); assert_eq!( registrable_domain("a.b.bbc.co.uk"), Some("bbc.co.uk".into()) ); assert_eq!( registrable_domain("shop.example.com.au"), Some("example.com.au".into()) ); // 2-label inputs cannot widen further; return as-is assert_eq!(registrable_domain("co.uk"), Some("co.uk".into())); } #[test] + #[cfg(not(target_os = "android"))] fn cookie_domain_host_match_rules() { // Exact match assert!(cookie_domain_matches_host("example.com", "example.com")); // Leading dot is treated like host-only (legacy compatibility) assert!(cookie_domain_matches_host(".example.com", "dl.example.com")); // Subdomain match assert!(cookie_domain_matches_host("example.com", "dl.example.com")); // Suffix-but-not-dot-bounded must NOT match assert!(!cookie_domain_matches_host("example.com", "notexample.com")); // Unrelated domain assert!(!cookie_domain_matches_host("attacker.com", "example.com")); // Public-suffix-style cookie should not match unrelated subdomains assert!(!cookie_domain_matches_host(".co.uk", "example.com")); // But still legitimately matches when host is on that domain assert!(cookie_domain_matches_host(".co.uk", "foo.co.uk")); }🤖 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-cookies/src/lib.rs` around lines 358 - 448, Gate the Android-incompatible tests so they don't compile on Android: add a cfg attribute like #[cfg(not(target_os = "android"))] to the tests that call the conditionally-compiled functions (the test functions registrable_strips_one_label, registrable_widens_to_three_labels_for_known_multi_part_tlds, and cookie_domain_host_match_rules) or wrap them in a sub-module annotated with #[cfg(not(target_os = "android"))]; this prevents compilation on Android where registrable_domain and cookie_domain_matches_host are absent while leaving other tests (e.g. extract_host and cookies_to_header_format) unaffected.
🤖 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 @.github/workflows/release.yml:
- Line 21: The workflow uses mutable action tags (e.g., actions/checkout@v4,
actions/setup-node@v4, android-actions/setup-android@v3,
dtolnay/rust-toolchain@stable, softprops/action-gh-release@v2,
actions/upload-artifact@v4, tauri-apps/tauri-action@v0 and others referenced in
the file) which must be pinned to immutable commit SHAs; update every uses:
entry (including the ones called out in the review) to the corresponding full
commit SHA for that action, replacing tags like actions/checkout@v4 with
actions/checkout@<commit-sha> and do the same for actions/setup-node,
android-actions/setup-android, dtolnay/rust-toolchain,
softprops/action-gh-release, actions/upload-artifact, tauri-apps/tauri-action,
etc., ensuring each uses: line in the workflow is updated to a specific commit
SHA and then run the workflow to verify no breakages.
- Around line 20-22: Update both actions/checkout@v4 steps named "Check out Git
repository" used in the android-release job and in the release matrix to
explicitly disable persisted credentials: add a with block containing
persist-credentials: false so checkout does not leave GITHUB_TOKEN credentials
available to later steps; locate the steps that use actions/checkout@v4 and
insert the with: persist-credentials: false setting for each occurrence.
In @.gitignore:
- Around line 31-32: Replace the blanket unignore of src-tauri/gen/android/**
with targeted unignore rules: keep the existing negation for
src-tauri/gen/android/ but remove or restrict src-tauri/gen/android/** and
instead explicitly unignore only the specific source/config files needed (e.g.,
gradle wrapper, settings, and manifest files) and re-add ignore patterns for
transient artifacts like **/build/** and **/.gradle/** so generated build
outputs are not committed; update the .gitignore entries around
src-tauri/gen/android/, src-tauri/gen/android/**, **/build/** and .gradle/**
accordingly.
In `@scripts/android-env.mjs`:
- Line 4: The PATH is being constructed using a hardcoded ":" which fails on
Windows; update scripts/android-env.mjs to use path.delimiter instead of ":"
when joining PATH entries—import the delimiter (or the whole path module)
alongside join (e.g., include delimiter from "node:path") and replace the ":"
usage in the PATH construction (where the PATH variable is assembled) with
path.delimiter so it works cross-platform.
In `@src-tauri/src/lib.rs`:
- Around line 90-98: The code currently only calls
std::fs::create_dir_all(&candidate) and treats that as proof the override is
writable; instead, after create_dir_all(&candidate) succeeds, attempt to create
(and immediately remove) a small temp file inside candidate (e.g.
candidate.join(".writetest-<pid>-<uniq>")) using
OpenOptions::new().create(true).write(true).open(...), handle errors by logging
the error (use the same eprintln format) and return
default_log_dir.to_path_buf(); on success remove the temp file and return
candidate; keep references to candidate and default_log_dir so the change is
localized and ensures an existing but non-writable directory will fall back to
the default.
In `@src/renderer/components/Native/ShowInFolder.vue`:
- Around line 2-4: The icon-only button in ShowInFolder.vue (the <button
class="show-in-folder" `@click.stop`="onFolderClick"> wrapping the FolderOpen
component) lacks an accessible name; add one by providing an explicit label
(e.g., aria-label="Show in folder" or a localized string) on the button or
include offscreen/visually-hidden text inside the button so screen readers
announce the action; ensure the label is kept in sync with any i18n and does not
affect the existing `@click.stop`="onFolderClick" behavior.
In `@src/renderer/components/Preference/Advanced.vue`:
- Around line 1113-1116: The mo-show-in-folder button is bound to logPath
causing it to open the saved folder even when the textarea is showing an unsaved
override (visibleLogPath); change the binding so the reveal action uses the
currently visible value. Update the template to pass visibleLogPath (fallback to
logPath if empty) into <mo-show-in-folder> and ensure any handler or prop
consumers (e.g., the component receiving :path) use that same visible value so
the folder reveal matches what the user sees; adjust references around
handleLogDirSelected / visibleLogPath to keep behavior consistent.
In `@src/renderer/components/Task/Index.vue`:
- Around line 76-83: The Select emits string values so before persisting call
setTasksPerPage convert the incoming value to a number (e.g. Number(value) or
parseInt(value, 10)) in onTasksPerPageChange (the handler bound to
`@update`:model-value) and anywhere setTasksPerPage is used (referenced around the
setTasksPerPage function) to ensure tasksPerPage is stored as a numeric type
instead of a string; update the model-value binding initialization
`${tasksPerPage}` if necessary so that the component receives/works with numbers
consistently.
In `@src/renderer/components/Task/TaskItemActions.vue`:
- Around line 3-20: The icon-only action buttons rendered in TaskItemActions.vue
(the <button> iterating over taskActions and invoking onActionClick) lack
accessible names; add an accessible label by binding an aria-label (and
optionally title) that maps each action value to a human-readable string (e.g.,
map 'PAUSE'→'Pause task', 'STOP'→'Stop task', 'RESUME'→'Resume task',
'RESTART'→'Restart task', 'DELETE'→'Delete permanently', 'TRASH'→'Move to
trash', 'FOLDER'→'Open folder', 'LINK'→'Open link', 'INFO'→'Show info') and use
:aria-label="getActionLabel(action)" on the <button> (implement a getActionLabel
helper or computed that returns the labels) so screen readers can announce each
button while preserving the existing onActionClick behavior.
In `@src/renderer/store/task.ts`:
- Around line 474-481: The current filter only checks base gids which lets stale
compound keys like "gid#f3" remain in selectedGidList; instead build a Set of
actual current row keys from orderedData (use task.key or task.rowKey if
present, falling back to task.gid) and filter this.selectedGidList by exact
membership in that Set so keys with suffixes are validated correctly; after
that, ensure any derived selection state (e.g., selectedGids) is
recomputed/updated from the cleaned selectedGidList.
In `@src/renderer/styles/android.css`:
- Line 500: Fix Stylelint violations: change the color property value from
"currentColor" to the lint-expected casing (e.g., "currentcolor"), reformat any
calc(...) expressions to keep operators on the same line with proper spacing
(e.g., "calc(100% - 10px)" instead of breaking the operator to a new line), and
insert the required empty line(s) before declaration blocks per the
declaration-empty-line-before rule. Locate occurrences by searching for the
literal "color: currentColor", any "calc(" usage, and the declaration blocks
around the ranges flagged (near the calc and color occurrences mentioned) and
apply the three fixes consistently across those blocks.
In `@src/renderer/styles/components/preferences.css`:
- Around line 297-314: The .dev-log-path-input rule uses scrollbar-width: thin
(Firefox only), so add WebKit scrollbar pseudo-element rules for cross‑browser
parity: append ::-webkit-scrollbar, ::-webkit-scrollbar-track,
::-webkit-scrollbar-thumb and ::-webkit-scrollbar-thumb:hover selectors for
.dev-log-path-input to set scrollbar height (e.g. 6px), transparent track, thumb
color using var(--border) and hover color using var(--muted-foreground), and a
small border-radius (e.g. 3px); place these rules immediately after the
.dev-log-path-input block so Chromium/Safari/Edge render a thin styled scrollbar
like Firefox.
In `@src/renderer/utils/native.ts`:
- Around line 17-24: The dirname function fails for Windows paths because it
uses value.lastIndexOf("/") only; update dirname (function name: dirname) to
consider backslashes as well—either normalize backslashes to forward slashes
before computing the index or compute the last separator index with
Math.max(value.lastIndexOf("/"), value.lastIndexOf("\\")). Then use that index
to slice the directory portion and keep the existing trimming of trailing
separators.
---
Outside diff comments:
In `@src-tauri/Cargo.toml`:
- Line 60: The Cargo.toml entry open = "5" is currently global and must be moved
under a desktop-only target (e.g. [target.'cfg(not(target_os =
"android"))'.dependencies]) and the capture_user_agent command must be cfg-gated
so Android builds don't reference open. Relocate the open dependency into the
desktop-only target section in Cargo.toml, add #[cfg(not(target_os =
"android"))] to the capture_user_agent function in
commands::cookie_cmds::capture_user_agent (and any use/imports of open there),
and also gate its registration in src-tauri/src/lib.rs so
commands::cookie_cmds::capture_user_agent is only registered on non-Android
targets.
In `@src-tauri/risuko-cookies/src/lib.rs`:
- Around line 358-448: Gate the Android-incompatible tests so they don't compile
on Android: add a cfg attribute like #[cfg(not(target_os = "android"))] to the
tests that call the conditionally-compiled functions (the test functions
registrable_strips_one_label,
registrable_widens_to_three_labels_for_known_multi_part_tlds, and
cookie_domain_host_match_rules) or wrap them in a sub-module annotated with
#[cfg(not(target_os = "android"))]; this prevents compilation on Android where
registrable_domain and cookie_domain_matches_host are absent while leaving other
tests (e.g. extract_host and cookies_to_header_format) unaffected.
🪄 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: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: a00e5d9e-4189-4cd0-ba97-13ce5d5e43c9
⛔ Files ignored due to path filters (104)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlsrc-tauri/Cargo.lockis excluded by!**/*.locksrc-tauri/gen/android/.editorconfigis excluded by!**/gen/**src-tauri/gen/android/.gitignoreis excluded by!**/gen/**src-tauri/gen/android/app/.gitignoreis excluded by!**/gen/**src-tauri/gen/android/app/build.gradle.ktsis excluded by!**/gen/**src-tauri/gen/android/app/proguard-rules.prois excluded by!**/gen/**src-tauri/gen/android/app/proguard-tauri.prois excluded by!**/gen/**src-tauri/gen/android/app/src/main/AndroidManifest.xmlis excluded by!**/gen/**src-tauri/gen/android/app/src/main/java/app/risuko/mobile/MainActivity.ktis excluded by!**/gen/**src-tauri/gen/android/app/src/main/java/app/risuko/mobile/RisukoForegroundService.ktis excluded by!**/gen/**src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xmlis excluded by!**/gen/**src-tauri/gen/android/app/src/main/res/drawable/empty_splash_icon.xmlis excluded by!**/gen/**src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xmlis excluded by!**/gen/**src-tauri/gen/android/app/src/main/res/drawable/ic_notification.xmlis excluded by!**/gen/**src-tauri/gen/android/app/src/main/res/layout/activity_main.xmlis excluded by!**/gen/**src-tauri/gen/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xmlis excluded by!**/gen/**src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.pngis excluded by!**/*.png,!**/gen/**src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.pngis excluded by!**/*.png,!**/gen/**src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.pngis excluded by!**/*.png,!**/gen/**src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.pngis excluded by!**/*.png,!**/gen/**src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.pngis excluded by!**/*.png,!**/gen/**src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.pngis excluded by!**/*.png,!**/gen/**src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.pngis excluded by!**/*.png,!**/gen/**src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.pngis excluded by!**/*.png,!**/gen/**src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.pngis excluded by!**/*.png,!**/gen/**src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.pngis excluded by!**/*.png,!**/gen/**src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.pngis excluded by!**/*.png,!**/gen/**src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngis excluded by!**/*.png,!**/gen/**src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.pngis excluded by!**/*.png,!**/gen/**src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.pngis excluded by!**/*.png,!**/gen/**src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngis excluded by!**/*.png,!**/gen/**src-tauri/gen/android/app/src/main/res/values-night/themes.xmlis excluded by!**/gen/**src-tauri/gen/android/app/src/main/res/values-v31/themes.xmlis excluded by!**/gen/**src-tauri/gen/android/app/src/main/res/values/colors.xmlis excluded by!**/gen/**src-tauri/gen/android/app/src/main/res/values/ic_launcher_background.xmlis excluded by!**/gen/**src-tauri/gen/android/app/src/main/res/values/strings.xmlis excluded by!**/gen/**src-tauri/gen/android/app/src/main/res/values/themes.xmlis excluded by!**/gen/**src-tauri/gen/android/app/src/main/res/xml/file_paths.xmlis excluded by!**/gen/**src-tauri/gen/android/app/tauri.build.gradle.ktsis excluded by!**/gen/**src-tauri/gen/android/app/tauri.propertiesis excluded by!**/gen/**src-tauri/gen/android/build.gradle.ktsis excluded by!**/gen/**src-tauri/gen/android/buildSrc/build.gradle.ktsis excluded by!**/gen/**src-tauri/gen/android/buildSrc/src/main/java/app/risuko/mobile/kotlin/BuildTask.ktis excluded by!**/gen/**src-tauri/gen/android/buildSrc/src/main/java/app/risuko/mobile/kotlin/RustPlugin.ktis excluded by!**/gen/**src-tauri/gen/android/gradle.propertiesis excluded by!**/gen/**src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jaris excluded by!**/*.jar,!**/gen/**src-tauri/gen/android/gradle/wrapper/gradle-wrapper.propertiesis excluded by!**/gen/**src-tauri/gen/android/gradlewis excluded by!**/gen/**src-tauri/gen/android/gradlew.batis excluded by!**/gen/**src-tauri/gen/android/settings.gradleis excluded by!**/gen/**src-tauri/gen/android/tauri.settings.gradleis excluded by!**/gen/**src-tauri/icons/128x128.pngis excluded by!**/*.pngsrc-tauri/icons/128x128@2x.pngis excluded by!**/*.pngsrc-tauri/icons/32x32.pngis excluded by!**/*.pngsrc-tauri/icons/64x64.pngis excluded by!**/*.pngsrc-tauri/icons/Square107x107Logo.pngis excluded by!**/*.pngsrc-tauri/icons/Square142x142Logo.pngis excluded by!**/*.pngsrc-tauri/icons/Square150x150Logo.pngis excluded by!**/*.pngsrc-tauri/icons/Square284x284Logo.pngis excluded by!**/*.pngsrc-tauri/icons/Square30x30Logo.pngis excluded by!**/*.pngsrc-tauri/icons/Square310x310Logo.pngis excluded by!**/*.pngsrc-tauri/icons/Square44x44Logo.pngis excluded by!**/*.pngsrc-tauri/icons/Square71x71Logo.pngis excluded by!**/*.pngsrc-tauri/icons/Square89x89Logo.pngis excluded by!**/*.pngsrc-tauri/icons/StoreLogo.pngis excluded by!**/*.pngsrc-tauri/icons/android/mipmap-hdpi/ic_launcher.pngis excluded by!**/*.pngsrc-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc-tauri/icons/android/mipmap-hdpi/ic_launcher_round.pngis excluded by!**/*.pngsrc-tauri/icons/android/mipmap-mdpi/ic_launcher.pngis excluded by!**/*.pngsrc-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc-tauri/icons/android/mipmap-mdpi/ic_launcher_round.pngis excluded by!**/*.pngsrc-tauri/icons/android/mipmap-xhdpi/ic_launcher.pngis excluded by!**/*.pngsrc-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.pngis excluded by!**/*.pngsrc-tauri/icons/android/mipmap-xxhdpi/ic_launcher.pngis excluded by!**/*.pngsrc-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.pngis excluded by!**/*.pngsrc-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.pngis excluded by!**/*.pngsrc-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.pngis excluded by!**/*.pngsrc-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.pngis excluded by!**/*.pngsrc-tauri/icons/icon.icois excluded by!**/*.icosrc-tauri/icons/icon.pngis excluded by!**/*.pngsrc-tauri/icons/icon.svgis excluded by!**/*.svgsrc-tauri/icons/ios/AppIcon-20x20@1x.pngis excluded by!**/*.pngsrc-tauri/icons/ios/AppIcon-20x20@2x-1.pngis excluded by!**/*.pngsrc-tauri/icons/ios/AppIcon-20x20@2x.pngis excluded by!**/*.pngsrc-tauri/icons/ios/AppIcon-20x20@3x.pngis excluded by!**/*.pngsrc-tauri/icons/ios/AppIcon-29x29@1x.pngis excluded by!**/*.pngsrc-tauri/icons/ios/AppIcon-29x29@2x-1.pngis excluded by!**/*.pngsrc-tauri/icons/ios/AppIcon-29x29@2x.pngis excluded by!**/*.pngsrc-tauri/icons/ios/AppIcon-29x29@3x.pngis excluded by!**/*.pngsrc-tauri/icons/ios/AppIcon-40x40@1x.pngis excluded by!**/*.pngsrc-tauri/icons/ios/AppIcon-40x40@2x-1.pngis excluded by!**/*.pngsrc-tauri/icons/ios/AppIcon-40x40@2x.pngis excluded by!**/*.pngsrc-tauri/icons/ios/AppIcon-40x40@3x.pngis excluded by!**/*.pngsrc-tauri/icons/ios/AppIcon-512@2x.pngis excluded by!**/*.pngsrc-tauri/icons/ios/AppIcon-60x60@2x.pngis excluded by!**/*.pngsrc-tauri/icons/ios/AppIcon-60x60@3x.pngis excluded by!**/*.pngsrc-tauri/icons/ios/AppIcon-76x76@1x.pngis excluded by!**/*.pngsrc-tauri/icons/ios/AppIcon-76x76@2x.pngis excluded by!**/*.pngsrc-tauri/icons/ios/AppIcon-83.5x83.5@2x.pngis excluded by!**/*.pngsrc/renderer/assets/logo.svgis excluded by!**/*.svgstatic/logo.svgis excluded by!**/*.svg
📒 Files selected for processing (87)
.github/workflows/release.yml.gitignorepackage.jsonpackages/risuko-app/package.jsonpackages/risuko-cli/npm/darwin-arm64/package.jsonpackages/risuko-cli/npm/darwin-x64/package.jsonpackages/risuko-cli/npm/linux-arm64-gnu/package.jsonpackages/risuko-cli/npm/linux-x64-gnu/package.jsonpackages/risuko-cli/npm/win32-arm64-msvc/package.jsonpackages/risuko-cli/npm/win32-x64-msvc/package.jsonpackages/risuko-cli/package.jsonpackages/risuko-js/npm/darwin-arm64/package.jsonpackages/risuko-js/npm/darwin-x64/package.jsonpackages/risuko-js/npm/linux-arm64-gnu/package.jsonpackages/risuko-js/npm/linux-x64-gnu/package.jsonpackages/risuko-js/npm/win32-arm64-msvc/package.jsonpackages/risuko-js/npm/win32-x64-msvc/package.jsonpackages/risuko-js/package.jsonpnpm-workspace.yamlscripts/android-env.mjsscripts/sign-android-apks.mjssrc-tauri/Cargo.tomlsrc-tauri/capabilities/default.jsonsrc-tauri/capabilities/desktop.jsonsrc-tauri/icons/android/values/ic_launcher_background.xmlsrc-tauri/icons/icon.icnssrc-tauri/risuko-cookies/Cargo.tomlsrc-tauri/risuko-cookies/src/lib.rssrc-tauri/risuko-engine/src/config/defaults.rssrc-tauri/risuko-engine/src/config/mod.rssrc-tauri/risuko-engine/src/engine/http.rssrc-tauri/src/commands/android_intent.rssrc-tauri/src/commands/app_cmds.rssrc-tauri/src/commands/config_cmds.rssrc-tauri/src/commands/event_cmds.rssrc-tauri/src/commands/file_cmds.rssrc-tauri/src/commands/health_cmds.rssrc-tauri/src/commands/mod.rssrc-tauri/src/lib.rssrc-tauri/src/managers/menu.rssrc-tauri/src/managers/mod.rssrc-tauri/src/managers/tray.rssrc-tauri/tauri.android.conf.jsonsrc/renderer/api/Api.tssrc/renderer/components/DragSelect/Index.vuesrc/renderer/components/Health/Index.vuesrc/renderer/components/Main.vuesrc/renderer/components/Native/SelectDirectory.vuesrc/renderer/components/Native/ShowInFolder.vuesrc/renderer/components/Preference/Advanced.vuesrc/renderer/components/Rss/Index.vuesrc/renderer/components/Subnav/SubnavSwitcher.vuesrc/renderer/components/Task/AddTask.vuesrc/renderer/components/Task/Index.vuesrc/renderer/components/Task/TaskActions.vuesrc/renderer/components/Task/TaskItem.vuesrc/renderer/components/Task/TaskItemActions.vuesrc/renderer/components/Task/TaskList.vuesrc/renderer/components/TaskDetail/Index.vuesrc/renderer/components/TaskDetail/TaskGeneral.vuesrc/renderer/components/ui/confirm-dialog/ConfirmDialog.vuesrc/renderer/pages/index/App.vuesrc/renderer/pages/index/main.tssrc/renderer/router/index.tssrc/renderer/shims/platform.tssrc/renderer/store/app.tssrc/renderer/store/preference.tssrc/renderer/store/task.tssrc/renderer/styles/android.csssrc/renderer/styles/app.csssrc/renderer/styles/components/input.csssrc/renderer/styles/components/preferences.csssrc/renderer/styles/components/task-detail.csssrc/renderer/styles/components/task.csssrc/renderer/utils/native.tssrc/shared/configKeys.tssrc/shared/locales/en-US/app.tssrc/shared/locales/en-US/preferences.tssrc/shared/locales/en-US/task.tssrc/shared/locales/index.tssrc/shared/locales/zh-CN/app.tssrc/shared/locales/zh-CN/preferences.tssrc/shared/locales/zh-CN/task.tssrc/shared/locales/zh-TW/app.tssrc/shared/locales/zh-TW/preferences.tssrc/shared/locales/zh-TW/task.tssrc/shared/types/config.ts
| - name: Check out Git repository | ||
| uses: actions/checkout@v4 | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the relevant workflow sections around the referenced lines
FILE=".github/workflows/release.yml"
echo "== File: $FILE =="
nl -ba "$FILE" | sed -n '1,120p'
echo
echo "== Snippet around lines 15-40 =="
nl -ba "$FILE" | sed -n '15,45p'
echo
echo "== Find all checkout uses in this workflow =="
rg -n "uses:\s*actions/checkout@v" "$FILE" -n || true
echo
echo "== For each checkout, show following lines to inspect 'with:' config =="
# Print checkout blocks (checkout line + next 12 lines)
python3 - <<'PY'
import re, itertools, pathlib
path = pathlib.Path(".github/workflows/release.yml")
lines = path.read_text().splitlines()
for i,l in enumerate(lines):
if re.search(r"uses:\s*actions/checkout@v\d+", l):
start = max(0, i-2)
end = min(len(lines), i+14)
print(f"\n--- checkout occurrence at line {i+1} ---")
for j in range(start, end):
ln = j+1
print(f"{ln:4d} {lines[j]}")
PYRepository: YueMiyuki/Risuko
Length of output: 163
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/workflows/release.yml"
echo "== File: $FILE (first 140 lines) =="
# Show with line numbers using awk
awk '{printf "%4d %s\n", NR, $0}' "$FILE" | sed -n '1,140p'
echo
echo "== Snippet around lines 15-45 (requested area) =="
awk '{if (NR>=15 && NR<=45) printf "%4d %s\n", NR, $0}' "$FILE"
echo
echo "== Find all checkout uses in this workflow =="
rg -n "uses:\s*actions/checkout@v" "$FILE" || true
echo
echo "== For each checkout occurrence, print nearby lines (uses line + 15 lines) =="
python3 - <<'PY'
import re, pathlib
p=pathlib.Path(".github/workflows/release.yml")
lines=p.read_text().splitlines()
for i,l in enumerate(lines):
if re.search(r"uses:\s*actions/checkout@v\d+", l):
print(f"\n--- checkout occurrence at line {i+1} ---")
for j in range(i, min(len(lines), i+16)):
print(f"{j+1:4d} {lines[j]}")
PYRepository: YueMiyuki/Risuko
Length of output: 7222
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/workflows/release.yml"
echo "== Snippet around line 130-190 =="
awk '{if (NR>=130 && NR<=200) printf "%4d %s\n", NR, $0}' "$FILE"
echo
echo "== Extract all 'uses: actions/checkout@v4' blocks including their immediate 'with:' sections (up to 15 lines) =="
python3 - <<'PY'
import re, pathlib
p=pathlib.Path(".github/workflows/release.yml")
lines=p.read_text().splitlines()
for i,l in enumerate(lines):
if re.search(r"uses:\s*actions/checkout@v4\s*$", l):
print(f"\n--- checkout occurrence at line {i+1} ---")
# print until next step header or 20 lines
for j in range(i, min(len(lines), i+22)):
print(f"{j+1:4d} {lines[j]}")
if j>i and re.search(r"^\s*-\s+name:\s+", lines[j]):
break
PYRepository: YueMiyuki/Risuko
Length of output: 3594
🌐 Web query:
actions/checkout@v4 persist-credentials default value
💡 Result:
For actions/checkout@v4, the input persist-credentials defaults to true (i.e., if you omit with: persist-credentials, credentials are configured/persisted in the local git config). [1][2] If you want to avoid persisting credentials, set: uses: actions/checkout@v4 with: persist-credentials: false [3][4]
Citations:
- 1: https://github.com/actions/checkout/blob/v4/action.yml
- 2: https://github.com/actions/checkout/blob/v4.1.1/action.yml
- 3: https://github.com/actions/checkout
- 4: https://github.com/marketplace/actions/checkout
Disable persisted checkout credentials in the actions/checkout@v4 steps (android-release and release matrix).
Both actions/checkout@v4 usages omit with: persist-credentials: false, so credentials are persisted by default and may be exposed to later steps.
Suggested patch
- name: Check out Git repository
uses: actions/checkout@v4
+ with:
+ persist-credentials: false - name: Check out Git repository
uses: actions/checkout@v4
+ with:
+ persist-credentials: false📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - name: Check out Git repository | |
| uses: actions/checkout@v4 | |
| - name: Check out Git repository | |
| uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: false |
🧰 Tools
🪛 zizmor (1.25.2)
[warning] 20-21: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 21-21: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 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 @.github/workflows/release.yml around lines 20 - 22, Update both
actions/checkout@v4 steps named "Check out Git repository" used in the
android-release job and in the release matrix to explicitly disable persisted
credentials: add a with block containing persist-credentials: false so checkout
does not leave GITHUB_TOKEN credentials available to later steps; locate the
steps that use actions/checkout@v4 and insert the with: persist-credentials:
false setting for each occurrence.
| runs-on: ubuntu-22.04 | ||
| steps: | ||
| - name: Check out Git repository | ||
| uses: actions/checkout@v4 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/workflows/release.yml"
echo "== File = $FILE =="
if [ ! -f "$FILE" ]; then
echo "Missing: $FILE"
exit 1
fi
echo "== Line-numbered excerpt around the originally cited lines =="
nl -ba "$FILE" | sed -n '1,220p'
echo "== All uses: lines (with line numbers) =="
nl -ba "$FILE" | rg -n '^\s*uses:\s*' -n || true
echo "== Check whether any uses: entries use @<commit-sha> (40/64 hex) =="
# Extract uses lines and report if they match a likely SHA (40 hex or 64 hex)
nl -ba "$FILE" | rg '^\s*uses:\s*' | sed -E 's/^\s*[0-9]+\s+uses:\s*//' | while read -r uses; do
if echo "$uses" | rg -q '@([0-9a-f]{40}|[0-9a-f]{64})$'; then
echo "PINNED_SHA $uses"
else
echo "NOT_PINNED $uses"
fi
doneRepository: YueMiyuki/Risuko
Length of output: 227
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/workflows/release.yml"
echo "== File = $FILE =="
if [ ! -f "$FILE" ]; then
echo "Missing: $FILE"
exit 1
fi
echo "== Line-numbered excerpt (1-220) =="
python3 - <<'PY'
path = ".github/workflows/release.yml"
with open(path, "r", encoding="utf-8") as f:
for i, line in enumerate(f, start=1):
if i > 220: break
print(f"{i:5d} {line.rstrip()}")
PY
echo "== All uses: entries (line-numbered) =="
python3 - <<'PY'
import re
path = ".github/workflows/release.yml"
pat = re.compile(r'^\s*uses:\s*(.+)\s*$')
with open(path, "r", encoding="utf-8") as f:
for i, line in enumerate(f, start=1):
m = pat.match(line)
if m:
print(f"{i:5d} {m.group(1)}")
PY
echo "== Check whether uses: use a commit SHA suffix =="
python3 - <<'PY'
import re
path = ".github/workflows/release.yml"
pat = re.compile(r'^\s*uses:\s*(.+)\s*$')
sha_pat = re.compile(r'@([0-9a-f]{40}|[0-9a-f]{64})$')
with open(path, "r", encoding="utf-8") as f:
for i, line in enumerate(f, start=1):
m = pat.match(line)
if not m:
continue
uses = m.group(1)
kind = "PINNED_SHA" if sha_pat.search(uses) else "NOT_PINNED"
print(f"{kind:11s} line {i}: {uses}")
PYRepository: YueMiyuki/Risuko
Length of output: 10984
Pin third-party GitHub Actions to commit SHAs.
.github/workflows/release.yml uses mutable action tags (e.g., actions/checkout@v4, actions/setup-node@v4, android-actions/setup-android@v3, dtolnay/rust-toolchain@stable, softprops/action-gh-release@v2, actions/upload-artifact@v4, tauri-apps/tauri-action@v0), which weakens CI supply-chain guarantees—pin every uses: to a full commit SHA.
Applies to uses: lines: 21, 24, 29, 35, 41, 53, 104, 113, 152, 155, 160, 166, 196, 277, 286, 317, 326, 367, 376.
🧰 Tools
🪛 zizmor (1.25.2)
[warning] 20-21: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 21-21: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 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 @.github/workflows/release.yml at line 21, The workflow uses mutable action
tags (e.g., actions/checkout@v4, actions/setup-node@v4,
android-actions/setup-android@v3, dtolnay/rust-toolchain@stable,
softprops/action-gh-release@v2, actions/upload-artifact@v4,
tauri-apps/tauri-action@v0 and others referenced in the file) which must be
pinned to immutable commit SHAs; update every uses: entry (including the ones
called out in the review) to the corresponding full commit SHA for that action,
replacing tags like actions/checkout@v4 with actions/checkout@<commit-sha> and
do the same for actions/setup-node, android-actions/setup-android,
dtolnay/rust-toolchain, softprops/action-gh-release, actions/upload-artifact,
tauri-apps/tauri-action, etc., ensuring each uses: line in the workflow is
updated to a specific commit SHA and then run the workflow to verify no
breakages.
| !src-tauri/gen/android/ | ||
| !src-tauri/gen/android/** |
There was a problem hiding this comment.
Avoid unignoring Android build artifacts wholesale.
Line 32 re-includes everything under src-tauri/gen/android/**, including generated build outputs. This is likely to bloat commits and leak transient artifacts. Re-allow only source/config files and keep **/build/** and .gradle/** ignored.
🤖 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 @.gitignore around lines 31 - 32, Replace the blanket unignore of
src-tauri/gen/android/** with targeted unignore rules: keep the existing
negation for src-tauri/gen/android/ but remove or restrict
src-tauri/gen/android/** and instead explicitly unignore only the specific
source/config files needed (e.g., gradle wrapper, settings, and manifest files)
and re-add ignore patterns for transient artifacts like **/build/** and
**/.gradle/** so generated build outputs are not committed; update the
.gitignore entries around src-tauri/gen/android/, src-tauri/gen/android/**,
**/build/** and .gradle/** accordingly.
| #!/usr/bin/env node | ||
| import { spawn } from "node:child_process"; | ||
| import { existsSync } from "node:fs"; | ||
| import { join } from "node:path"; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="scripts/android-env.mjs"
echo "== File: $FILE =="
# Show imports near the top (including line 4)
nl -ba "$FILE" | sed -n '1,30p'
echo
echo "== Around line 55 =="
nl -ba "$FILE" | sed -n '45,70p'
echo
echo "== Search for hardcoded PATH separator usage =="
rg -n --hidden --no-ignore -S "PATH:|process\.env\.PATH|:'\s*\$\{|:\$|:\$\{|\:\$|:\"" "$FILE" || true
echo
echo "== Search for node:path delimiter usage =="
rg -n --hidden --no-ignore -S "path\.delimiter|delimiter" "$FILE" || trueRepository: YueMiyuki/Risuko
Length of output: 157
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="scripts/android-env.mjs"
echo "== File: $FILE =="
awk '{printf "%6d %s\n", NR, $0}' "$FILE" | sed -n '1,30p'
echo
echo "== Around line 55 =="
awk '{printf "%6d %s\n", NR, $0}' "$FILE" | sed -n '45,70p'
echo
echo "== Search for PATH construction and hardcoded ':' =="
# Print lines mentioning PATH config plus any ':' literals nearby
rg -n --hidden --no-ignore -S "PATH" "$FILE" || true
rg -n --hidden --no-ignore -S "PATH:.*:" "$FILE" || true
rg -n --hidden --no-ignore -S "process\.env\.PATH|javaBin|llvmBin|node:path" "$FILE" || true
rg -n --hidden --no-ignore -S ":\$\{|\:\$|:\`|'\:'|\"\:\"" "$FILE" || true
echo
echo "== Search for path.delimiter usage =="
rg -n --hidden --no-ignore -S "path\.delimiter|delimiter" "$FILE" || true
echo
echo "== Show import from node:path =="
rg -n --hidden --no-ignore -S "from \"node:path\"|from 'node:path'|node:path" "$FILE" || trueRepository: YueMiyuki/Risuko
Length of output: 4806
Use path.delimiter for PATH construction.
PATH is built with a hardcoded : on line 55, which breaks Windows where ; is required. Replace it with path.delimiter.
Suggested patch
-import { join } from "node:path";
+import { delimiter, join } from "node:path";
@@
- PATH: `${javaBin ? `${javaBin}:` : ""}${llvmBin}:${process.env.PATH || ""}`,
+ PATH: `${javaBin ? `${javaBin}${delimiter}` : ""}${llvmBin}${delimiter}${process.env.PATH || ""}`,🤖 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 `@scripts/android-env.mjs` at line 4, The PATH is being constructed using a
hardcoded ":" which fails on Windows; update scripts/android-env.mjs to use
path.delimiter instead of ":" when joining PATH entries—import the delimiter (or
the whole path module) alongside join (e.g., include delimiter from "node:path")
and replace the ":" usage in the PATH construction (where the PATH variable is
assembled) with path.delimiter so it works cross-platform.
| if let Err(e) = std::fs::create_dir_all(&candidate) { | ||
| eprintln!( | ||
| "log-dir-override '{}' is not writable ({}). Falling back to default.", | ||
| candidate.display(), | ||
| e | ||
| ); | ||
| return default_log_dir.to_path_buf(); | ||
| } | ||
| candidate |
There was a problem hiding this comment.
Validate actual log-file writability before accepting override.
Line 90 only checks directory creation. An existing but non-writable directory still passes, so logs can be silently dropped instead of falling back to default as documented.
Suggested fix
@@
if let Err(e) = std::fs::create_dir_all(&candidate) {
eprintln!(
"log-dir-override '{}' is not writable ({}). Falling back to default.",
candidate.display(),
e
);
return default_log_dir.to_path_buf();
}
+ // Probe actual file writability; create_dir_all alone is not sufficient.
+ let probe = candidate.join(format!(".risuko-log-write-test-{}", std::process::id()));
+ if let Err(e) = std::fs::OpenOptions::new()
+ .create(true)
+ .append(true)
+ .open(&probe)
+ {
+ eprintln!(
+ "log-dir-override '{}' is not writable ({}). Falling back to default.",
+ candidate.display(),
+ e
+ );
+ return default_log_dir.to_path_buf();
+ }
+ let _ = std::fs::remove_file(&probe);
candidate
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if let Err(e) = std::fs::create_dir_all(&candidate) { | |
| eprintln!( | |
| "log-dir-override '{}' is not writable ({}). Falling back to default.", | |
| candidate.display(), | |
| e | |
| ); | |
| return default_log_dir.to_path_buf(); | |
| } | |
| candidate | |
| if let Err(e) = std::fs::create_dir_all(&candidate) { | |
| eprintln!( | |
| "log-dir-override '{}' is not writable ({}). Falling back to default.", | |
| candidate.display(), | |
| e | |
| ); | |
| return default_log_dir.to_path_buf(); | |
| } | |
| // Probe actual file writability; create_dir_all alone is not sufficient. | |
| let probe = candidate.join(format!(".risuko-log-write-test-{}", std::process::id())); | |
| if let Err(e) = std::fs::OpenOptions::new() | |
| .create(true) | |
| .append(true) | |
| .open(&probe) | |
| { | |
| eprintln!( | |
| "log-dir-override '{}' is not writable ({}). Falling back to default.", | |
| candidate.display(), | |
| e | |
| ); | |
| return default_log_dir.to_path_buf(); | |
| } | |
| let _ = std::fs::remove_file(&probe); | |
| candidate |
🤖 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/lib.rs` around lines 90 - 98, The code currently only calls
std::fs::create_dir_all(&candidate) and treats that as proof the override is
writable; instead, after create_dir_all(&candidate) succeeds, attempt to create
(and immediately remove) a small temp file inside candidate (e.g.
candidate.join(".writetest-<pid>-<uniq>")) using
OpenOptions::new().create(true).write(true).open(...), handle errors by logging
the error (use the same eprintln format) and return
default_log_dir.to_path_buf(); on success remove the temp file and return
candidate; keep references to candidate and default_log_dir so the change is
localized and ensures an existing but non-writable directory will fall back to
the default.
| <button | ||
| type="button" | ||
| v-for="(action, index) in taskActions" | ||
| :key="action" | ||
| class="task-item-action" | ||
| :style="{ '--stagger-index': index }" | ||
| @click.stop="onActionClick(action, $event)" | ||
| > | ||
| <i v-if="action === 'PAUSE'"> | ||
| <Pause :size="14" /> | ||
| </i> | ||
| <i v-if="action === 'STOP'"> | ||
| <Square :size="14" /> | ||
| </i> | ||
| <i v-if="action === 'RESUME'"> | ||
| <Play :size="14" /> | ||
| </i> | ||
| <i v-if="action === 'RESTART'"> | ||
| <RotateCcw :size="14" /> | ||
| </i> | ||
| <i v-if="action === 'DELETE'"> | ||
| <Trash2 :size="14" /> | ||
| </i> | ||
| <i v-if="action === 'TRASH'"> | ||
| <Trash :size="14" /> | ||
| </i> | ||
| <i v-if="action === 'FOLDER'"> | ||
| <Folder :size="14" /> | ||
| </i> | ||
| <i v-if="action === 'LINK'"> | ||
| <Link :size="14" /> | ||
| </i> | ||
| <i v-if="action === 'INFO'"> | ||
| <Info :size="14" /> | ||
| </i> | ||
| </li> | ||
| </ul> | ||
| <Pause v-if="action === 'PAUSE'" :size="14" /> | ||
| <Square v-else-if="action === 'STOP'" :size="14" /> | ||
| <Play v-else-if="action === 'RESUME'" :size="14" /> | ||
| <RotateCcw v-else-if="action === 'RESTART'" :size="14" /> | ||
| <Trash2 v-else-if="action === 'DELETE'" :size="14" /> | ||
| <Trash v-else-if="action === 'TRASH'" :size="14" /> | ||
| <Folder v-else-if="action === 'FOLDER'" :size="14" /> | ||
| <Link v-else-if="action === 'LINK'" :size="14" /> | ||
| <Info v-else-if="action === 'INFO'" :size="14" /> | ||
| </button> |
There was a problem hiding this comment.
Add accessible labels to icon-only action buttons.
Buttons in Line 3–20 render only icons, so screen readers have no actionable name.
Suggested fix
<button
type="button"
v-for="(action, index) in taskActions"
:key="action"
class="task-item-action"
+ :aria-label="getActionLabel(action)"
+ :title="getActionLabel(action)"
:style="{ '--stagger-index': index }"
`@click.stop`="onActionClick(action, $event)"
> methods: {
+ getActionLabel(action: string) {
+ const map = {
+ PAUSE: this.$t("task.pause-task"),
+ STOP: this.$t("task.bt-stop-seeding"),
+ RESUME: this.$t("task.resume-task"),
+ RESTART: this.$t("task.restart-task"),
+ DELETE: this.$t("task.delete-task"),
+ TRASH: this.$t("task.remove-record"),
+ FOLDER: this.$t("task.show-in-folder"),
+ LINK: this.$t("task.copy-task-link"),
+ INFO: this.$t("task.task-detail-title"),
+ };
+ return map[action] || action;
+ },
onActionClick(action, event) {🤖 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/renderer/components/Task/TaskItemActions.vue` around lines 3 - 20, The
icon-only action buttons rendered in TaskItemActions.vue (the <button> iterating
over taskActions and invoking onActionClick) lack accessible names; add an
accessible label by binding an aria-label (and optionally title) that maps each
action value to a human-readable string (e.g., map 'PAUSE'→'Pause task',
'STOP'→'Stop task', 'RESUME'→'Resume task', 'RESTART'→'Restart task',
'DELETE'→'Delete permanently', 'TRASH'→'Move to trash', 'FOLDER'→'Open folder',
'LINK'→'Open link', 'INFO'→'Show info') and use
:aria-label="getActionLabel(action)" on the <button> (implement a getActionLabel
helper or computed that returns the labels) so screen readers can announce each
button while preserving the existing onActionClick behavior.
| // Keep selected rows only when their underlying gid still exists | ||
| // Row keys can include `#f<index>`, so strip that before checking | ||
| const gids = new Set(orderedData.map((task) => task.gid)); | ||
| this.selectedGidList = this.selectedGidList.filter((key) => { | ||
| const hashIdx = key.indexOf("#"); | ||
| const gid = hashIdx === -1 ? key : key.slice(0, hashIdx); | ||
| return gids.has(gid); | ||
| }); |
There was a problem hiding this comment.
Reconcile selection by row keys, not only base gid.
On Line 474–481, filtering by underlying gid keeps stale file-row selections (e.g., gid#f3) after that row disappears. Downstream, batch actions still operate on selectedGids, so users can trigger actions on items that appear unselected.
Proposed fix
- const gids = new Set(orderedData.map((task) => task.gid));
- this.selectedGidList = this.selectedGidList.filter((key) => {
- const hashIdx = key.indexOf("#");
- const gid = hashIdx === -1 ? key : key.slice(0, hashIdx);
- return gids.has(gid);
- });
+ const rowKeys = new Set(
+ this.displayTaskList.map((task: DisplayTask) => task._displayKey || task.gid),
+ );
+ this.selectedGidList = this.selectedGidList.filter((key) => rowKeys.has(key));🤖 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/renderer/store/task.ts` around lines 474 - 481, The current filter only
checks base gids which lets stale compound keys like "gid#f3" remain in
selectedGidList; instead build a Set of actual current row keys from orderedData
(use task.key or task.rowKey if present, falling back to task.gid) and filter
this.selectedGidList by exact membership in that Set so keys with suffixes are
validated correctly; after that, ensure any derived selection state (e.g.,
selectedGids) is recomputed/updated from the cleaned selectedGidList.
| html.platform-android .subnav-badge, | ||
| html.mobile-phone .subnav-badge { | ||
| background: rgba(103, 80, 164, 0.14); | ||
| color: currentColor; |
There was a problem hiding this comment.
Resolve Stylelint violations in this stylesheet before merge.
This file currently includes lint errors (operator newline formatting in calc(...), empty-line-before declarations, and currentColor keyword casing). These are likely CI-blocking if stylelint is enforced.
Example cleanup pattern
- color: currentColor;
+ color: currentcolor;
- max-height: calc(
- 100dvh -
- var(--android-bottom-bar) -
- env(safe-area-inset-top) -
- env(safe-area-inset-bottom) -
- 40px
- ) !important;
+ max-height: calc(100dvh - var(--android-bottom-bar) - env(safe-area-inset-top) - env(safe-area-inset-bottom) - 40px) !important;Also applies to: 724-724, 810-829, 889-890, 1049-1055
🧰 Tools
🪛 Stylelint (17.12.0)
[error] 500-500: Expected "currentColor" to be "currentcolor" (value-keyword-case)
(value-keyword-case)
🤖 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/renderer/styles/android.css` at line 500, Fix Stylelint violations:
change the color property value from "currentColor" to the lint-expected casing
(e.g., "currentcolor"), reformat any calc(...) expressions to keep operators on
the same line with proper spacing (e.g., "calc(100% - 10px)" instead of breaking
the operator to a new line), and insert the required empty line(s) before
declaration blocks per the declaration-empty-line-before rule. Locate
occurrences by searching for the literal "color: currentColor", any "calc("
usage, and the declaration blocks around the ranges flagged (near the calc and
color occurrences mentioned) and apply the three fixes consistently across those
blocks.
| .dev-log-path-input { | ||
| flex: 1 1 0; | ||
| min-width: 0; | ||
| height: 36px; | ||
| padding: 8px 10px; | ||
| border: 0; | ||
| outline: 0; | ||
| resize: none; | ||
| overflow-x: auto; | ||
| overflow-y: hidden; | ||
| white-space: pre; | ||
| background: transparent; | ||
| color: var(--foreground); | ||
| font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, monospace; | ||
| font-size: 12px; | ||
| line-height: 18px; | ||
| scrollbar-width: thin; | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | ⚡ Quick win
Consider cross-browser scrollbar styling.
The scrollbar-width: thin property at line 313 only works in Firefox. For consistent scrollbar styling across Webkit browsers (Chrome, Safari, Edge), you'll need to add ::-webkit-scrollbar pseudo-element rules.
🎨 Proposed addition for Webkit scrollbar support
Add this rule after the .dev-log-path-input block:
.dev-log-path-input::-webkit-scrollbar {
height: 6px;
}
.dev-log-path-input::-webkit-scrollbar-track {
background: transparent;
}
.dev-log-path-input::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 3px;
}
.dev-log-path-input::-webkit-scrollbar-thumb:hover {
background: var(--muted-foreground);
}🤖 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/renderer/styles/components/preferences.css` around lines 297 - 314, The
.dev-log-path-input rule uses scrollbar-width: thin (Firefox only), so add
WebKit scrollbar pseudo-element rules for cross‑browser parity: append
::-webkit-scrollbar, ::-webkit-scrollbar-track, ::-webkit-scrollbar-thumb and
::-webkit-scrollbar-thumb:hover selectors for .dev-log-path-input to set
scrollbar height (e.g. 6px), transparent track, thumb color using var(--border)
and hover color using var(--muted-foreground), and a small border-radius (e.g.
3px); place these rules immediately after the .dev-log-path-input block so
Chromium/Safari/Edge render a thin styled scrollbar like Firefox.
| function dirname(path = ""): string { | ||
| const value = `${path || ""}`.replace(/[/\\]+$/g, ""); | ||
| const index = value.lastIndexOf("/"); | ||
| if (index <= 0) { | ||
| return value; | ||
| } | ||
| return value.slice(0, index); | ||
| } |
There was a problem hiding this comment.
Fix dirname for Windows path separators.
On Line 19, lastIndexOf("/") misses backslash-only paths, so reveal directory calculation can fail on Windows-style paths.
Proposed fix
function dirname(path = ""): string {
const value = `${path || ""}`.replace(/[/\\]+$/g, "");
- const index = value.lastIndexOf("/");
- if (index <= 0) {
+ const normalized = value.replace(/\\/g, "/");
+ const index = normalized.lastIndexOf("/");
+ if (index < 0) {
return value;
}
+ if (index === 0) {
+ return "/";
+ }
return value.slice(0, index);
}🤖 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/renderer/utils/native.ts` around lines 17 - 24, The dirname function
fails for Windows paths because it uses value.lastIndexOf("/") only; update
dirname (function name: dirname) to consider backslashes as well—either
normalize backslashes to forward slashes before computing the index or compute
the last separator index with Math.max(value.lastIndexOf("/"),
value.lastIndexOf("\\")). Then use that index to slice the directory portion and
keep the existing trimming of trailing separators.
This pull request introduces initial Android build and development support as an opt-in feature, ensuring that desktop development remains unaffected. It adds documentation, scripts, and CI/CD integration for Android, and refactors capability and dependency management to isolate desktop-only features from Android builds.
Android Support:
android:init,dev:android,build:android,run:android) inpackage.jsonand a dedicatedscripts/android.mjsto automate Android project setup, build, signing, and icon sync. [1] [2]docs/ANDROID.md,docs/ANDROID-CN.md), and updatedREADME.md,README-CN.md,CONTRIBUTING.md, andCONTRIBUTING-CN.mdto clarify that Android is an optional development path. [1] [2] [3] [4] [5] [6]release-androidjob to the GitHub Actions workflow for automated Android artifact builds, signing, and release/upload for tagged and manual builds.scripts/build.mjsto prevent accidental Android builds using the desktop build command, directing users to the new Android workflow.Desktop/Android Isolation:
src-tauri/Cargo.tomlto move desktop-only crates and plugins (open,trash,tauri-plugin-autostart,tauri-plugin-nosleep,tauri-plugin-single-instance) behind platform-specific targets, so they are excluded from Android builds. [1] [2] [3]default.jsonnow covers shared permissions, while a newdesktop.jsonisolates desktop-only features, ensuring Android builds do not request or include desktop-specific APIs. [1] [2] [3]Summary by CodeRabbit
New Features
Build & Release
Improvements