Skip to content

Add Vortex dependency/status dashlet and wcc_lite auto-acquisition (Unit J) - #23

Merged
TheValiantOne merged 5 commits into
mainfrom
feature/vortex-bundle-tooling-status-dashlet
Aug 11, 2026
Merged

Add Vortex dependency/status dashlet and wcc_lite auto-acquisition (Unit J)#23
TheValiantOne merged 5 commits into
mainfrom
feature/vortex-bundle-tooling-status-dashlet

Conversation

@TheValiantOne

@TheValiantOne TheValiantOne commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Unit J — dependency/status tile + bundle-tooling acquisition

Summary

  • Adds a Vortex dashboard tile (context.registerDashlet) showing WSM's dependency/status snapshot: text-merge/bundle dependency validity, mods directory (+ exists), conflict count, merged mod name, and detected QuickBMS/wcc_lite paths. Spawns a short-lived WsmMcpClient per its documented lifecycle policy, closed in a finally.
  • Adds bundle-tooling detection (src/bundleTools.ts) for QuickBMS and wcc_lite: checks this extension's own managed storage (getBundleToolsDir, reserved by Unit F) and, as a fallback, a prior IDCs/WitcherScriptMerger fork install's own Tools\ folder (the build Vortex's built-in game-witcher3 extension already downloads as W3ScriptMerger).
  • Adds wcc_lite auto-download (src/wccLiteAcquisition.ts + src/nexusDownloader.ts) via Vortex's own Nexus-download mechanism (api.ext.nexusDownload/nexusGetModFiles), reusing archiveExtractor.ts's existing extract plumbing. QuickBMS is detection/link-only, never auto-downloaded.
  • Populates wsmEnv.ts's previously-unpopulated quickBmsPath/quickBmsPluginPath/wccLitePath fields via detectBundleTools(api), wired into the status tile's own WSM spawn.

Licensing/EULA caveat — please read before merging

wcc_lite is an official CD Projekt Red tool ("The Witcher 3 modding tools" / "Official ModKit", Nexus mod id 3173 on the witcher3 domain), distributed via Nexus Mods, not a WSM-authored artifact. This PR auto-downloads it into a separate, Vortex-managed location (<userData>/witcherscriptmerger-vortex/bundle-tools/wcc_lite/) — it never touches this repo's own source control, and the root CLAUDE.md's existing "not in source control" policy for QuickBMS/wcc_lite is unchanged by this PR.

That said: this extension redistributing/auto-fetching wcc_lite at runtime has not been independently confirmed against Nexus Mods' / CD Projekt Red's own redistribution terms beyond "it's an official tool hosted on an official Nexus mod page." The root CLAUDE.md already treats QuickBMS/wcc_lite licensing as an explicitly open decision requiring the repo owner's sign-off before changing the "not bundled in source control" policy — auto-fetching at runtime into a Vortex-managed location is a different question from bundling in source control, but it still deserves the repo owner's explicit go/no-go before this ships broadly. Flagging this plainly rather than burying it, per this unit's own instructions.

QuickBMS is intentionally not auto-downloaded (no canonical Nexus-hosted release found, redistribution terms murkier) — the status tile only detects an existing install or links to QuickBMS's own homepage (http://aluigi.altervista.org/quickbms.htm), mirroring WitcherScriptMerger/Forms/DependencyForm.cs's own behavior for this exact dependency.

Mod id / API-surface caveats

  • Mod id 3173 ("Official ModKit," CD Projekt RED) was corroborated via two independent web searches and cross-referenced against WitcherScriptMerger/Forms/DependencyForm.cs's own wcc_lite link (a Nexus news post announcing a ModKit update, itself pointing at this same mod page) — not verified against a live, authenticated Nexus session (this environment has no Nexus API key, and nexusmods.com returns HTTP 403 for unauthenticated scraping). The exact file id is deliberately not hardcoded — it's resolved live via api.ext.nexusGetModFiles, picking the file Nexus marks is_primary, so it doesn't go stale as the ModKit is updated.
  • Deviation from the task's testing instruction, disclosed rather than silent: the task asked to mock the download at githubRelease.ts's nodeHttpsClient boundary. wcc_lite is fetched through Vortex's own Nexus-download mechanism (api.ext.nexusDownload), not a plain HTTPS GET — Nexus doesn't serve an unauthenticated direct-download URL for a raw HttpClient to hit even in principle. The mocked-download pipeline is instead covered by injecting a fake NexusDownloader/ArchiveExtractor (src/wccLiteAcquisition.test.ts), the same DI-seam pattern toolAcquisition.test.ts already uses for the WSM-tool pipeline.
  • Archive-layout caveat: the "Official ModKit" download's exact internal zip layout was never verified against a live download. If wcc_lite.exe isn't found anywhere inside it after extraction (bounded search, depth 6), acquisition throws a clear, specific error rather than silently failing or wrongly succeeding.
  • api.ext.nexusDownload/nexusGetModFiles/selectors.downloadPathForGame/util.opn are all real, verified-against-typings API surfaces (node_modules/@nexusmods/vortex-api/lib/api.d.ts), but none of this PR's real Vortex-backed implementations (nexusDownloader.ts's createVortexNexusDownloader, the dashlet's actual render/click flow) have been exercised against a real Vortex host — same "real but unexercised" status archiveExtractor.ts's own createVortexArchiveExtractor already carries from Unit F.

Testing

  • npm run typecheck && npm run build && npm run lint && npm test — all clean (84 unit tests across 13 files).
  • npm run test:integration — all clean (92 tests across 16 files, including the pre-existing real-spawned-WSM-process tests and this unit's new test/bundleTools.integration.test.ts, which proves real, no-mock local detection: a fake wcc_lite.exe/quickbms.exe+witcher3.bms placed under a real scratch getBundleToolsDir(api)-shaped path is found without any network/extraction attempt, and acquireWccLite short-circuits before touching an injected downloader/extractor that would fail the test if invoked).
  • Manual/real-Vortex-session verification explicitly not done (no real Vortex host in this environment): a real wcc_lite download completing end-to-end and get_status subsequently reporting bundleDependenciesValid: true afterward is unverified beyond the mocked/local tests above. Flagging this plainly per the task's own instructions.
  • Code-reviewed twice (code-review skill), both rounds' findings fixed:
    • Round 1: a QuickBMS cross-root path-mismatch bug (exe from one candidate root could pair with a plugin from a different root), a status-tile Promise chaining bug (the "Get wcc_lite" button's busy state cleared before its own post-download refresh actually finished), a stale-error-not-cleared-on-refresh bug, a missing concurrency guard between the two tile buttons, <a target="_blank"> vs. vortex-api's own util.opn for external links inside Vortex's Electron renderer, missing wipe-before-extract/in-flight-coalescing on acquireWccLite (now mirrors toolAcquisition.ts's own pattern), and an implicit (peer-dependency-only) react/react-dom/@types/react resolution now made explicit in package.json.
    • Round 2 (headline finding): registerWsmStatusDashlet(context) was being called inside context.once(...), but @nexusmods/vortex-api's own lib/api.d.ts doc comment on IExtensionContext is explicit that register functions "must be called immediately inside the init function" and that once is documented as being for extension setup "except for the register calls" — so the dashlet registration would very likely never have taken effect against a real Vortex host. Fixed: registerWsmStatusDashlet(context) now runs directly in main's body, synchronously, before context.once(...). This also corrected a stale doc comment in index.ts (predating this unit) that told future units to register inside context.once — left as-is, it would have propagated the same bug into units G/H/I's own registrations. Also fixed this round: findFileByNameBounded now propagates non-ENOENT readdir errors instead of silently treating them as "nothing here" (a transient lock/permission error could otherwise cause acquireWccLite to wipe a working install before an unnecessary re-download) and now prefers an x64-path match when an archive ships both architectures at the same depth; getWsmStatusSummary now wraps its pre-connect steps (fileExists/detectBundleTools) in their own try/catch so a filesystem error there produces the documented {kind:'error'} shape instead of an unhandled rejection; the status tile now guards every async state-setter against firing after unmount (the tile's own visibility is tied to a live game-mode switch, so a user can navigate away mid-download); a stale duplicate fileExists/isEnoent implementation was removed in favor of importing bundleTools.ts's (now-exported) copy; nexusDownloader.ts's timeout error now names the last-observed DownloadState (so a user-paused download reads as "paused," not a generic timeout); wccLiteAcquisition.ts's "no wcc_lite.exe found" error now names the exact mod/file id that was tried; and two stale doc comments (storage.ts's getBundleToolsDir, wsmEnv.ts's bundle-tool-path fields overclaiming universal population) were corrected.

Known limitations left as-is (flagging rather than silently shipping)

  • Reachability: getWsmStatusSummary reports {kind:'not-acquired'} whenever no WSM build has been downloaded yet, and no unit so far (including this one) has built a "Get WitcherScriptMerger" UI trigger for toolAcquisition.ts's own acquireWsmTool (it's exported specifically for "a later unit's own UI trigger... to call on explicit user request," per that file's own doc comment, predating this unit). So for a user with no WSM build yet, this unit's dashlet — and the wcc_lite/QuickBMS detection it surfaces — is only reachable once some unit adds that trigger. Not this unit's scope to add (out of scope per the task instructions), but worth the coordinator's awareness.
  • No feature gate on the wcc_lite auto-download button: per this unit's own instructions ("Auto-fetching at runtime... is still worth the repo owner's explicit go/no-go before this ships broadly — say so plainly, don't bury it"), this PR implements the working auto-fetch and flags the caveat prominently above, rather than adding an additional opt-in/config toggle not requested by the task. Worth an explicit call from the repo owner on whether a gate is wanted before merging.
  • No React/DOM test coverage for WsmStatusDashletContent itself (only registerWsmStatusDashlet's registerDashlet argument shape is tested) — this repo has no React testing-library dependency for any unit yet; adding one felt like scope creep for this unit specifically. The component's data-fetching logic (wsmStatusSummary.ts) has full coverage; the render/hook logic does not.
  • statusTile.ts wraps its content in a plain <div> rather than vortex-api's own exported Dashlet chrome component (confirmed real and unconditionally exported in lib/api.d.ts) — left as a documented, deliberate choice (see that file's own doc comment) rather than risk getting IDashletProps' actual children-rendering behavior wrong without a real Vortex host to verify against.

AI-assisted development

This PR was substantially produced by an AI coding agent (Claude Code), per CONTRIBUTING.md's "AI-assisted development" section.

Chris Knight and others added 5 commits August 10, 2026 16:15
…nit J)

