Skip to content

Workshop Update: Manual Mod Folder Dialog#1072

Open
Nightwalker743 wants to merge 5 commits intoutkarshdalal:masterfrom
Nightwalker743:workshop-update
Open

Workshop Update: Manual Mod Folder Dialog#1072
Nightwalker743 wants to merge 5 commits intoutkarshdalal:masterfrom
Nightwalker743:workshop-update

Conversation

@Nightwalker743
Copy link
Copy Markdown
Contributor

@Nightwalker743 Nightwalker743 commented Apr 2, 2026

Description

  • FolderPickerDialog for manually choosing mod installation paths when automatic detection fails
  • Improved mod path detection
  • ZIP extraction for single-archive workshop uploads
  • This also fixes a crash when a user goes to "Manage Workshop" before launching a newly-installed game for the first time.
  • This also fixes a bug where if you download mods before the container is created for the first time then when you go to launch, the mods will download once again since the previous mods were deleted.

Right now the logic for automatic mod folder symlink placement isn't perfect (and it never will be without full steam client), so this adds the ability to choose the mod folder yourself where mods will be symlinked to in case if automatic detection fails. This should help with workshop support compatibility quite a bit.

Recording

Screen_Recording_20260402_004755_GameNative.mp4

Checklist

  • If I have access to #code-changes, I have discussed this change there and it has been green-lighted. If I do not have access, I have still provided clear context in this PR. If I skip both, I accept that this change may face delays in review, may not be reviewed at all, or may be closed.
  • I have attached a recording of the change.
  • I have read and agree to the contribution guidelines in CONTRIBUTING.md.

Summary by cubic

Adds a full-screen Workshop manager with an in-app folder picker for manual mod placement, safer ISteamUGC routing to prevent duplicates, and enriched mods.json for better item details. Also extracts single-archive uploads and hardens container and folder‑picker behavior to avoid crashes.

  • New Features

    • Full-screen manager with hero image, search, per‑mod toggles, select/deselect all, and a selected-size summary.
    • Override Mod Folder with a built-in browser (Game Directory, C: Drive, My Documents/My Games, AppData variants). Shows a Windows‑style path, supports Clear, and saves per container as workshopModPath.
    • Manual path: creates symlinks in the chosen folder, cleans old workshop symlinks across game/AppData/Documents, protects the new target during cleanup, and always writes mods.json alongside.
    • ZIP single‑archive extraction with a .zip_extracted marker; plus CKM extraction, LZMA decompression, and magic‑byte extension fixing. Adds disk space checks before downloads.
    • Smarter detection and routing with caching: prefers ISteamUGC when the binary signals Workshop API usage and a HIGH‑confidence mod dir exists; forces ISteamUGC for Starbound. mods.json now includes description, tags, preview_url, and workshop_item_url. WorkshopItem gains description and tags.
  • Bug Fixes

    • Ensures the container exists before downloads, fixing a pre‑launch crash in Manage Workshop and preventing duplicate downloads on first launch.
    • Folder picker stability: fixes multi‑level breadcrumbs, guards directory listing with try/catch/finally, and rethrows CancellationException to avoid stuck states.
    • Cleans stale Phase 6 filesystem symlinks when the ISteamUGC path is preferred to prevent duplicate mods.

Written for commit 79fc8e6. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Manual per-game override for the workshop mod folder with a folder picker, Clear/Select actions and persisted choice; UI shows “Automatic” or the chosen path.
  • Improvements

    • Workshop items now include descriptions and tags.
    • Automatic ZIP mod extraction with safety checks and extracted markers.
    • Improved mod-directory detection heuristics and symlink handling to honor manual targets, reduce stale links, and persist overrides.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 2, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a per-game workshop mod-folder override with a folder-picker UI and persistence, enriches workshop item metadata, auto-extracts single-archive mods in-place, and updates symlink detection/creation and strategy caching with a new stdSeen signal.

Changes

