Imported from Zendevve/nextended (
AGENTS.md). Install upstream withnpx skills add Zendevve/nextended. Copyright stays with the author.
Repository Guidelines
Project Overview
nextended (v1.1.0) is a Manifest V3 browser extension: a power suite for Nexus Mods.
Features: instant single-mod downloads (timer/requirements bypass), collections bulk
downloader (GraphQL fetch, mandatory/optional/custom selection, revision diffing, local
file matcher, pause/resume/stop), rate-limit/cooldown protection, archived-file unlock
injector, popup quick toggles + full options page.
Architecture & Data Flow
- MV3 surfaces (
manifest.json): background service workerbackground.js(type: module); isolated-world content bundlecontent.js+content.cssatdocument_idle; MAIN-worldpageShield.jsatdocument_start;action.default_popup: popup/index.html;options_ui.page: options/index.htmlwithopen_in_tab: true. - Content entry (
src/content/index.ts): attachesClickInterceptor+RequirementsBypass, watches SPA navigation (patchedhistory.pushState/replaceStatepopstate+ debouncedMutationObserver+ post-hydrationhandleRoute), mounts aCollectionEngineon collection routes, auto-starts?file_id=/countdown pages viaSingleDownloader.startDownloadFlow, always runsArchiveInjector.inject().
- Single-download flow:
ClickInterceptor.extractFileId/isNMMDownload/ game-ID resolution →GraphQLClient.fetchPrimaryModFileId/fetchGameIdfallback →SingleDownloader.startDownloadFlow(GenerateDownloadUrl,DownloadPopUp/ModRequirementsPopUpwidget fallbacks, Cloudflare/VPN redirect fallback) →chrome.runtime.sendMessage({type: 'TRIGGER_DOWNLOAD' | 'AUTO_CLOSE_TAB'}). - Background (
src/background/index.ts):DownloadManager.init()at import;onMessagehandlesAUTO_CLOSE_TAB(timedchrome.tabs.remove,TabManagerfallback) andTRIGGER_DOWNLOAD(DownloadManager.triggerDownload,return truefor asyncsendResponse).DownloadManageralso handlesonDeterminingFilenamewhenoverrideFileNamesis set. - Collections flow (
collectionEngine.ts):GraphQLClient.fetchCollectionMods→ split mandatory/optional → rendercollectionToolbar/progressBar/logConsole/selectModsModal/updateRevisionModal→ per-fileSingleDownloader+RateLimiterpauses +FileMatcherskips +RevisionDiffer(added/updated/removed). - State: persisted only via
StorageManager(chrome.storage.local,localStoragefallback); transient route/engine state in content-script module globals plus DOM mount#nextended-collection-containerandwindow.__nextended_shield_activeguard. - Page shield (
src/content/pageShield.ts, MAIN world): stubs blocked analytics (statistics,ramp,Nexus,user.statistics,analytics,mixpanel,_qevents,quantserve,pSUPERFLY,dataLayer,gtag,ga), wrapsexitFullscreen, swallows matchingerror/unhandledrejectionnoise so blocked scripts don't break the page.
Key Directories
src/common/: sharedtypes.ts,endpoints.ts,config.ts(DEFAULT_CONFIG),storage.ts(StorageManager),logger.ts(Logger).src/background/:index.ts(message dispatcher),downloadManager.ts,tabManager.ts.src/content/:index.ts(router/mount point),pageShield.ts(MAIN-world shield).src/content/interceptors/:clickInterceptor.ts(download-click capture + ID resolution),requirementsBypass.ts(requirements tab/modal bypass).src/content/modules/:graphQLClient.ts,singleDownloader.ts,rateLimiter.ts,archiveInjector.ts.src/content/modules/collections/:collectionEngine.ts;utils/fileMatcher.ts,utils/revisionDiffer.ts;components/{collectionToolbar,progressBar,logConsole, selectModsModal,updateRevisionModal}.ts.src/content/styles/content.css: shipped asdist/content.css.src/popup/{index.html,popup.ts,popup.css}: quick toggles + status indicator.src/options/{index.html,options.ts,options.css}: full settings, debounced auto-save.tests/unit/: 14*.test.tssuites (see Testing & QA).icons/:icon-{16,48,128}.png;dist/is the unpacked-extension build output.
Development Commands
npm install # install deps (npm + package-lock.json)
npm run build # node build.js → dist/
npm test # vitest run (single pass)
npm run test:watch # vitest (watch mode)
Manual load: build, then chrome://extensions/ → Developer mode → Load unpacked → select
dist/ (Firefox: about:debugging#/runtime/this-firefox). No lint/format script.
Code Conventions & Common Patterns
- TypeScript
strict,ES2022/ESNext+Bundlerresolution (tsconfig.json);chrome+vitest/globalstypes. - Static-only service classes:
StorageManager,DownloadManager,TabManager,GraphQLClient,SingleDownloader,RateLimiter,ClickInterceptor,RequirementsBypass,ArchiveInjector,FileMatcher,RevisionDiffer. Example:await StorageManager.getConfig(),RateLimiter.calculateFilePause(kb, mb, extra). - Const-object namespaces:
Logger,ENDPOINTS,DEFAULT_CONFIG. Logging always viaLogger.{debug,info,warn,error}(prefixes[nextended]); neverconsole.*directly. - Naming:
PascalCaseclasses/components,camelCasemethods/fields,UPPER_SNAKEconsts (ENDPOINTS,DEFAULT_CONFIG),*.test.tstests mirroring module names (e.g.rateLimiter.test.ts). - Async:
async/awaitthroughout; wrap callback Chrome APIs innew Promise(DownloadManager.triggerDownload);return truefromonMessagelisteners with asyncsendResponse; always checkchrome.runtime.lastErroraftertabs.remove/downloads.download. - Environment guards:
typeof chrome !== 'undefined'with fallbacks (localStorage, anchor-click download,window.close).JSON.parsefallbacks use silentcatch {}. - DOM idempotence:
attach()/inject()guarded byattachedflags orWeakSet(ClickInterceptor.processing,ArchiveInjector.handled); route dedupe vialastRoute/lastAutoStartedKey. - SPA handling: patch
history.pushState/replaceState, listenpopstate, debounceMutationObserver(scheduleRouteCheck(300)), defer past hydration (requestIdleCallbackelsesetTimeout 300); probe mount targets with fallbacks ("Add collection" card → Media heading/tabs →main/#mainContent/#__next). - Config access:
StorageManager.getConfig()mergesDEFAULT_CONFIGunder stored values;setConfig(partial)is read-merge-write. Keys:nextended_config,nextended_history,nextended_rate_limit. - Error handling: broad
try/catch→Logger.error/warn+ safe fallback (baresuggest(),null, redirect to files tab); suppress only matched page-shield noise.
Important Files
- Entry points:
src/background/index.ts,src/content/index.ts,src/content/pageShield.ts,src/popup/{index.html,popup.ts},src/options/{index.html,options.ts}. - Config/build:
manifest.json,package.json,build.js(5 IIFE bundles + asset copy),vite.config.ts(entries + Vitesthappy-domblock),tsconfig.json. - Core logic:
src/content/modules/singleDownloader.ts(startDownloadFlow),src/content/interceptors/clickInterceptor.ts(attach,extractFileId,isNMMDownload),src/content/modules/graphQLClient.ts(fetchCollectionMods,fetchGameId,fetchPrimaryModFileId),src/content/modules/collections/collectionEngine.ts. - Shared:
src/common/{types,config,storage,endpoints,logger}.ts. - Docs/legal:
README.md,CHANGELOG.md,LICENSE(proprietary; personal non-commercial use).
Runtime/Tooling Preferences
- Runtime: Node.js + npm (
"type": "module",package-lock.jsoncommitted). No Bun/pnpm config. - Bundler: Vite 6 library-mode IIFE builds for
background.js,content.js,pageShield.js(global namesNexusPowerSuite{Background,Content,PageShield}); separate multi-page builds forpopup/andoptions/.vite.config.tsalso works as a Vitest config; do not treat itsrollupOptions.inputas the full build —build.jsis authoritative. - Extension targets: Chromium (
chrome://extensions, Load unpackeddist/) and Firefox (about:debugging). Host scope is Nexus Mods +api-router.nexusmods.com+nexus-cdn. - Test env:
happy-domvia Vitest;@types/chrome+@types/nodefor extension/Node APIs.
Testing & QA
- Framework: Vitest 3 (
globals: true,environment: 'happy-dom',include: ['tests/**/*.test.ts']invite.config.ts). - Suites (
tests/unit/):archiveInjector,clickInterceptor,conflictDetector,downloadManager,externalDownloader,fileMatcher,graphQLClient,modManagerButtonInjector,options,pageShield,rateLimiter,revisionDiffer,selectModsModal,singleDownloader(172 passing perCHANGELOG.md1.1.0). - Pattern: import unit under test, seed
StorageManagerstate inbeforeEach, assert pure helpers directly. Example (rateLimiter.test.ts): seed{count: 199, ...}, expectregisterDownload()→{requiresCooldown: true, waitTimeSec: 300}. - No coverage thresholds or e2e harness configured; verify extension changes with
npm test,npm run build, and manual Load-unpacked smoke test on Nexus Mods pages.
Agent skills
Issue tracker
Issues are tracked as GitHub issues in Zendevve/nextended via the gh CLI. See docs/agents/issue-tracker.md.
Triage labels
Default five-label vocabulary, unchanged: needs-triage, needs-info, ready-for-agent, ready-for-human, wontfix. See docs/agents/triage-labels.md.
Domain docs
Single-context: one CONTEXT.md + docs/adr/ at the repo root. See docs/agents/domain.md.
