Summary
A union type-alias entry point renders no description on its generated markdown page, even when it has a docstring. Segment carries a rich description via Segment.__doc__, but the current transportation markdown renders that text zero times -- the # Segment page goes straight from its title to ## Fields.
Root cause
Union descriptions are sourced from FieldInfo.description during type analysis, never from __doc__. capture_union_members (extraction/type_analyzer.py:161) drives analyze_type, whose captured description is "the first FieldInfo.description found during unwrapping." Segment = Annotated[Union[...], Field(discriminator=...)] has exactly one FieldInfo -- the discriminator Field, which carries no description -- so UnionSpec.description resolves to None. The alias's __doc__ (set via Segment.__doc__ = "...") is never consulted.
Contrast with model classes: extract_model reads a class's __doc__ into RecordSpec.description, so a model's docstring renders. Unions have no equivalent path.
The rendering side is not the problem: the union template already emits {% if model.description %}, so a populated UnionSpec.description would render in the right place. The gap is purely sourcing.
Expectation
Give unions the same docstring path models already have -- source the union description from the docstring. The open decision is the sourcing rule / priority:
- the alias or class docstring (
__doc__)
FieldInfo.description (the current source -- keep as an override, or drop?)
This is a deliberate output change: it adds a description paragraph to every documented union page.
Relationship to the RootModel migration (#595)
The RootModel migration is the clean enabler. Once a union entry point is a RootModel class rather than an Annotated[...] alias carrying a Segment.__doc__ = assignment, its description lives in a normal class docstring -- a first-class source identical in kind to a model's. Sourcing from an Annotated alias's __doc__ is comparatively fragile.
Because the fix is a deliberate output change, it is deferred out of the byte-identical Segment-as-RootModel spike (the spike keeps UnionSpec.description at None -> None) and tracked here.
Summary
A union type-alias entry point renders no description on its generated markdown page, even when it has a docstring.
Segmentcarries a rich description viaSegment.__doc__, but the current transportation markdown renders that text zero times -- the# Segmentpage goes straight from its title to## Fields.Root cause
Union descriptions are sourced from
FieldInfo.descriptionduring type analysis, never from__doc__.capture_union_members(extraction/type_analyzer.py:161) drivesanalyze_type, whose captured description is "the firstFieldInfo.descriptionfound during unwrapping."Segment = Annotated[Union[...], Field(discriminator=...)]has exactly oneFieldInfo-- the discriminatorField, which carries nodescription-- soUnionSpec.descriptionresolves toNone. The alias's__doc__(set viaSegment.__doc__ = "...") is never consulted.Contrast with model classes:
extract_modelreads a class's__doc__intoRecordSpec.description, so a model's docstring renders. Unions have no equivalent path.The rendering side is not the problem: the union template already emits
{% if model.description %}, so a populatedUnionSpec.descriptionwould render in the right place. The gap is purely sourcing.Expectation
Give unions the same docstring path models already have -- source the union description from the docstring. The open decision is the sourcing rule / priority:
__doc__)FieldInfo.description(the current source -- keep as an override, or drop?)This is a deliberate output change: it adds a description paragraph to every documented union page.
Relationship to the RootModel migration (#595)
The RootModel migration is the clean enabler. Once a union entry point is a
RootModelclass rather than anAnnotated[...]alias carrying aSegment.__doc__ =assignment, its description lives in a normal class docstring -- a first-class source identical in kind to a model's. Sourcing from anAnnotatedalias's__doc__is comparatively fragile.Because the fix is a deliberate output change, it is deferred out of the byte-identical Segment-as-RootModel spike (the spike keeps
UnionSpec.descriptionatNone -> None) and tracked here.