Cohort / File(s) Summary
UI Components
app/src/main/java/app/gamenative/ui/component/dialog/WorkshopManagerDialog.kt
Adds workshopModPath, gameRootDir, winePrefix, onModPathChanged params; adds "Override Mod Folder" button, FolderPickerDialog (private), folder-browsing state, breadcrumb/navigation, and updated header/save layout.
Screen State Management
app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt
Adds persisted workshopModPath Compose state; reads from container extras on dialog open; passes override props into WorkshopManagerDialog; onModPathChanged updates state and writes back to container.
Domain Model
app/src/main/java/app/gamenative/workshop/WorkshopItem.kt
Adds description: String = "" and tags: String = "" fields to WorkshopItem.
Workshop Core Logic
app/src/main/java/app/gamenative/workshop/WorkshopManager.kt
Adds extractZipMods(workshopContentDir: File) to detect and safely extract single-archive mods; enriches mods.json metadata (description/tags, preview/workshop URLs); configureModSymlinks gains workshopModPath param to bypass auto-detection and create symlinks into manual target; adjusts stale-symlink cleanup, ensures container creation, and adds logging.
Detection Logic
app/src/main/java/app/gamenative/workshop/WorkshopModPathDetector.kt
DetectionResult adds stdSeen: Boolean; expands heuristic directory name sets; propagates stdSeen through fallback paths to influence strategy decisions.

Sequence Diagram

sequenceDiagram
    participant User
    participant UI as WorkshopManagerDialog
    participant Picker as FolderPickerDialog
    participant Screen as SteamAppScreen
    participant Container as Container Extras
    participant Manager as WorkshopManager

    User->>UI: Click "Override Mod Folder"
    UI->>Picker: Open (gameRootDir, winePrefix)
    Picker->>Picker: Build roots, list dirs, show breadcrumb
    User->>Picker: Navigate & Select folder
    Picker->>Screen: onModPathChanged(selectedPath)
    Screen->>Container: putExtra("workshopModPath", selectedPath) / saveData()
    Screen->>Manager: configureSymlinksForApp(..., workshopModPath=selectedPath)
    Manager->>Manager: Bypass auto-detection, create symlinks into override path
    Manager->>Manager: extractZipMods() may detect & extract ZIPs in-place
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • phobos665

Poem

🐰 I hopped through folders, picked a new nest,
Told the app where mods should quietly rest.
Zips unwrapped, links aligned in a row,
Metadata hums, and containers know.
Happy rabbit dances — onward we go!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly reflects the main feature added: a manual mod folder dialog for the Workshop manager, which is the primary user-facing addition in this changeset.
Description check ✅ Passed The pull request description is substantially complete with clear objectives, a recording, and all checklist items checked.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 5 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="app/src/main/java/app/gamenative/ui/component/dialog/WorkshopManagerDialog.kt">

<violation number="1" location="app/src/main/java/app/gamenative/ui/component/dialog/WorkshopManagerDialog.kt:597">
P2: Folder picker performs unbounded full directory listing/sort without protective error handling, risking stalls and unstable behavior on large/restricted folders.</violation>
</file>

<file name="app/src/main/java/app/gamenative/workshop/WorkshopManager.kt">

<violation number="1" location="app/src/main/java/app/gamenative/workshop/WorkshopManager.kt:552">
P1: ZIP extraction of external workshop content has no decompression limits, allowing zip-bomb style disk/IO exhaustion.</violation>

<violation number="2" location="app/src/main/java/app/gamenative/workshop/WorkshopManager.kt:3178">
P2: Stale symlink cleanup uses unresolved symlink target strings, so relative/normalized links to workshop content may be missed and left behind.</violation>

<violation number="3" location="app/src/main/java/app/gamenative/workshop/WorkshopManager.kt:3399">
P1: Container-creation failure is ignored, so download continues into a path that can later be orphan-cleaned and delete freshly downloaded workshop data.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
app/src/main/java/app/gamenative/workshop/WorkshopModPathDetector.kt (1)

46-61: ⚠️ Potential issue | 🟠 Major

These folder names are too generic to drive an install-path decision.

