Skip to content

Conversation

@RalucaP
Copy link
Contributor

@RalucaP RalucaP commented Nov 10, 2025

Bug/issue: rdar://135837611

Summary

This PR addresses an issue where API Collections were displaying incorrect icons in external references. The issue occurred when external references to API Collections were rendering with "role": "article" instead of "role": "collectionGroup", causing them to display article icons instead of the proper collection icons.

The implementation adds the missing .collectionGroup case to the renderKindAndRole function to ensure API Collections render consistently as articles with the collectionGroup role, displaying the correct collection icon in both main documents and external references.

Dependencies

None.

Testing

The functionality can be tested using the provided test case in DocumentationContentRendererTests.swift:

func testRenderKindAndRoleForAPICollection() throws {
    // API Collections should render as articles with collectionGroup role to display correct icon
    let (collectionKind, collectionRole) = DocumentationContentRenderer.renderKindAndRole(.collectionGroup, semantic: nil)
    XCTAssertEqual(collectionRole, "collectionGroup", "API Collections should have collectionGroup role for correct icon")
    
    // Verify other node types still work correctly
    let (articleKind, articleRole) = DocumentationContentRenderer.renderKindAndRole(.article, semantic: nil)
    XCTAssertEqual(articleKind, RenderNode.Kind.article)
    XCTAssertEqual(articleRole, "article")
    
    let (symbolKind, symbolRole) = DocumentationContentRenderer.renderKindAndRole(.class, semantic: nil)
    XCTAssertEqual(symbolKind, RenderNode.Kind.symbol)
    XCTAssertEqual(symbolRole, "symbol")
}

Checklist

  • Updated tests
  • Ran the ./bin/test script and it succeeded
  • Updated documentation if necessary

Copy link
Contributor

@patshaughnessy patshaughnessy left a comment

Choose a reason for hiding this comment

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

This change looks good.

There are many different DocumentationNode.Kind values... have we tested if any other icons are incorrect?

@RalucaP RalucaP force-pushed the fix-api-collection-icon-rendering branch from 9213698 to b385a24 Compare November 12, 2025 15:37
@RalucaP
Copy link
Contributor Author

RalucaP commented Nov 12, 2025

Hi Pat, I made some changes to address the radar concerns for cross-framework API collection icons, can you please take another look?

There are many different DocumentationNode.Kind values... have we tested if any other icons are incorrect?

This radar is specific for API Collections, if more icons appear incorrect, we should fix them too, but probably in different PRs. :) Thanks!

Copy link
Contributor

@patshaughnessy patshaughnessy left a comment

Choose a reason for hiding this comment

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

Some minor suggestions. Thanks for the fix!

Copy link
Contributor

@d-ronnqvist d-ronnqvist left a comment

Choose a reason for hiding this comment

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

We can't rely on the task groups information for this. Is there some other information that indicates that a external page is an API collection?

/// Create a topic render render reference for this link summary and its content variants.
func makeTopicRenderReference() -> TopicRenderReference {
let (kind, role) = DocumentationContentRenderer.renderKindAndRole(kind, semantic: nil)
let (kind, role) = DocumentationContentRenderer.renderKindAndRole(kind, semantic: nil, linkSummary: self)
Copy link
Contributor

Choose a reason for hiding this comment

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

Question: Is there some other value about the link summary that we can use to determine if it's an API collection?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After analyzing the LinkDestinationSummary structure and the actual Apple documentation data, I found that there are no reliable alternatives to taskGroups for identifying API Collections in external references. The available fields (title patterns, URL patterns, abstract content, references) would all be fragile heuristics.
We could add an optional role to LinkDestinationSummary, but that would be a much larger change. Would you prefer that?

Copy link
Contributor

Choose a reason for hiding this comment

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

The taskGroup information isn't a reliable source for this information. It's not requited for documentation sources to include it and we are also talking about removing it because the link summary isn't meant to be a representation of the documentation hierarchy.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is there another DocumentationNode.Kind value that API collection pages should be using? I see that we have both collection and collectionGroup but I don't recall what either of those are meant to represent. If not, should we introduce a dedicated "API Collection" kind and use that?

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe collection is the root/framework page, and collectionGroup are API Collections or pages that groups symbol pages together.

Copy link
Contributor

Choose a reason for hiding this comment

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

Reading the kind values documentation I would think that a framework would use module. Regardless, if we have a kind value that represents API collections then it sounds like we should use that to identify API collections.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you, both! I spoke with @d-ronnqvist and ended up taking a different approach, let me know your thoughts. :)

@RalucaP RalucaP force-pushed the fix-api-collection-icon-rendering branch from b385a24 to c7d498f Compare November 20, 2025 17:00
@RalucaP RalucaP requested a review from d-ronnqvist November 20, 2025 17:01
@RalucaP RalucaP force-pushed the fix-api-collection-icon-rendering branch from c7d498f to b582adb Compare November 24, 2025 12:17
Copy link
Contributor

@d-ronnqvist d-ronnqvist left a comment

Choose a reason for hiding this comment

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

This looks like a good solution to me.

I'm curious how the code would behave if the API Collection specified an explicit "Article" kind using

@Metadata {
  @PageKind(article)
}