Adds a status dashlet showing WSM's dependency/status snapshot (text-merge and
bundle dependency validity, mods directory, conflict count, detected
QuickBMS/wcc_lite paths), and bundle-tooling detection/acquisition:
QuickBMS is detected only (never downloaded, per its murkier licensing);
wcc_lite is auto-fetched from its Nexus Mods "Official ModKit" page via
Vortex's own Nexus-download mechanism, reusing archiveExtractor.ts's existing
extract plumbing. Populates wsmEnv.ts's previously-unpopulated
quickBmsPath/quickBmsPluginPath/wccLitePath fields.

See the PR description for the licensing/EULA caveat this introduces and
what was/wasn't verified against a real Vortex host.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GXAuGMLB44T5Zv5o5ZzKah
Move registerWsmStatusDashlet(context) out of context.once and into
main()'s own body: @nexusmods/vortex-api's own lib/api.d.ts documents
register functions as needing to be called immediately inside init, not
deferred into once, so the dashlet registration likely never took effect
against a real Vortex host. Also corrects a stale index.ts doc comment
(predating this unit) that told future units to register from inside
once, which would have propagated the same bug into later units.

Also fixes: findFileByNameBounded now propagates non-ENOENT readdir
errors instead of silently treating them as "nothing here" (previously
risked a false-negative detection triggering a destructive wipe +
re-download of a working install) and now prefers an x64-path match when
an archive ships both architectures; getWsmStatusSummary wraps its
pre-connect steps in their own try/catch so a filesystem error produces
the documented {kind:'error'} result instead of an unhandled rejection;
the status tile guards every async state setter against firing after
unmount; a duplicate fileExists/isEnoent implementation was removed in
favor of bundleTools.ts's now-exported copy; nexusDownloader.ts's timeout
error now names the last-observed download state; wccLiteAcquisition.ts's
"not found" error now names the exact mod/file id tried; and two stale
doc comments were corrected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GXAuGMLB44T5Zv5o5ZzKah
Both units add their own registration to index.ts's main(): Unit G's
did-deploy conflict-scan handler (registered inside context.once, an
event listener) and Unit J's status dashlet (registered directly in
main(), per Vortex's own documented contract that register calls must
not be deferred through context.once - Unit J's own header-comment
correction on this point, citing the exact api.d.ts language, is kept as
the canonical explanation here).