collectModsDirectories() and the binary path matcher both treat ALL_MOD_DIR_NAMES hits as actionable. With textures, sounds, audio, content, assets, and especially data in that set, ordinary asset references now become candidates; because resolveInstallPath() returns the first matching segment, a path like Data/Mods/... now resolves to installDir/Data instead of the actual Mods folder. Keep these names out of the fast path, or require a second corroborating signal before returning SymlinkIntoDir.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/workshop/WorkshopModPathDetector.kt` around
lines 46 - 61, The list ALL_MOD_DIR_NAMES contains overly generic entries (e.g.
"textures", "sounds", "audio", "content", "assets", "data") that are causing
false positives in collectModsDirectories() and resolveInstallPath() — update
the code to remove these generic names from the fast-path sets
(MEDIUM_CONFIDENCE_NAMES/LOW_CONFIDENCE_NAMES or at least exclude them from
ALL_MOD_DIR_NAMES) or change the decision logic in collectModsDirectories() /
resolveInstallPath() so that a match on a low-confidence name requires a second
corroborating signal (e.g., presence of a high-confidence sibling like
"Mods"/"workshop" or a binary path matcher hit) before returning SymlinkIntoDir;
locate symbols MEDIUM_CONFIDENCE_NAMES, LOW_CONFIDENCE_NAMES, ALL_MOD_DIR_NAMES,
collectModsDirectories, resolveInstallPath, and SymlinkIntoDir to implement the
change.
🧹 Nitpick comments (1)
app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt (1)

1246-1257: Avoid a second container-ID source of truth here.

The rest of SteamAppScreen already treats libraryItem.appId as the canonical key for this container. Reconstructing STEAM_$gameId just for the new workshop-path flow makes the override state depend on a separate ID format.

♻️ Suggested cleanup
-            val containerId = "STEAM_$gameId"
+            val containerId = libraryItem.appId

Also applies to: 1265-1268, 1310-1313

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt`
around lines 1246 - 1257, The code creates a local containerId ("STEAM_$gameId")
causing a second source of truth; replace uses of the reconstructed containerId
with the canonical libraryItem.appId to ensure the workshop-path override state
ties to the same key as the rest of SteamAppScreen. Update places that set
containerId and calls to ContainerUtils.getContainer(context, containerId) (used
when computing wsWinePrefix and any other workshop-path logic around
workshopModPath/wsGameRootDir) to use libraryItem.appId instead, removing the
duplicated "STEAM_$gameId" construction.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/main/java/app/gamenative/ui/component/dialog/WorkshopManagerDialog.kt`:
- Around line 563-570: The current userDir selection in WorkshopManagerDialog.kt
can pick an unstable first entry from usersDir.listFiles(); change the logic to
prefer the explicit "steamuser" directory under drive_c/users if it exists and
is a directory, and only if it does not exist fall back to scanning
usersDir.listFiles() for the first directory not named "Public" (or default to
File(usersDir, "steamuser") if none found); update the userDir assignment that
uses usersDir, winePrefix and userDir to implement this deterministic
preference.

In
`@app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt`:
- Around line 1309-1315: The Timber info log in the CoroutineScope block (inside
SteamAppScreen.kt where ContainerUtils.getContainer and
container.putExtra("workshopModPath", ...) are used) writes the full newPath
which may contain PII; change the log to not include the absolute path—log only
that the override was set or cleared (e.g., "workshop mod path override set" vs
"cleared"), or if you need a hint log a sanitized basename derived from newPath
(use the filename component only) instead of the full path in the
Timber.tag("Workshop").i(...) call.

In `@app/src/main/java/app/gamenative/workshop/WorkshopManager.kt`:
- Around line 512-525: The ZIP extraction gate currently requires a single
non-preview file and a permanent .zip_extracted marker; change it to detect
actual archives (look for any file whose name endsWith(".zip", ignoreCase=true)
or whose content matches ZIP magic) instead of requiring exactly one payload,
and move invocation so extraction runs after fixFileExtensions() within
runPostProcessing(); ensure File(itemDir, ".zip_extracted") is only created
after a successful extraction by extractZip(...) and treat the marker as stale
if the archive's lastModified is newer than the marker (or if extraction
previously failed) so updates/retries aren’t skipped (update logic around
File(itemDir, ".zip_extracted"), the filtering that builds contentFiles/zipFile,
and where runPostProcessing() calls fixFileExtensions()).
- Around line 2168-2202: The try/catch around symlink creation (working with
targetDir, modDirs, Files.createSymbolicLink) logs failures but leaves the
global/manual override flag in an incorrect state (willUseFilesystemMods),
causing the method to skip auto-sync; fix by capturing the current
willUseFilesystemMods value before the try, and in the catch restore that prior
value (or unset the manual override) in addition to logging via
Timber.tag(TAG).w; apply the same pattern to the other similar blocks referenced
(the catch blocks around the symlink steps at the other locations) so any
failure does not permanently force manual mode.

---

Outside diff comments:
In `@app/src/main/java/app/gamenative/workshop/WorkshopModPathDetector.kt`:
- Around line 46-61: The list ALL_MOD_DIR_NAMES contains overly generic entries
(e.g. "textures", "sounds", "audio", "content", "assets", "data") that are
causing false positives in collectModsDirectories() and resolveInstallPath() —
update the code to remove these generic names from the fast-path sets
(MEDIUM_CONFIDENCE_NAMES/LOW_CONFIDENCE_NAMES or at least exclude them from
ALL_MOD_DIR_NAMES) or change the decision logic in collectModsDirectories() /
resolveInstallPath() so that a match on a low-confidence name requires a second
corroborating signal (e.g., presence of a high-confidence sibling like
"Mods"/"workshop" or a binary path matcher hit) before returning SymlinkIntoDir;
locate symbols MEDIUM_CONFIDENCE_NAMES, LOW_CONFIDENCE_NAMES, ALL_MOD_DIR_NAMES,
collectModsDirectories, resolveInstallPath, and SymlinkIntoDir to implement the
change.

---

Nitpick comments:
In
`@app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt`:
- Around line 1246-1257: The code creates a local containerId ("STEAM_$gameId")
causing a second source of truth; replace uses of the reconstructed containerId
with the canonical libraryItem.appId to ensure the workshop-path override state
ties to the same key as the rest of SteamAppScreen. Update places that set
containerId and calls to ContainerUtils.getContainer(context, containerId) (used
when computing wsWinePrefix and any other workshop-path logic around
workshopModPath/wsGameRootDir) to use libraryItem.appId instead, removing the
duplicated "STEAM_$gameId" construction.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 99e79017-36dc-48d2-8ab6-dfedac631bf0

📥 Commits

Reviewing files that changed from the base of the PR and between aa412fc and 6deb3c3.

📒 Files selected for processing (5)
  • app/src/main/java/app/gamenative/ui/component/dialog/WorkshopManagerDialog.kt
  • app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt
  • app/src/main/java/app/gamenative/workshop/WorkshopItem.kt
  • app/src/main/java/app/gamenative/workshop/WorkshopManager.kt
  • app/src/main/java/app/gamenative/workshop/WorkshopModPathDetector.kt

- Full-screen workshop manager dialog with hero image, mod list,
  select-all/deselect-all, and per-mod toggle switches
- FolderPickerDialog for manually choosing mod installation paths
  when automatic detection fails
- Improved mod path detection: binary scanning, config file parsing,
  AppData walking with fuzzy matching
- CKM (Creation Kit Module) extraction for Skyrim mods
- LZMA decompression with concurrent processing
- ZIP extraction for single-archive workshop uploads
- Magic-byte file type detection and extension fixing
- Disk space checking before downloads
- allSelected derivedStateOf optimization (removed wasteful toMap key)
- Added KNOWN_EXTENSIONS to WorkshopItem for file type validation
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
app/src/main/java/app/gamenative/workshop/WorkshopModPathDetector.kt (1)

46-61: ⚠️ Potential issue | 🟠 Major

Keep generic asset folders out of the candidate-name sets.

textures, sounds, audio, content, assets, and especially data are ordinary game resource directories; some are even treated as infrastructure in CONFIG_SKIP_DIRS below. With these additions the install/binary heuristics can now return SymlinkIntoDir(<game>/Data|Textures|Audio...), and Phase 6 later materializes that result, which risks dropping workshop items into the base asset tree instead of a real mod folder.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/workshop/WorkshopModPathDetector.kt` around
lines 46 - 61, The MEDIUM_CONFIDENCE_NAMES and LOW_CONFIDENCE_NAMES sets in
WorkshopModPathDetector.kt include generic asset folders that should not be
treated as mod candidates; remove "textures", "sounds", "audio" from
MEDIUM_CONFIDENCE_NAMES and remove "content", "assets", "data" (and any other
generic resource names like "textures"/"audio" if duplicated) from
LOW_CONFIDENCE_NAMES so they are no longer part of ALL_MOD_DIR_NAMES, and
instead ensure these generic names are present in CONFIG_SKIP_DIRS (or an
equivalent skip list) so the installer/binary heuristics won't treat them as mod
install targets; keep the ALL_MOD_DIR_NAMES union as-is so it reflects the
corrected sets.
♻️ Duplicate comments (3)
app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt (1)

1307-1315: ⚠️ Potential issue | 🟠 Major

Don't log the full override path.

This still emits the user-selected absolute path at info level. That path can include the Wine username and other local directory names; log only set/cleared or a sanitized basename.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt`
around lines 1307 - 1315, The current onModPathChanged lambda stores the full
user-selected absolute path (workshopModPath) and logs it via
Timber.tag("Workshop").i(...); change the logging so it never emits the full
absolute path: after setting workshopModPath and saving via
ContainerUtils.getContainer(...)/container.putExtra(...)/container.saveData(),
log only whether the override was set or cleared or log a sanitized basename
(e.g., File(newPath).name) instead of the full newPath; update the Timber call
in the onModPathChanged block to use that sanitized value or a "set/cleared"
message.
app/src/main/java/app/gamenative/workshop/WorkshopManager.kt (2)

2100-2202: ⚠️ Potential issue | 🟠 Major

Unset the manual override when manual symlink creation fails.

If targetDir is invalid or unwritable, the catch only logs. hasManualModPath stays true, so later logic still suppresses Phase 6 filesystem sync and stale-symlink cleanup. That leaves the game with neither the manual links nor the old auto-detected links.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/workshop/WorkshopManager.kt` around lines
2100 - 2202, The try/catch around creating symlinks for the manual path
currently only logs errors (catch block) and leaves the manual override set
(workshopModPath / hasManualModPath), which prevents Phase 6 sync and
stale-symlink cleanup; modify the catch(e: Exception) in the symlink creation
block so that on failure you clear/unset the manual override (e.g., reset
workshopModPath to empty or call the existing routine that disables manual mode)
and update any related state (hasManualModPath) and logs so the code will fall
back to auto-detection and allow the normal cleanup/sync to run. Ensure you
reference the same symbols used here: targetDir, modDirs, workshopModPath,
hasManualModPath, and the catch block that currently calls
Timber.tag(TAG).w(...).