I feel that an explicit page kind like that should override any default kind inferred from the page's content. I think that maybe the new code already works like this, but it would be good to have a second test that verifies that behavior.

(none of the individual questions or minor feedback are blocking)

Comment on lines 874 to 877
// Verify round-trip encoding preserves the correct kind
let encoded = try JSONEncoder().encode(pageSummary)
let decoded = try JSONDecoder().decode(LinkDestinationSummary.self, from: encoded)
XCTAssertEqual(decoded.kind, .collectionGroup)
Copy link
Contributor

Choose a reason for hiding this comment

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

minor: We have a test helper that does this

Suggested change
// Verify round-trip encoding preserves the correct kind
let encoded = try JSONEncoder().encode(pageSummary)
let decoded = try JSONDecoder().decode(LinkDestinationSummary.self, from: encoded)
XCTAssertEqual(decoded.kind, .collectionGroup)
try assertRoundTripCoding(pageSummary)

let renderNode = converter.convert(node)

let summaries = node.externallyLinkableElementSummaries(context: context, renderNode: renderNode)
let pageSummary = summaries[0]
Copy link
Contributor

Choose a reason for hiding this comment

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

minor: If this page didn't produce any summaries, then line would trap and stop running all the other tests. If you use an optional expression that's wrapped in XCTUnwrap, then if the code would behave unexpectedly, the failure would be limited to this test, and the rest would continue to run.

Suggested change
let pageSummary = summaries[0]
let pageSummary = try XCTUnwrap(summaries.first)


let catalogHierarchy = Folder(name: "unit-test.docc", content: [
TextFile(name: "APICollection.md", utf8Content: """
# Time Pitch Algorithm Settings
Copy link
Contributor

Choose a reason for hiding this comment

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

minor: This title is oddly specific for a test

Suggested change
# Time Pitch Algorithm Settings
# Some API collection

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated! Changed from specific 'Time Pitch Algorithm Settings' to generic 'API Collection' title to avoid confusion with real-world examples.

## Topics
### Algorithms
Copy link
Contributor

Choose a reason for hiding this comment

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

minor: This topic name doesn't match the symbol that it's curating.

Because the API collection only has a single topic section, you can probably remove its title if we don't have a better title for it.

Suggested change
### Algorithms

- ``TestModule/TestClass``
"""),
JSONFile(name: "TestModule.symbols.json", content: symbolGraph),
InfoPlist(displayName: "TestBundle", identifier: "com.test.example")
Copy link
Contributor

Choose a reason for hiding this comment

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

Question: is it intentional that the Info.plist file specifies a name that's different from the module name? That's a bit unusual (but completely valid).

I'm asking because catalogs generally don't need an Info.plist file except that in this test you're hardcoding its name below when creating the reference, so removing it would cause the API collection to not be found for that reference string.

Copy link
Contributor Author

@RalucaP RalucaP Nov 25, 2025

Choose a reason for hiding this comment

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

I believe this is correct! 'TestBundle' (display name) vs 'TestModule' (module name) should follow the convention for testing cross-module references.

Copy link
Contributor

Choose a reason for hiding this comment

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

This test doesn't verify any cross-module linking behaviors and even then it's uncommon and a bit unconventional to specify a display name in the Info.plist that's different from the module name. It's more common for documentation to either explicitly specify the same display name as the module name (often redundantly) or to not have an Info.plist at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done! Removed the Info.plist from both test methods since they focus on API Collection kind detection rather than cross-module linking. Thanks!

@RalucaP RalucaP force-pushed the fix-api-collection-icon-rendering branch from b582adb to 83bba6d Compare November 25, 2025 12:39
@RalucaP
Copy link
Contributor Author

RalucaP commented Nov 25, 2025

Thanks for the thorough review, @d-ronnqvist ! All feedback addressed with improved test quality, better error handling, and added regression test for @PageKind precedence. :)
The fix maintains backward compatibility while correctly identifying API Collections in external references.

I'm curious how the code would behave if the API Collection specified an explicit "Article" kind using

@Metadata {
  @PageKind(article)
}

Good catch! Added testExplicitPageKindOverridesAPICollectionDetection() to verify explicit @PageKind(article) takes precedence over automatic detection. The existing precedence logic already handles this correctly.

@RalucaP RalucaP requested a review from d-ronnqvist November 25, 2025 12:46
Adds tests to verify that API Collections (articles with Topics sections) are correctly identified as .collectionGroup kind in linkable entities, ensuring cross-framework references display the correct icon. Includes regression test for explicit @pagekind(article) metadata override behavior to ensure the fix doesn't break existing functionality. The first test is temporarily skipped until the fix is implemented in the next commit.

rdar://135837611
Ensures API Collections are correctly identified as collectionGroup kind in linkable entities by using DocumentationContentRenderer.roleForArticle logic. This fixes cross-framework references where API Collections were incorrectly showing Article icons instead of Collection Group icons.

rdar://135837611
@RalucaP RalucaP force-pushed the fix-api-collection-icon-rendering branch from 83bba6d to 83cf898 Compare November 25, 2025 16:16
@d-ronnqvist
Copy link
Contributor

@swift-ci please test

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.

4 participants