vortexApiStub.ts's shared selectors object needed both Unit J's
downloadPathForGame and Unit G's profileById as separate entries.
index.test.ts needed the union of both branches' vi.hoisted mocks,
vi.mock blocks, and test suites (Unit G's "did-deploy conflict scanning"
describe block plus Unit J's dashlet-registration test).

Verified after resolution: typecheck, build, lint, 128 unit tests, and
138 total tests including integration all pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GXAuGMLB44T5Zv5o5ZzKah
Unit I's merge-history dashlet merged to main since this branch's last
resolution. Both it and Unit J's status dashlet register directly in
main() (per the same register-outside-context.once contract Unit J's
own header-comment correction documents). Combined the imports, header
comment (now describing the merge-history dashlet as the third
registration, the status dashlet as the fourth), and both
context.register* calls. index.test.ts and vortexApiStub.ts merged
cleanly without manual intervention - Unit I's context.registerDashlet
stub and Unit J's vi.mock('./statusTile') approach don't overlap.

Verified after resolution: typecheck, build, lint, 138 unit tests, and
149 total tests including integration all pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GXAuGMLB44T5Zv5o5ZzKah
main now contains both Unit I's merge-history dashlet and Unit H's resolve
action, each independently colliding with this branch's status-dashlet
registration in index.ts/index.test.ts. Combined all three registrations
(dashlet, resolve action, status tile) in main()'s body and renumbered the
header doc-comment's registration narrative accordingly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GXAuGMLB44T5Zv5o5ZzKah
@TheValiantOne
TheValiantOne merged commit cba957a into main Aug 11, 2026
1 check passed
@TheValiantOne
TheValiantOne deleted the feature/vortex-bundle-tooling-status-dashlet branch August 11, 2026 01:37
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.

1 participant