From 0d0500e597ec047b11bba6f4667148b5b8e2f828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20B=C3=A4r?= Date: Mon, 22 Jun 2026 08:29:34 +0200 Subject: [PATCH 01/10] feat: render comment block tags as separate sections Replace the bold `@tag` definition-list rendering with a labelled section per tag, mirroring the existing `@since` layout. --- src/components/Comment.tsx | 41 ++++++++++++++++++++++++-------------- src/components/styles.css | 17 ++++++++++++++++ 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/components/Comment.tsx b/src/components/Comment.tsx index 3cb06157..2cbab9ed 100644 --- a/src/components/Comment.tsx +++ b/src/components/Comment.tsx @@ -1,8 +1,27 @@ // https://github.com/TypeStrong/typedoc-default-themes/blob/master/src/default/partials/comment.hbs -import { Fragment } from 'react'; import type { JSONOutput } from 'typedoc'; import { Markdown } from './Markdown'; +// Human-friendly headings for the tags we render as their own section. Tags not +// listed fall back to their name with the leading `@` stripped and capitalised. +const TAG_LABELS: Record = { + '@deprecated': 'Deprecated', + '@remarks': 'Remarks', + '@returns': 'Returns', + '@see': 'See', + '@throws': 'Throws', +}; + +function getTagLabel(tag: string): string { + if (TAG_LABELS[tag]) { + return TAG_LABELS[tag]; + } + + const name = tag.replace(/^@/, ''); + + return name.charAt(0).toUpperCase() + name.slice(1); +} + export interface CommentProps { comment?: JSONOutput.Comment; root?: boolean; @@ -103,20 +122,12 @@ export function Comment({ comment, root, hideTags = [] }: CommentProps) { )} - {blockTags.length > 0 && ( -
- {blockTags.map((tag) => ( - -
- {tag.tag} -
-
- -
-
- ))} -
- )} + {blockTags.map((tag, index) => ( +
+ {getTagLabel(tag.tag)} + +
+ ))} ); } diff --git a/src/components/styles.css b/src/components/styles.css index 17987ab5..189e6bba 100644 --- a/src/components/styles.css +++ b/src/components/styles.css @@ -210,6 +210,23 @@ html[data-theme='light'] .tsd-panel-content { margin-bottom: var(--tsd-spacing-vertical-full); } +.tsd-comment-tag { + font-size: 90%; + margin-top: 0.75em; + padding-top: 0.5em; + border-top: 1px solid rgba(0, 0, 0, 0.05); +} + +.tsd-comment-tag-label { + display: block; + margin-bottom: 0.25em; + font-size: 85%; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.03em; + color: var(--tsd-muted-text); +} + /* SOURCES */ .tsd-sources, From 7c63681193ab131f706a55f9dd15256a0dbfe484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20B=C3=A4r?= Date: Mon, 22 Jun 2026 08:30:45 +0200 Subject: [PATCH 02/10] feat: add default messages for empty @deprecated and @see tags --- src/components/Comment.tsx | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/components/Comment.tsx b/src/components/Comment.tsx index 2cbab9ed..b673b274 100644 --- a/src/components/Comment.tsx +++ b/src/components/Comment.tsx @@ -12,6 +12,13 @@ const TAG_LABELS: Record = { '@throws': 'Throws', }; +// Tags that carry meaning even without a user-supplied message get a sensible +// default so the section is never empty. +const TAG_DEFAULT_MESSAGES: Record = { + '@deprecated': 'This is deprecated and should not be used in new code.', + '@see': 'See the related documentation.', +}; + function getTagLabel(tag: string): string { if (TAG_LABELS[tag]) { return TAG_LABELS[tag]; @@ -122,12 +129,17 @@ export function Comment({ comment, root, hideTags = [] }: CommentProps) { )} - {blockTags.map((tag, index) => ( -
- {getTagLabel(tag.tag)} - -
- ))} + {blockTags.map((tag, index) => { + const content = + displayPartsToMarkdown(tag.content).trim() || TAG_DEFAULT_MESSAGES[tag.tag] || ''; + + return ( +
+ {getTagLabel(tag.tag)} + +
+ ); + })} ); } From 61d85496a7e0a1d57b4856a1f933a9115883ee6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20B=C3=A4r?= Date: Mon, 22 Jun 2026 08:33:23 +0200 Subject: [PATCH 03/10] fix: use content-based key for comment tag sections --- src/components/Comment.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Comment.tsx b/src/components/Comment.tsx index b673b274..daea4b01 100644 --- a/src/components/Comment.tsx +++ b/src/components/Comment.tsx @@ -129,12 +129,12 @@ export function Comment({ comment, root, hideTags = [] }: CommentProps) { )} - {blockTags.map((tag, index) => { + {blockTags.map((tag) => { const content = displayPartsToMarkdown(tag.content).trim() || TAG_DEFAULT_MESSAGES[tag.tag] || ''; return ( -
+
{getTagLabel(tag.tag)}
From def2c1dd63248d7d487aafe679ca610e53335fee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20B=C3=A4r?= Date: Mon, 22 Jun 2026 08:33:37 +0200 Subject: [PATCH 04/10] feat: stop rendering the @example tag --- src/components/Comment.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Comment.tsx b/src/components/Comment.tsx index daea4b01..a18ff73f 100644 --- a/src/components/Comment.tsx +++ b/src/components/Comment.tsx @@ -110,7 +110,7 @@ export function Comment({ comment, root, hideTags = [] }: CommentProps) { } // Hide custom tags. - hideTags.push('@reference', '@since'); + hideTags.push('@reference', '@since', '@example'); const blockTags = comment.blockTags?.filter((tag) => { From 2c0dac9197ff68f922f1fd795a6bf9b9efc22e9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20B=C3=A4r?= Date: Mon, 22 Jun 2026 10:15:02 +0200 Subject: [PATCH 05/10] refactor: move block tag sections after hard-rendered content Extract CommentTags and render it after params/returns in signatures, after type declarations in member declarations, and after structural panels in top-level reflections. Drop the labeled heading in favour of the same italic style used by @since. --- src/components/Comment.tsx | 71 +++++++++++++++----------- src/components/MemberDeclaration.tsx | 8 +-- src/components/MemberSignatureBody.tsx | 20 ++++---- src/components/Reflection.tsx | 6 ++- src/components/styles.css | 17 ------ 5 files changed, 58 insertions(+), 64 deletions(-) diff --git a/src/components/Comment.tsx b/src/components/Comment.tsx index a18ff73f..36c33c73 100644 --- a/src/components/Comment.tsx +++ b/src/components/Comment.tsx @@ -2,37 +2,29 @@ import type { JSONOutput } from 'typedoc'; import { Markdown } from './Markdown'; -// Human-friendly headings for the tags we render as their own section. Tags not -// listed fall back to their name with the leading `@` stripped and capitalised. -const TAG_LABELS: Record = { - '@deprecated': 'Deprecated', - '@remarks': 'Remarks', - '@returns': 'Returns', - '@see': 'See', - '@throws': 'Throws', -}; - // Tags that carry meaning even without a user-supplied message get a sensible // default so the section is never empty. const TAG_DEFAULT_MESSAGES: Record = { - '@deprecated': 'This is deprecated and should not be used in new code.', + '@deprecated': 'This API is deprecated and may be removed in a future version.', '@see': 'See the related documentation.', }; -function getTagLabel(tag: string): string { - if (TAG_LABELS[tag]) { - return TAG_LABELS[tag]; - } +const ALWAYS_HIDDEN = ['@reference', '@since', '@example']; - const name = tag.replace(/^@/, ''); +function filterBlockTags( + blockTags: JSONOutput.CommentTag[], + hideTags: string[], +): JSONOutput.CommentTag[] { + const hidden = [...ALWAYS_HIDDEN, ...hideTags]; - return name.charAt(0).toUpperCase() + name.slice(1); + return blockTags.filter((tag) => !hidden.includes(tag.tag) && tag.tag !== '@default'); } export interface CommentProps { comment?: JSONOutput.Comment; root?: boolean; hideTags?: string[]; + noBlockTags?: boolean; } export function hasComment(comment?: JSONOutput.Comment): boolean { @@ -104,22 +96,38 @@ export function displayPartsToMarkdown(parts: JSONOutput.CommentDisplayPart[]): .join(''); } -export function Comment({ comment, root, hideTags = [] }: CommentProps) { +export interface CommentTagsProps { + comment?: JSONOutput.Comment; + hideTags?: string[]; +} + +export function CommentTags({ comment, hideTags = [] }: CommentTagsProps) { + const blockTags = filterBlockTags(comment?.blockTags ?? [], hideTags); + + return ( + <> + {blockTags.map((tag) => { + const content = + displayPartsToMarkdown(tag.content).trim() || TAG_DEFAULT_MESSAGES[tag.tag] || ''; + + if (!content) return null; + + return ( +
+ +
+ ); + })} + + ); +} + +export function Comment({ comment, root, hideTags = [], noBlockTags = false }: CommentProps) { if (!comment || !hasComment(comment)) { return null; } - // Hide custom tags. - hideTags.push('@reference', '@since', '@example'); - - const blockTags = - comment.blockTags?.filter((tag) => { - if (hideTags.includes(tag.tag)) { - return false; - } - - return tag.tag !== '@default'; - }) ?? []; + const blockTags = noBlockTags ? [] : filterBlockTags(comment.blockTags ?? [], hideTags); return (
@@ -133,9 +141,10 @@ export function Comment({ comment, root, hideTags = [] }: CommentProps) { const content = displayPartsToMarkdown(tag.content).trim() || TAG_DEFAULT_MESSAGES[tag.tag] || ''; + if (!content) return null; + return ( -
- {getTagLabel(tag.tag)} +
); diff --git a/src/components/MemberDeclaration.tsx b/src/components/MemberDeclaration.tsx index 4303c2a9..387ce0b2 100644 --- a/src/components/MemberDeclaration.tsx +++ b/src/components/MemberDeclaration.tsx @@ -3,7 +3,7 @@ import { useMinimalLayout } from '../hooks/useMinimalLayout'; import { useRequiredReflection } from '../hooks/useReflection'; import { escapeMdx } from '../utils/helpers'; -import { Comment, displayPartsToMarkdown, getSinceContent, hasComment } from './Comment'; +import { Comment, CommentTags, displayPartsToMarkdown, getSinceContent, hasComment } from './Comment'; import { DefaultValue } from './DefaultValue'; import { Icon } from './Icon'; import { Markdown } from './Markdown'; @@ -44,7 +44,7 @@ export function MemberDeclaration({ id }: MemberDeclarationProps) {
- + {hasComment(reflection.comment) && (showTypes || showDeclaration) && (
@@ -64,8 +64,10 @@ export function MemberDeclaration({ id }: MemberDeclarationProps) {
)} + + {sinceContent && ( -
+
)} diff --git a/src/components/MemberSignatureBody.tsx b/src/components/MemberSignatureBody.tsx index a41253d0..c581a6e4 100644 --- a/src/components/MemberSignatureBody.tsx +++ b/src/components/MemberSignatureBody.tsx @@ -7,7 +7,7 @@ import { usePluginData } from '@docusaurus/useGlobalData'; import { useMinimalLayout } from '../hooks/useMinimalLayout'; import type { TSDSignatureReflection } from '../types'; import { ApiDataContext } from './ApiDataContext'; -import { Comment, displayPartsToMarkdown, getSinceContent, hasComment } from './Comment'; +import { Comment, CommentTags, displayPartsToMarkdown, getSinceContent, hasComment } from './Comment'; import { CommentBadges, isCommentWithModifiers } from './CommentBadges'; import { DefaultValue } from './DefaultValue'; import { Flags } from './Flags'; @@ -109,7 +109,7 @@ export function MemberSignatureBody({ hideSources, sig }: MemberSignatureBodyPro {isCommentWithModifiers(sig.comment) && } - + {hasComment(sig.comment) && (showTypes || showParams || showReturn) && (
@@ -217,15 +217,13 @@ export function MemberSignatureBody({ hideSources, sig }: MemberSignatureBodyPro )} - { - sinceContent && ( - <> -
- -
- - ) - } + + + {sinceContent && ( +
+ +
+ )} ); } diff --git a/src/components/Reflection.tsx b/src/components/Reflection.tsx index 40c2aca5..97d4c0a3 100644 --- a/src/components/Reflection.tsx +++ b/src/components/Reflection.tsx @@ -3,7 +3,7 @@ import { useMemo } from 'react'; import type { TSDDeclarationReflection, TSDReflection, TSDSignatureReflection } from '../types'; import { createHierarchy } from '../utils/hierarchy'; -import { Comment, displayPartsToMarkdown, getSinceContent, hasComment } from './Comment'; +import { Comment, CommentTags, displayPartsToMarkdown, getSinceContent, hasComment } from './Comment'; import { CommentBadges, isCommentWithModifiers } from './CommentBadges'; import { Hierarchy } from './Hierarchy'; import { Icon } from './Icon'; @@ -31,7 +31,7 @@ export function Reflection({ reflection }: ReflectionProps) { return ( <> {isCommentWithModifiers(reflection.comment) && } - {hasComment(reflection.comment) && } + {hasComment(reflection.comment) && } {sinceContent && (
@@ -138,6 +138,8 @@ export function Reflection({ reflection }: ReflectionProps) { )} + + diff --git a/src/components/styles.css b/src/components/styles.css index 189e6bba..17987ab5 100644 --- a/src/components/styles.css +++ b/src/components/styles.css @@ -210,23 +210,6 @@ html[data-theme='light'] .tsd-panel-content { margin-bottom: var(--tsd-spacing-vertical-full); } -.tsd-comment-tag { - font-size: 90%; - margin-top: 0.75em; - padding-top: 0.5em; - border-top: 1px solid rgba(0, 0, 0, 0.05); -} - -.tsd-comment-tag-label { - display: block; - margin-bottom: 0.25em; - font-size: 85%; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.03em; - color: var(--tsd-muted-text); -} - /* SOURCES */ .tsd-sources, From 08a62ff029af971246dcfeaa3e5cb02dd678943e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20B=C3=A4r?= Date: Mon, 22 Jun 2026 10:33:28 +0200 Subject: [PATCH 06/10] feat: prefix @deprecated tag content with "Deprecated -" --- src/components/Comment.tsx | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/components/Comment.tsx b/src/components/Comment.tsx index 36c33c73..2f19273c 100644 --- a/src/components/Comment.tsx +++ b/src/components/Comment.tsx @@ -9,6 +9,10 @@ const TAG_DEFAULT_MESSAGES: Record = { '@see': 'See the related documentation.', }; +const TAG_PREFIX: Record = { + '@deprecated': 'Deprecated - ', +}; + const ALWAYS_HIDDEN = ['@reference', '@since', '@example']; function filterBlockTags( @@ -96,6 +100,16 @@ export function displayPartsToMarkdown(parts: JSONOutput.CommentDisplayPart[]): .join(''); } +function resolveTagContent(tag: JSONOutput.CommentTag): string { + const raw = displayPartsToMarkdown(tag.content).trim(); + + if (raw) { + return TAG_PREFIX[tag.tag] ? `${TAG_PREFIX[tag.tag]}${raw}` : raw; + } + + return TAG_DEFAULT_MESSAGES[tag.tag] || ''; +} + export interface CommentTagsProps { comment?: JSONOutput.Comment; hideTags?: string[]; @@ -107,8 +121,7 @@ export function CommentTags({ comment, hideTags = [] }: CommentTagsProps) { return ( <> {blockTags.map((tag) => { - const content = - displayPartsToMarkdown(tag.content).trim() || TAG_DEFAULT_MESSAGES[tag.tag] || ''; + const content = resolveTagContent(tag); if (!content) return null; @@ -138,8 +151,7 @@ export function Comment({ comment, root, hideTags = [], noBlockTags = false }: C )} {blockTags.map((tag) => { - const content = - displayPartsToMarkdown(tag.content).trim() || TAG_DEFAULT_MESSAGES[tag.tag] || ''; + const content = resolveTagContent(tag); if (!content) return null; From d9d8a8c52e500f455429b0cc42a0a2c5e5f3ec96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20B=C3=A4r?= Date: Mon, 22 Jun 2026 10:48:29 +0200 Subject: [PATCH 07/10] fix: move root tags to bottom and use :last-child for margin --- src/components/Reflection.tsx | 16 +++++++++------- src/components/styles.css | 5 ++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/components/Reflection.tsx b/src/components/Reflection.tsx index 97d4c0a3..d97393dc 100644 --- a/src/components/Reflection.tsx +++ b/src/components/Reflection.tsx @@ -33,12 +33,6 @@ export function Reflection({ reflection }: ReflectionProps) { {isCommentWithModifiers(reflection.comment) && } {hasComment(reflection.comment) && } - {sinceContent && ( -
- -
- )} - {'typeParameter' in reflection && reflection.typeParameter && reflection.typeParameter.length > 0 && @@ -138,7 +132,15 @@ export function Reflection({ reflection }: ReflectionProps) { )} - +
+ + + {sinceContent && ( +
+ +
+ )} +
diff --git a/src/components/styles.css b/src/components/styles.css index 17987ab5..f9ff441d 100644 --- a/src/components/styles.css +++ b/src/components/styles.css @@ -199,9 +199,8 @@ html[data-theme='light'] .tsd-panel-content { border-top: 1px solid rgba(0, 0, 0, 0.05); } -/* Top-level symbols render the tag inline above other sections, so it needs a - bottom margin to sit evenly; member tags close out a panel and don't. */ -.tsd-comment-since-root { + +.tsd-tags-root > .tsd-comment-since:last-child { margin-bottom: 1em; } From 153f19d901480c0aedf2be8e487d565e18d7192a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20B=C3=A4r?= Date: Mon, 22 Jun 2026 11:18:39 +0200 Subject: [PATCH 08/10] chore: add @see tag prefix --- src/components/Comment.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Comment.tsx b/src/components/Comment.tsx index 2f19273c..06797a3a 100644 --- a/src/components/Comment.tsx +++ b/src/components/Comment.tsx @@ -11,6 +11,7 @@ const TAG_DEFAULT_MESSAGES: Record = { const TAG_PREFIX: Record = { '@deprecated': 'Deprecated - ', + '@see': 'See more at ', }; const ALWAYS_HIDDEN = ['@reference', '@since', '@example']; From 0f254a18140a3e9395ba6584e3df3ac9b1af4ebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20B=C3=A4r?= Date: Mon, 22 Jun 2026 11:30:28 +0200 Subject: [PATCH 09/10] feat: render @example tag as code block without label --- src/components/Comment.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Comment.tsx b/src/components/Comment.tsx index 06797a3a..a6ff728d 100644 --- a/src/components/Comment.tsx +++ b/src/components/Comment.tsx @@ -14,7 +14,7 @@ const TAG_PREFIX: Record = { '@see': 'See more at ', }; -const ALWAYS_HIDDEN = ['@reference', '@since', '@example']; +const ALWAYS_HIDDEN = ['@reference', '@since']; function filterBlockTags( blockTags: JSONOutput.CommentTag[], From c889b472899cc3ff72a3aab87b59ee6261893014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20B=C3=A4r?= Date: Mon, 22 Jun 2026 11:32:42 +0200 Subject: [PATCH 10/10] refactor: deduplicate block-tag render and use Set for tag filtering --- src/components/Comment.tsx | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/src/components/Comment.tsx b/src/components/Comment.tsx index a6ff728d..80148def 100644 --- a/src/components/Comment.tsx +++ b/src/components/Comment.tsx @@ -14,15 +14,17 @@ const TAG_PREFIX: Record = { '@see': 'See more at ', }; -const ALWAYS_HIDDEN = ['@reference', '@since']; +const ALWAYS_HIDDEN = new Set(['@reference', '@since']); +const EMPTY_TAGS: string[] = []; function filterBlockTags( blockTags: JSONOutput.CommentTag[], hideTags: string[], ): JSONOutput.CommentTag[] { - const hidden = [...ALWAYS_HIDDEN, ...hideTags]; + const hidden = + hideTags.length === 0 ? ALWAYS_HIDDEN : new Set([...ALWAYS_HIDDEN, ...hideTags]); - return blockTags.filter((tag) => !hidden.includes(tag.tag) && tag.tag !== '@default'); + return blockTags.filter((tag) => !hidden.has(tag.tag) && tag.tag !== '@default'); } export interface CommentProps { @@ -116,7 +118,7 @@ export interface CommentTagsProps { hideTags?: string[]; } -export function CommentTags({ comment, hideTags = [] }: CommentTagsProps) { +export function CommentTags({ comment, hideTags = EMPTY_TAGS }: CommentTagsProps) { const blockTags = filterBlockTags(comment?.blockTags ?? [], hideTags); return ( @@ -136,13 +138,11 @@ export function CommentTags({ comment, hideTags = [] }: CommentTagsProps) { ); } -export function Comment({ comment, root, hideTags = [], noBlockTags = false }: CommentProps) { +export function Comment({ comment, root, hideTags = EMPTY_TAGS, noBlockTags = false }: CommentProps) { if (!comment || !hasComment(comment)) { return null; } - const blockTags = noBlockTags ? [] : filterBlockTags(comment.blockTags ?? [], hideTags); - return (
{!!comment.summary && ( @@ -151,17 +151,7 @@ export function Comment({ comment, root, hideTags = [], noBlockTags = false }: C
)} - {blockTags.map((tag) => { - const content = resolveTagContent(tag); - - if (!content) return null; - - return ( -
- -
- ); - })} + {!noBlockTags && }
); }