Skip to content

feat(cli): propagate display names for x-fern-sdk-group-name hierarchy levels#13300

Open
dsinghvi wants to merge 3 commits intomainfrom
devin/1773174589-sdk-group-name-display-names
Open

feat(cli): propagate display names for x-fern-sdk-group-name hierarchy levels#13300
dsinghvi wants to merge 3 commits intomainfrom
devin/1773174589-sdk-group-name-display-names

Conversation

@dsinghvi
Copy link
Member

@dsinghvi dsinghvi commented Mar 10, 2026

Description

Refs: Companion to https://github.com/fern-api/fern-platform/pull/8304

When x-fern-sdk-group-name is set with human-readable names (e.g., ["Tableau APIs", "Tableau REST API", "Analytics Extensions Settings Methods"]), the original group names are now preserved as displayName on each subpackage in the IR. Previously, group names were camelCased for internal use but the original human-readable form was lost, resulting in flat/ugly hierarchy names in the generated API reference.

Changes Made

  • Added groupDisplayNames?: string[] to GroupNameAndLocation and AbstractOperationConverter.Output
  • AbstractOperationConverter: captures the raw x-fern-sdk-group-name array values as groupDisplayNames
  • OperationConverter / OpenAPIConverter: threads groupDisplayNames through to addEndpoint
  • AbstractSpecConverter.getOrCreatePackage: accepts groupDisplayNames and sets displayName on each subpackage in the hierarchy
  • Added test fixture: deeply nested x-fern-sdk-group-name with space-containing names (Tableau APIs example)
  • Updated all affected snapshots (v3-importer-tests + register package)

Testing

  • Unit tests added (new x-fern-sdk-group-name fixture case with 3-level hierarchy)
  • All snapshot tests pass (v3-importer-tests + register package)
  • biome check passes
  • All required CI checks pass

Updates Since Last Revision

  • Fixed snapshot mismatch in register package (auth-with-overrides-fdr.snap and auth-with-overrides-ir.snap) where the displayName for the users subpackage changed from undefined to "users" due to the new logic.

Human Review Checklist

  • displayName set for all x-fern-sdk-group-name groups: The current logic sets displayName for ALL endpoints using x-fern-sdk-group-name, even when the group name is already camelCase-like (e.g., "displayName": "chatCompletions" in streaming fixtures). This may cause unintended rendering changes downstream. Consider whether displayName should only be set when the original name contains spaces or differs significantly from its camelCased form.
  • Unintended snapshot changes: Several baseline-sdk snapshots (anyof-const-query-parameter, errors-complex, errors-simple, url-reference) have large structural changes (new enum types, modified union representations) that appear unrelated to display names. These look like stale snapshots that got regenerated. Verify these changes are acceptable or revert them.
  • Display name precedence: If multiple endpoints provide different display names for the same group path, the first encountered non-null value wins. Confirm this is the intended behavior.

Link to Devin Session
Requested by: @dsinghvi

Co-Authored-By: Deep Singhvi <deep@buildwithfern.com>
@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

devin-ai-integration bot and others added 2 commits March 10, 2026 20:53
Co-Authored-By: Deep Singhvi <deep@buildwithfern.com>
Co-Authored-By: Deep Singhvi <deep@buildwithfern.com>
@devin-ai-integration devin-ai-integration bot changed the title feat: propagate display names for x-fern-sdk-group-name hierarchy levels feat(cli): propagate display names for x-fern-sdk-group-name hierarchy levels Mar 10, 2026
Comment on lines 263 to +265
if (method != null) {
return { group, method };
// Use the original group names as display names when they differ from their camelCased versions
const groupDisplayNames = group.length > 0 ? group : undefined;
Copy link

Choose a reason for hiding this comment

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

🟡 The comment on line 264 says "Use the original group names as display names when they differ from their camelCased versions" but line 265 unconditionally sets groupDisplayNames for ALL non-empty groups without comparing against camelCase(group[i]). This causes every subpackage from x-fern-sdk-group-name to get a redundant displayName even when it matches the internal name (e.g., displayName: "chatCompletions", displayName: "user", displayName: "auth"). The fix should filter: only include group[i] in groupDisplayNames when group[i] !== camelCase(group[i]).

Extended reasoning...

What the bug is

In AbstractOperationConverter.computeGroupNameAndLocationFromExtensions() at lines 264-265, there is a mismatch between the comment and the implementation:

// Use the original group names as display names when they differ from their camelCased versions
const groupDisplayNames = group.length > 0 ? group : undefined;

The comment explicitly states the intent: display names should only be set when they differ from their camelCased versions. However, the implementation simply checks if the group array is non-empty and passes the entire array as display names unconditionally.

How it manifests

Downstream in AbstractSpecConverter.getOrCreatePackage() (line 593), displayNameParts[i] is directly assigned as the displayName for each subpackage. Since groupDisplayNames is always set for non-empty groups, every subpackage created from an x-fern-sdk-group-name extension now receives a displayName, even when the display name is identical to the internal camelCased name.

This is confirmed by snapshot changes across many test fixtures:

  • x-fern-streaming-with-reference.json: displayName: "chatCompletions" (same as camelCase)
  • x-fern-global-headers.json: displayName: "user" (same as camelCase)
  • oauth.json: displayName: "auth" (same as camelCase)
  • env-exhaustive-multi-multi.json: displayName: "alpha", displayName: "beta" (same as camelCase)
  • streaming-sse-inferred.json: displayName: "completions" (same as camelCase)

Step-by-step proof

Consider an OpenAPI spec with x-fern-sdk-group-name: ["user"]:

  1. groupNameExtension.convert() returns groups: ["user"]
  2. group.length > 0 is true, so groupDisplayNames = ["user"]
  3. This flows through OperationConverterOpenAPIConverter.addEndpointToIr()AbstractSpecConverter.getOrCreatePackage()
  4. In getOrCreatePackage, camelCase("user") produces "user" as the internal name
  5. displayNameParts[i] is "user" (from groupDisplayNames), so the subpackage gets displayName: "user"
  6. But "user" === camelCase("user"), so this displayName adds no information — it should have been undefined

Contrast with the intended case like x-fern-sdk-group-name: ["Tableau APIs"]:

  1. camelCase("Tableau APIs")"tableauApIs"
  2. "Tableau APIs" !== "tableauApIs", so displayName: "Tableau APIs" is meaningful and correct

Impact

Consumer code (e.g., API reference generators) may interpret the presence of a displayName as indicating a human-readable override is needed. Setting redundant display names on every subpackage could cause unintended rendering changes downstream — for example, displaying "chatCompletions" as a label instead of applying the consumer's own formatting to the internal name.

Fix

The fix should compare each group element against its camelCased form and only include it when they differ:

const groupDisplayNames = group.length > 0
    ? group.map((g) => g !== camelCase(g) ? g : undefined)
    : undefined;

Then only set groupDisplayNames to the array if at least one element is non-undefined, or keep it simple and let the downstream code handle undefined entries (which it already does via displayNameParts[i] != null checks in getOrCreatePackage).

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant