From 9660364b56cfec2e94af3777a2ea876265b81db6 Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:31:34 -0700 Subject: [PATCH 1/7] fix(repo): backfill JSDoc on inline object literals lost via Omit/Pick MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When TypeScript resolves a type through `Omit<…>` / `Pick<…>`, inline anonymous object literal property reflections get re-synthesized and TypeDoc only retains the leading property's JSDoc — the rest come through with `comment === undefined`. The same shape elsewhere in the project (e.g. on the un-resolved type) carries all comments correctly. Group `TypeLiteral` reflections by structural fingerprint (sorted `(name, type, optional)` tuples of property children) at `RendererEvent.BEGIN`; within each group, copy missing comments from the most-commented sibling onto the others. Surfaced concretely as the empty descriptions on `telemetry?.debug?` and `telemetry?.perEventSampling?` in `react/clerk-provider-props.mdx`, while the sibling at `shared/clerk-options.mdx` (rendered via the parallel union-arm path) had all three. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...-typedoc-inline-object-comment-backfill.md | 2 + .typedoc/custom-plugin.mjs | 118 ++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 .changeset/fix-typedoc-inline-object-comment-backfill.md diff --git a/.changeset/fix-typedoc-inline-object-comment-backfill.md b/.changeset/fix-typedoc-inline-object-comment-backfill.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/fix-typedoc-inline-object-comment-backfill.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.typedoc/custom-plugin.mjs b/.typedoc/custom-plugin.mjs index 53d3b774ce3..a700233a246 100644 --- a/.typedoc/custom-plugin.mjs +++ b/.typedoc/custom-plugin.mjs @@ -138,6 +138,18 @@ const LINK_REPLACEMENTS = [ ], ['create-organization-domain-params', '#create-organization-domain-params'], ['organization-domain-verification', '/docs/reference/types/organization-domain-resource'], + [ + 'prepare-affiliation-verification-params', + '/docs/reference/types/organization-domain-resource#prepare-affiliation-verification-params', + ], + [ + 'attempt-affiliation-verification-params', + '/docs/reference/types/organization-domain-resource#attempt-affiliation-verification-params', + ], + [ + 'organization-domain-ownership-verification', + '/docs/reference/types/organization-domain-resource#organization-domain-verification', + ], ]; /** @@ -509,6 +521,110 @@ function collectPropertiesFromType(type, reflectionsByName) { return []; } +/** + * Cheap structural fingerprint for a `Type`. One-level deep — enough to disambiguate + * inline shapes used in different positions while staying fast on large projects. Two + * shapes that produce the same fingerprint are treated as structurally identical. + * + * @param {import('typedoc').SomeType | undefined} type + * @returns {string} + */ +function typeFingerprint(type) { + if (!type) return '?'; + const t = + /** @type {{ type?: string; name?: string; value?: unknown; elementType?: import('typedoc').SomeType; types?: import('typedoc').SomeType[]; declaration?: import('typedoc').DeclarationReflection }} */ ( + /** @type {unknown} */ (type) + ); + switch (t.type) { + case 'intrinsic': + return `i:${t.name ?? ''}`; + case 'literal': + return `l:${JSON.stringify(t.value)}`; + case 'reference': + return `r:${t.name ?? ''}`; + case 'array': + return `a:${typeFingerprint(t.elementType)}`; + case 'optional': + return `o:${typeFingerprint(t.elementType)}`; + case 'union': + return `u:[${(t.types ?? []).map(typeFingerprint).sort().join(',')}]`; + case 'intersection': + return `n:[${(t.types ?? []).map(typeFingerprint).sort().join(',')}]`; + case 'reflection': { + const kids = t.declaration?.children?.filter(c => c.kindOf?.(ReflectionKind.Property)) ?? []; + return `rf:[${kids + .map(c => `${c.name}${c.flags?.isOptional ? '?' : ''}`) + .sort() + .join(',')}]`; + } + default: + return t.type ?? '?'; + } +} + +/** + * When TypeScript resolves a type through `Omit<...>` / `Pick<...>` (e.g. + * `ClerkProviderProps = Omit & { … }`), inline anonymous + * object literal property types get re-synthesized — and TypeDoc loses the JSDoc on + * most of their members. Only the first/leading property's comment survives, the + * rest come through with `comment === undefined`. The same shape elsewhere in the + * project (e.g. directly under `IsomorphicClerkOptions['telemetry']`) carries all + * comments correctly. + * + * Match `@kind:typeLiteral` reflections by structural fingerprint (set of + * `(name, type, optional)` tuples on their property children); within each group, + * pick the reflection with the most commented children as the source-of-truth and + * copy missing comments onto its siblings. + * + * @param {import('typedoc').Reflection[]} all + */ +function backfillInlineObjectChildComments(all) { + /** @type {Map} */ + const groups = new Map(); + for (const r of all) { + if (!r.kindOf?.(ReflectionKind.TypeLiteral)) continue; + const decl = /** @type {import('typedoc').DeclarationReflection} */ (r); + const propChildren = decl.children?.filter(c => c.kindOf?.(ReflectionKind.Property)); + if (!propChildren?.length) continue; + const key = propChildren + .map(c => `${c.name}${c.flags?.isOptional ? '?' : ''}:${typeFingerprint(c.type)}`) + .sort() + .join('|'); + if (!groups.has(key)) groups.set(key, []); + /** @type {import('typedoc').DeclarationReflection[]} */ (groups.get(key)).push(decl); + } + + for (const group of groups.values()) { + if (group.length < 2) continue; + /** @type {import('typedoc').DeclarationReflection | null} */ + let best = null; + let bestScore = -1; + for (const refl of group) { + const score = + refl.children?.filter(c => c.kindOf?.(ReflectionKind.Property) && c.comment?.summary?.length).length ?? 0; + if (score > bestScore) { + best = refl; + bestScore = score; + } + } + if (!best || bestScore === 0) continue; + /** @type {Map} */ + const bestByName = new Map(); + for (const c of best.children ?? []) { + if (c.kindOf?.(ReflectionKind.Property)) bestByName.set(c.name, c); + } + for (const refl of group) { + if (refl === best) continue; + for (const child of refl.children ?? []) { + if (!child.kindOf?.(ReflectionKind.Property)) continue; + if (child.comment?.summary?.length) continue; + const src = bestByName.get(child.name); + if (src?.comment) child.comment = src.comment; + } + } + } +} + /** * @param {import('typedoc-plugin-markdown').MarkdownApplication} app */ @@ -565,6 +681,8 @@ export function load(app) { } } } + + backfillInlineObjectChildComments(all); }); app.renderer.on(MarkdownPageEvent.END, output => { From baa7de8c69198ef5a62e513756490040aea08254 Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:40:13 -0700 Subject: [PATCH 2/7] update typedocs for billing and organizationdomain --- packages/shared/src/types/billing.ts | 20 +++++++++++++------ .../shared/src/types/organizationDomain.ts | 8 ++++---- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/packages/shared/src/types/billing.ts b/packages/shared/src/types/billing.ts index 724fa60286f..f408aa419c8 100644 --- a/packages/shared/src/types/billing.ts +++ b/packages/shared/src/types/billing.ts @@ -87,6 +87,7 @@ export interface BillingNamespace { /** * Gets the credit balance for the current payer. + * @returns A [`BillingCreditBalanceResource`](https://clerk.com/docs/reference/types/billing-credit-balance-resource) object. * * @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change. It is advised to [pin](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes. */ @@ -94,6 +95,7 @@ export interface BillingNamespace { /** * Gets the credit history for the current payer. + * @returns A [`ClerkPaginatedResponse`](https://clerk.com/docs/reference/types/clerk-paginated-response) of [`BillingCreditLedgerResource`](https://clerk.com/docs/reference/types/billing-credit-ledger-resource) objects. * * @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change. It is advised to [pin](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes. */ @@ -913,19 +915,19 @@ export interface BillingSubscriptionResource extends ClerkResource { } /** + * The `BillingCreditBalanceResource` type represents the credit balance for a payer. * @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change. It is advised to [pin](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes. */ export interface BillingCreditBalanceResource { - /** - * The balance of the credit. - */ + /** The balance of the credit. */ balance: BillingMoneyAmount | null; } +/** + * @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change. It is advised to [pin](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes. + */ export type GetCreditBalanceParams = { - /** - * The ID of the Organization to get the credit balance for. - */ + /** The ID of the Organization to get the credit balance for. */ orgId?: string; }; @@ -940,13 +942,19 @@ export type GetCreditHistoryParams = { }; /** + * The `BillingCreditLedgerResource` type represents a credit ledger entry for a payer. * @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change. It is advised to [pin](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes. */ export interface BillingCreditLedgerResource { + /** The ID of the credit ledger entry. */ id: string; + /** The amount of the credit ledger entry. */ amount: BillingMoneyAmount; + /** The type of the source of the credit ledger entry. */ sourceType: string; + /** The ID of the source of the credit ledger entry. */ sourceId: string; + /** The date when the credit ledger entry was created. */ createdAt: Date; } diff --git a/packages/shared/src/types/organizationDomain.ts b/packages/shared/src/types/organizationDomain.ts index eeca290333e..e5a48634084 100644 --- a/packages/shared/src/types/organizationDomain.ts +++ b/packages/shared/src/types/organizationDomain.ts @@ -173,13 +173,13 @@ export interface OrganizationDomainResource extends ClerkResource { totalPendingSuggestions: number; /** * Begins the verification process of a created Organization domain by sending a verification code to the provided email address. - * @returns The updated [`OrganizationDomainResource`](https://clerk.com/docs/nextjs/reference/types/organization-domain-resource) object. + * @returns The updated [`OrganizationDomainResource`](https://clerk.com/docs/reference/types/organization-domain-resource) object. */ prepareAffiliationVerification: (params: PrepareAffiliationVerificationParams) => Promise; /** - * Completes the verification process started by [`prepareAffiliationVerification()`](https://clerk.com/docs/nextjs/reference/types/organization-domain-resource#prepare-affiliation-verification), by validating the provided verification code. - * @returns The updated [`OrganizationDomainResource`](https://clerk.com/docs/nextjs/reference/types/organization-domain-resource) object. + * Completes the verification process started by [`prepareAffiliationVerification()`](https://clerk.com/docs/reference/types/organization-domain-resource#prepare-affiliation-verification), by validating the provided verification code. + * @returns The updated [`OrganizationDomainResource`](https://clerk.com/docs/reference/types/organization-domain-resource) object. */ attemptAffiliationVerification: (params: AttemptAffiliationVerificationParams) => Promise; /** @@ -189,7 +189,7 @@ export interface OrganizationDomainResource extends ClerkResource { delete: () => Promise; /** * Updates the enrollment mode of the Verified Domain. - * @returns The updated [`OrganizationDomainResource`](https://clerk.com/docs/nextjs/reference/types/organization-domain-resource) object. + * @returns The updated [`OrganizationDomainResource`](https://clerk.com/docs/reference/types/organization-domain-resource) object. */ updateEnrollmentMode: (params: UpdateEnrollmentModeParams) => Promise; } From 2e5b7dd0ff88bb38095896dd9f0fc3e95b12031a Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:43:21 -0700 Subject: [PATCH 3/7] remove custom plugin updates --- .typedoc/custom-plugin.mjs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.typedoc/custom-plugin.mjs b/.typedoc/custom-plugin.mjs index a700233a246..e6155b51bc5 100644 --- a/.typedoc/custom-plugin.mjs +++ b/.typedoc/custom-plugin.mjs @@ -138,18 +138,6 @@ const LINK_REPLACEMENTS = [ ], ['create-organization-domain-params', '#create-organization-domain-params'], ['organization-domain-verification', '/docs/reference/types/organization-domain-resource'], - [ - 'prepare-affiliation-verification-params', - '/docs/reference/types/organization-domain-resource#prepare-affiliation-verification-params', - ], - [ - 'attempt-affiliation-verification-params', - '/docs/reference/types/organization-domain-resource#attempt-affiliation-verification-params', - ], - [ - 'organization-domain-ownership-verification', - '/docs/reference/types/organization-domain-resource#organization-domain-verification', - ], ]; /** From 1d04b80b2ba88eab93c441a3f972da781f5feb4b Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Tue, 30 Jun 2026 14:58:48 -0700 Subject: [PATCH 4/7] fix formatting --- .typedoc/custom-plugin.mjs | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/.typedoc/custom-plugin.mjs b/.typedoc/custom-plugin.mjs index e6155b51bc5..92b767794f0 100644 --- a/.typedoc/custom-plugin.mjs +++ b/.typedoc/custom-plugin.mjs @@ -510,9 +510,7 @@ function collectPropertiesFromType(type, reflectionsByName) { } /** - * Cheap structural fingerprint for a `Type`. One-level deep — enough to disambiguate - * inline shapes used in different positions while staying fast on large projects. Two - * shapes that produce the same fingerprint are treated as structurally identical. + * Cheap structural fingerprint for a `Type`. One-level deep — enough to disambiguate inline shapes used in different positions while staying fast on large projects. Two shapes that produce the same fingerprint are treated as structurally identical. * * @param {import('typedoc').SomeType | undefined} type * @returns {string} @@ -551,18 +549,9 @@ function typeFingerprint(type) { } /** - * When TypeScript resolves a type through `Omit<...>` / `Pick<...>` (e.g. - * `ClerkProviderProps = Omit & { … }`), inline anonymous - * object literal property types get re-synthesized — and TypeDoc loses the JSDoc on - * most of their members. Only the first/leading property's comment survives, the - * rest come through with `comment === undefined`. The same shape elsewhere in the - * project (e.g. directly under `IsomorphicClerkOptions['telemetry']`) carries all - * comments correctly. + * When TypeScript resolves a type through `Omit<...>` / `Pick<...>` (e.g. `ClerkProviderProps = Omit & { … }`), inline anonymous object literal property types get re-synthesized — and TypeDoc loses the JSDoc on most of their members. Only the first/leading property's comment survives, the rest come through with `comment === undefined`. The same shape elsewhere in the project (e.g. directly under `IsomorphicClerkOptions['telemetry']`) carries all comments correctly. * - * Match `@kind:typeLiteral` reflections by structural fingerprint (set of - * `(name, type, optional)` tuples on their property children); within each group, - * pick the reflection with the most commented children as the source-of-truth and - * copy missing comments onto its siblings. + * Match `@kind:typeLiteral` reflections by structural fingerprint (set of `(name, type, optional)` tuples on their property children); within each group, pick the reflection with the most commented children as the source-of-truth and copy missing comments onto its siblings. * * @param {import('typedoc').Reflection[]} all */ From 4a865b332dbf5855ebee6a87509e7320bffd8c3a Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Tue, 30 Jun 2026 15:07:19 -0700 Subject: [PATCH 5/7] small copy update --- packages/shared/src/types/billing.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared/src/types/billing.ts b/packages/shared/src/types/billing.ts index f408aa419c8..1db27d37140 100644 --- a/packages/shared/src/types/billing.ts +++ b/packages/shared/src/types/billing.ts @@ -942,7 +942,7 @@ export type GetCreditHistoryParams = { }; /** - * The `BillingCreditLedgerResource` type represents a credit ledger entry for a payer. + * The `BillingCreditLedgerResource` type represents a credit ledger entry for the current payer or given Organization. * @experimental This is an experimental API for the Billing feature that is available under a public beta, and the API is subject to change. It is advised to [pin](https://clerk.com/docs/pinning) the SDK version and the clerk-js version to avoid breaking changes. */ export interface BillingCreditLedgerResource { From 5e7840d949492fecf527776fc33bd5e405386a11 Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Tue, 30 Jun 2026 15:48:18 -0700 Subject: [PATCH 6/7] fix(repo): preserve intentionally-empty inline-object comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tighten the backfill guard so `@generateWithEmptyComment` survivors keep their empty descriptions instead of being overwritten with sibling JSDoc. Treat any `.comment` object as user intent — only fill children with no comment at all, and only from sources whose summary is non-empty. Co-Authored-By: Claude Opus 4.7 (1M context) --- .typedoc/custom-plugin.mjs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.typedoc/custom-plugin.mjs b/.typedoc/custom-plugin.mjs index 92b767794f0..aa806ad3e87 100644 --- a/.typedoc/custom-plugin.mjs +++ b/.typedoc/custom-plugin.mjs @@ -594,9 +594,13 @@ function backfillInlineObjectChildComments(all) { if (refl === best) continue; for (const child of refl.children ?? []) { if (!child.kindOf?.(ReflectionKind.Property)) continue; - if (child.comment?.summary?.length) continue; + // Any `.comment` object means TypeDoc found a JSDoc block at the source — including + // intentionally empty comments left over from `@generateWithEmptyComment` after the + // modifier tag is stripped in `EVENT_RESOLVE_END`. Only fill in children that have + // no comment at all. + if (child.comment) continue; const src = bestByName.get(child.name); - if (src?.comment) child.comment = src.comment; + if (src?.comment?.summary?.length) child.comment = src.comment; } } } From 8b9da58f52be441c50395d6a7b27e47b8dbb836b Mon Sep 17 00:00:00 2001 From: Alexis Aguilar <98043211+alexisintech@users.noreply.github.com> Date: Tue, 30 Jun 2026 15:49:49 -0700 Subject: [PATCH 7/7] fix(repo): deepen type fingerprint to disambiguate generic and nested shapes Reference fingerprints now include type arguments and reflection fingerprints recurse into each property's type, so `Foo` vs `Foo` and `{ x: string }` vs `{ x: number }` no longer collide. Threads a shared `Set` of visited reflection ids through the recursion to guard against cycles. Co-Authored-By: Claude Opus 4.7 (1M context) --- .typedoc/custom-plugin.mjs | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/.typedoc/custom-plugin.mjs b/.typedoc/custom-plugin.mjs index aa806ad3e87..ee160a04d23 100644 --- a/.typedoc/custom-plugin.mjs +++ b/.typedoc/custom-plugin.mjs @@ -510,15 +510,18 @@ function collectPropertiesFromType(type, reflectionsByName) { } /** - * Cheap structural fingerprint for a `Type`. One-level deep — enough to disambiguate inline shapes used in different positions while staying fast on large projects. Two shapes that produce the same fingerprint are treated as structurally identical. + * Structural fingerprint for a `Type`. Recurses into composite shapes so two types that only differ in their type arguments (`Foo` vs `Foo`) or in their nested property types (`{ x: string }` vs `{ x: number }`) get distinct fingerprints. Two shapes that produce the same fingerprint are treated as structurally identical and so eligible for cross-pollinating JSDoc comments. + * + * Recursion guard: a single shared `Set` of visited reflection ids threads through every nested call to avoid infinite loops on cyclic types (e.g. a type literal that ultimately references itself). * * @param {import('typedoc').SomeType | undefined} type + * @param {Set} [seen] * @returns {string} */ -function typeFingerprint(type) { +function typeFingerprint(type, seen = new Set()) { if (!type) return '?'; const t = - /** @type {{ type?: string; name?: string; value?: unknown; elementType?: import('typedoc').SomeType; types?: import('typedoc').SomeType[]; declaration?: import('typedoc').DeclarationReflection }} */ ( + /** @type {{ type?: string; name?: string; value?: unknown; elementType?: import('typedoc').SomeType; types?: import('typedoc').SomeType[]; typeArguments?: import('typedoc').SomeType[]; declaration?: import('typedoc').DeclarationReflection }} */ ( /** @type {unknown} */ (type) ); switch (t.type) { @@ -526,20 +529,33 @@ function typeFingerprint(type) { return `i:${t.name ?? ''}`; case 'literal': return `l:${JSON.stringify(t.value)}`; - case 'reference': - return `r:${t.name ?? ''}`; + case 'reference': { + const args = t.typeArguments?.length ? `<${t.typeArguments.map(a => typeFingerprint(a, seen)).join(',')}>` : ''; + return `r:${t.name ?? ''}${args}`; + } case 'array': - return `a:${typeFingerprint(t.elementType)}`; + return `a:${typeFingerprint(t.elementType, seen)}`; case 'optional': - return `o:${typeFingerprint(t.elementType)}`; + return `o:${typeFingerprint(t.elementType, seen)}`; case 'union': - return `u:[${(t.types ?? []).map(typeFingerprint).sort().join(',')}]`; + return `u:[${(t.types ?? []) + .map(a => typeFingerprint(a, seen)) + .sort() + .join(',')}]`; case 'intersection': - return `n:[${(t.types ?? []).map(typeFingerprint).sort().join(',')}]`; + return `n:[${(t.types ?? []) + .map(a => typeFingerprint(a, seen)) + .sort() + .join(',')}]`; case 'reflection': { - const kids = t.declaration?.children?.filter(c => c.kindOf?.(ReflectionKind.Property)) ?? []; + const decl = t.declaration; + if (decl?.id != null) { + if (seen.has(decl.id)) return `rf:`; + seen.add(decl.id); + } + const kids = decl?.children?.filter(c => c.kindOf?.(ReflectionKind.Property)) ?? []; return `rf:[${kids - .map(c => `${c.name}${c.flags?.isOptional ? '?' : ''}`) + .map(c => `${c.name}${c.flags?.isOptional ? '?' : ''}:${typeFingerprint(c.type, seen)}`) .sort() .join(',')}]`; }