Repository: KSP-CKAN/CKAN (2026-04-13T05:13:06Z)

Issue:
[Bug]: Installing from a ckan file has issues with suggests or recommends stanza having conflicts
### Is there an existing issue for this?

- [x] I have checked the existing issues for an existing issue. I even actually looked at the ones that are pinned to the top!

### Operating System

Win-11

### CKAN Version

1.36.3.26017

### Games

KSP 1

### Game Version

1.12.5

### Did you make any manual changes to your game folder (i.e., not via CKAN)?

_No response_

### Describe the bug

Having a "recommends" or a "suggests" stanza in the ckan will cause any mods which are listed as a conflict in an earlier line to hide the later ones.

### Steps to reproduce

Create a .ckan export
Add the following: to the end, following the "depends" stanza

```json
  "recommends": [
            { "name": "KcalbelohSystem" },
            { "name": "PromisedWorldsCore" },
            { "name": "JNSQ" },
            { "name": "GPP" },
            { "name": "OuterPlanetsMod" }
      ]
```

Try to install from the ckan.
The following mods will be shown in the Recommendations line:
    JNSQ
    Kcalbeloh System
    Promised Worlds Core

Moving JNSQ after the GPP line will show the following:
    Galileio's Planet Pack
    Kcalbeloh System
    Outer Planets Mod
    Promised Worlds - Core

I would have expected all the mods to be listed, and if two selected which have a conflict, to show them in red

### Relevant log output

_No response_


Discussion:
I haven't checked yet, but I would assume this affects _all_ recommendations and suggestions, not just modpacks.



Patch:
diff --git a/Core/Relationships/RelationshipResolver.cs b/Core/Relationships/RelationshipResolver.cs
index 0c1314af6..8bd13fce7 100644
--- a/Core/Relationships/RelationshipResolver.cs
+++ b/Core/Relationships/RelationshipResolver.cs
@@ -281,7 +281,10 @@ private void ResolveStanza(List<RelationshipDescriptor>?        stanza,
                 IReadOnlyList<CkanModule>? candidates = null;
                 try
                 {
-                    candidates = resolved.Candidates(descriptor, modlist.Values,
+                    candidates = resolved.Candidates(descriptor,
+                                                     options.get_recommenders
+                                                         ? Dependencies().ToArray()
+                                                         : modlist.Values,
                                                      registry, game);
                     log.DebugFormat("Got {0} candidates for {1}",
                                     candidates.Count, descriptor);
@@ -338,7 +341,10 @@ private void ResolveStanza(List<RelationshipDescriptor>?        stanza,
                 // Finally, check our candidate against everything which might object
                 // to it being installed; that's all the mods which are fixed in our
                 // list thus far, as well as everything on the system.
-                var fixed_mods = modlist.Values.Concat(installed_modules).ToHashSet();
+                var fixed_mods = (options.get_recommenders ? Dependencies()
+                                                           : modlist.Values)
+                                     .Concat(installed_modules)
+                                     .ToHashSet();
 
                 var conflicting_mod = fixed_mods.FirstOrDefault(mod => mod.ConflictsWith(candidate));
                 if (conflicting_mod == null)
diff --git a/GUI/Controls/InstallationHistory.Designer.cs b/GUI/Controls/InstallationHistory.Designer.cs
index bff892bc1..0e9680bc5 100644
--- a/GUI/Controls/InstallationHistory.Designer.cs
+++ b/GUI/Controls/InstallationHistory.Designer.cs
@@ -69,7 +69,7 @@ private void InitializeComponent()
             //
             // InstallButton
             //
-            //this.InstallButton.Image = global::CKAN.GUI.Properties.Resources.install;
+            this.InstallButton.Image = global::CKAN.GUI.EmbeddedImages.apply;
             this.InstallButton.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None;
             this.InstallButton.Name = "InstallButton";
             this.InstallButton.Size = new System.Drawing.Size(114, 56);


Test patch:
diff --git a/Tests/Core/IO/ModuleInstallerTests.cs b/Tests/Core/IO/ModuleInstallerTests.cs
index d7e5e99da..4e94ebbc8 100644
--- a/Tests/Core/IO/ModuleInstallerTests.cs
+++ b/Tests/Core/IO/ModuleInstallerTests.cs
@@ -974,6 +974,55 @@ public void FindRecommendations_ReplaceParallaxWithParallaxContinued_1ThousandRe
             }
         }
 
+        [Test]
+        public void FindRecommendations_WithConflictingRecommendations_FindsAll()
+        {
+            // Arrange
+            var user            = new NullUser();
+            var modGen          = new RandomModuleGenerator(new Random());
+            var recommendation1 = modGen.GenerateRandomModule();
+            var recommendation2 = modGen.GenerateRandomModule(conflicts: new List<RelationshipDescriptor>()
+            {
+                new ModuleRelationshipDescriptor() { name = recommendation1.identifier },
+            });
+            var recommendation3 = modGen.GenerateRandomModule(conflicts: new List<RelationshipDescriptor>()
+            {
+                new ModuleRelationshipDescriptor() { name = recommendation1.identifier },
+                new ModuleRelationshipDescriptor() { name = recommendation2.identifier },
+            });
+            var recommender = modGen.GenerateRandomModule(recommends: new List<RelationshipDescriptor>()
+            {
+                new ModuleRelationshipDescriptor() { name = recommendation3.identifier },
+                new ModuleRelationshipDescriptor() { name = recommendation1.identifier },
+                new ModuleRelationshipDescriptor() { name = recommendation2.identifier },
+            });
+            using (var inst = new DisposableKSP())
+            using (var repo = new TemporaryRepository(recommendation1.ToJson(),
+                                                      recommendation2.ToJson(),
+                                                      recommendation3.ToJson(),
+                                                      recommender.ToJson()))
+            using (var repoData = new TemporaryRepositoryData(user, repo.repo))
+            {
+                var registry = new CKAN.Registry(repoData.Manager, repo.repo);
+
+                // Act
+                var result = ModuleInstaller.FindRecommendations(inst.KSP,
+                                                                 new CkanModule[] { recommender },
+                                                                 new CkanModule[] { recommender },
+                                                                 new CkanModule[] { },
+                                                                 new CkanModule[] { },
+                                                                 registry,
+                                                                 out var recs,
+                                                                 out var sugs,
+                                                                 out var sups);
+
+                // Assert
+                Assert.IsTrue(result);
+                CollectionAssert.AreEquivalent(new CkanModule[] { recommendation1, recommendation2, recommendation3 },
+                                               recs.Keys);
+            }
+        }
+
         [TestCase(new string[]
                   {
                       @"{
diff --git a/Tests/Data/TestData.cs b/Tests/Data/TestData.cs
index 99b4382cc..68294f78f 100644
--- a/Tests/Data/TestData.cs
+++ b/Tests/Data/TestData.cs
@@ -1004,6 +1004,7 @@ public CkanModule GenerateRandomModule(
             GameVersion?                  ksp_version = null,
             List<RelationshipDescriptor>? conflicts   = null,
             List<RelationshipDescriptor>? depends     = null,
+            List<RelationshipDescriptor>? recommends  = null,
             List<RelationshipDescriptor>? suggests    = null,
             List<string>?                 provides    = null,
             string?                       identifier  = null,
@@ -1021,6 +1022,7 @@ public CkanModule GenerateRandomModule(
                 ksp_version = ksp_version,
                 conflicts   = conflicts,
                 depends     = depends,
+                recommends  = recommends,
                 suggests    = suggests,
                 provides    = provides,
             };