505-560: ⚠️ Potential issue | 🟠 Major

ZIP extraction still skips updates and extension-repair cases.

extractZipMods() still runs before fixFileExtensions(), so archives that only become recognizable after magic-byte repair are left unextracted until a later run. The permanent .zip_extracted marker also survives re-downloads, so updated archives are skipped entirely.

Also applies to: 3276-3283

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/app/gamenative/workshop/WorkshopManager.kt` around lines
505 - 560, extractZipMods currently runs before fixFileExtensions and uses a
permanent ".zip_extracted" marker that causes updated or extension-repaired
archives to be skipped; change the flow so extraction occurs after
fixFileExtensions (run fixFileExtensions before calling extractZipMods) and
modify extractZipMods to treat the marker as versioned: instead of a permanent
file, record/check a stamp tied to the ZIP (e.g., store marker named
".zip_extracted_<zipLastModified>" or compute a checksum) or compare the
zipFile.lastModified() against the marker timestamp so re-downloaded/modified
archives are re-extracted; update code paths involving extractZipMods and the
marker creation logic (references: extractZipMods(), fixFileExtensions(), the
".zip_extracted" marker handling, and the zipFile delete/createNewFile sequence)
to implement this behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/app/gamenative/workshop/WorkshopManager.kt`:
- Around line 3393-3406: The catch for ContainerUtils.getOrCreateContainer(...)
currently only logs and allows workshop download to proceed, which reintroduces
the orphan-container/data-loss path; instead fail-closed by aborting/propagating
the error when container bootstrap fails: replace the catch-with-log with logic
that either rethrows the exception or returns from the surrounding method (so no
subsequent writes to getContainerWinePrefix(...) occur), and keep or augment the
Timber.tag(TAG).w(...) message to include the reason before exiting; reference
ContainerUtils.getOrCreateContainer, the containerId = "STEAM_$appId" local, and
subsequent use of getContainerWinePrefix(...) to locate where to stop progress.

---

Outside diff comments:
In `@app/src/main/java/app/gamenative/workshop/WorkshopModPathDetector.kt`:
- Around line 46-61: The MEDIUM_CONFIDENCE_NAMES and LOW_CONFIDENCE_NAMES sets
in WorkshopModPathDetector.kt include generic asset folders that should not be
treated as mod candidates; remove "textures", "sounds", "audio" from
MEDIUM_CONFIDENCE_NAMES and remove "content", "assets", "data" (and any other
generic resource names like "textures"/"audio" if duplicated) from
LOW_CONFIDENCE_NAMES so they are no longer part of ALL_MOD_DIR_NAMES, and
instead ensure these generic names are present in CONFIG_SKIP_DIRS (or an
equivalent skip list) so the installer/binary heuristics won't treat them as mod
install targets; keep the ALL_MOD_DIR_NAMES union as-is so it reflects the
corrected sets.

---

Duplicate comments:
In
`@app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt`:
- Around line 1307-1315: The current onModPathChanged lambda stores the full
user-selected absolute path (workshopModPath) and logs it via
Timber.tag("Workshop").i(...); change the logging so it never emits the full
absolute path: after setting workshopModPath and saving via
ContainerUtils.getContainer(...)/container.putExtra(...)/container.saveData(),
log only whether the override was set or cleared or log a sanitized basename
(e.g., File(newPath).name) instead of the full newPath; update the Timber call
in the onModPathChanged block to use that sanitized value or a "set/cleared"
message.

In `@app/src/main/java/app/gamenative/workshop/WorkshopManager.kt`:
- Around line 2100-2202: The try/catch around creating symlinks for the manual
path currently only logs errors (catch block) and leaves the manual override set
(workshopModPath / hasManualModPath), which prevents Phase 6 sync and
stale-symlink cleanup; modify the catch(e: Exception) in the symlink creation
block so that on failure you clear/unset the manual override (e.g., reset
workshopModPath to empty or call the existing routine that disables manual mode)
and update any related state (hasManualModPath) and logs so the code will fall
back to auto-detection and allow the normal cleanup/sync to run. Ensure you
reference the same symbols used here: targetDir, modDirs, workshopModPath,
hasManualModPath, and the catch block that currently calls
Timber.tag(TAG).w(...).
- Around line 505-560: extractZipMods currently runs before fixFileExtensions
and uses a permanent ".zip_extracted" marker that causes updated or
extension-repaired archives to be skipped; change the flow so extraction occurs
after fixFileExtensions (run fixFileExtensions before calling extractZipMods)
and modify extractZipMods to treat the marker as versioned: instead of a
permanent file, record/check a stamp tied to the ZIP (e.g., store marker named
".zip_extracted_<zipLastModified>" or compute a checksum) or compare the
zipFile.lastModified() against the marker timestamp so re-downloaded/modified
archives are re-extracted; update code paths involving extractZipMods and the
marker creation logic (references: extractZipMods(), fixFileExtensions(), the
".zip_extracted" marker handling, and the zipFile delete/createNewFile sequence)
to implement this behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4bd047f0-aeb3-46d1-912a-903fb50bf0ed

📥 Commits

Reviewing files that changed from the base of the PR and between 6deb3c3 and bcc000e.

📒 Files selected for processing (5)
  • app/src/main/java/app/gamenative/ui/component/dialog/WorkshopManagerDialog.kt
  • app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt
  • app/src/main/java/app/gamenative/workshop/WorkshopItem.kt
  • app/src/main/java/app/gamenative/workshop/WorkshopManager.kt
  • app/src/main/java/app/gamenative/workshop/WorkshopModPathDetector.kt
✅ Files skipped from review due to trivial changes (1)
  • app/src/main/java/app/gamenative/workshop/WorkshopItem.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/app/gamenative/ui/component/dialog/WorkshopManagerDialog.kt

@utkarshdalal
Copy link
Copy Markdown
Owner

conflicts :(

# Conflicts:
#	app/src/main/java/app/gamenative/ui/component/dialog/WorkshopManagerDialog.kt
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/src/main/java/app/gamenative/ui/component/dialog/WorkshopManagerDialog.kt (1)

161-166: Switch the workshop list to LazyColumn.

Line 165 makes the whole dialog a scrolling Column, and Lines 406-478 then compose every filtered row and preview image eagerly. That will get expensive fast on accounts with large Workshop libraries and is likely to cause jank/memory pressure in exactly the screen that can grow unbounded.

Also applies to: 406-478

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/app/gamenative/ui/component/dialog/WorkshopManagerDialog.kt`
around lines 161 - 166, The dialog currently uses a full-screen scrollable
Column (Modifier.verticalScroll(scrollState)) in WorkshopManagerDialog which
eagerly composes every filtered row and preview (lines ~406-478); replace the
outer Column with a LazyColumn (use rememberLazyListState) and move
filtering/iteration into LazyColumn.items with stable keys so only visible items
are composed, and ensure per-item composables (the workshop row/preview
functions) load images lazily to avoid holding all bitmaps in memory; update
modifiers (fillMaxSize(), background(Color.Black)) to the LazyColumn and remove
verticalScroll(scrollState).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/main/java/app/gamenative/ui/component/dialog/WorkshopManagerDialog.kt`:
- Around line 612-617: The breadcrumb construction in the remember block
(variables breadcrumb, currentDir, currentRootLabel, roots) flattens path parts
by replacing separators with spaces; instead, split the relative path on both
'/' and '\' into segments and rejoin with " / " so segments become "Root / Mods
/ Foo / Bar"; update the rel computation to derive segments (e.g.,
rel.split(Regex("[/\\\\]+")).filterNot { it.isBlank() }) and use joinToString("
/ ") combined with currentRootLabel when building the final breadcrumb string.

---

Nitpick comments:
In
`@app/src/main/java/app/gamenative/ui/component/dialog/WorkshopManagerDialog.kt`:
- Around line 161-166: The dialog currently uses a full-screen scrollable Column
(Modifier.verticalScroll(scrollState)) in WorkshopManagerDialog which eagerly
composes every filtered row and preview (lines ~406-478); replace the outer
Column with a LazyColumn (use rememberLazyListState) and move
filtering/iteration into LazyColumn.items with stable keys so only visible items
are composed, and ensure per-item composables (the workshop row/preview
functions) load images lazily to avoid holding all bitmaps in memory; update
modifiers (fillMaxSize(), background(Color.Black)) to the LazyColumn and remove
verticalScroll(scrollState).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 536e4862-b862-4140-8ec1-ba8f8962b839

📥 Commits

Reviewing files that changed from the base of the PR and between bcc000e and 00e7af6.

📒 Files selected for processing (2)
  • app/src/main/java/app/gamenative/ui/component/dialog/WorkshopManagerDialog.kt
  • app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt
✅ Files skipped from review due to trivial changes (1)
  • app/src/main/java/app/gamenative/ui/screen/library/appscreen/SteamAppScreen.kt

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/main/java/app/gamenative/ui/component/dialog/WorkshopManagerDialog.kt`:
- Around line 595-608: The directory enumeration in LaunchedEffect(currentDir)
can throw or return null and currently loading may never be reset; wrap the
withContext(Dispatchers.IO) read in a try/catch/finally so that any exceptions
from dir.listFiles() are caught, set subDirs = emptyList() on error, and always
set loading = false in the finally block; specifically modify the LaunchedEffect
block that references currentDir, loading, subDirs, withContext(Dispatchers.IO)
and dir.listFiles() to perform the guarded read (filter/sort) inside the try,
handle null/exception by assigning emptyList(), and ensure loading is cleared
regardless of success or failure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a8fcffd9-82cf-4d04-84f8-95aed1e98d7c

📥 Commits

Reviewing files that changed from the base of the PR and between 00e7af6 and 437590a.

📒 Files selected for processing (1)
  • app/src/main/java/app/gamenative/ui/component/dialog/WorkshopManagerDialog.kt

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

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="app/src/main/java/app/gamenative/ui/component/dialog/WorkshopManagerDialog.kt">

<violation number="1" location="app/src/main/java/app/gamenative/ui/component/dialog/WorkshopManagerDialog.kt:606">
P2: Broad `Exception` catch in `LaunchedEffect` swallows coroutine cancellation and can apply stale UI state after directory changes.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants