Supporting more types of documentation with Swift-DocC

Hi all!

I'm excited to pitch new support in Swift-DocC for customizing some of the documentation compiler's default behaviors to support more contents types, especially thinking about more book-like content. These changes will make Swift-DocC a more appropriate tool for content like The Swift Standard Library and other large collections of conceptual documentation that are often put together for larger libraries or library ecosystems.

By default, Swift-DocC is optimized for documentation sites that include many symbols but comparatively few articles. This creates some problems when writing documentation that is made up of largely article-based content.

In order to address this, Swift-DocC will add a new kind of directive called an "option directive". Option directives will be similar to Swift-DocC's existing @Metadata directives but will be placed in a parent @Options directive to set options for the current page. An option directive should customize an automatic behavior of the DocC compiler. This is distinct from DocC's existing concept of a Metadata directive which customizes some kind of content of the current page that isn't authored anywhere on the page body.

This pitch is organized similar to Supporting more dynamic content in Swift-DocC reference documentation as a collection of new directives.

I'm looking forward to your feedback around the naming and behavior of these directives.



@Options

A new @Options directive for holding other directives describing how content on the local page should be autogenerated and rendered. This directive will function like the existing @Metadata directive but be used specifically for “option directives” that effect the way content is rendered on the page (and not the content itself).

Any directive that can be used in @Options is called an “option directive” and must also be supported in the corresponding @GlobalOptions directive.

@Options {
  @AutomaticTitleHeading(disabled)
  @AutomaticSeeAlso(siblingPages)
  @TopicsVisualStyle(list)
}

@GlobalOptions

A new @GlobalOptions for holding option directives describing how a DocC archive should be rendered. The options included in this directive will apply to all pages in the current catalog.

If a catalog contains multiple @GlobalOptions directives a warning will be emitted and both will be ignored.

The options included in the global options block will be superseded by any colliding elements in a page-specific @Options block. This allows for authors to set a general behavior that most pages in a catalog will adhere to, while still customizing the behavior of certain specific pages.

@GlobalOptions {
  @AutomaticTitleHeading(disabled)
  @TopicsVisualStyle(grid)
}



@AutomaticTitleHeading

A new @AutomaticTitleHeading option directive for customizing the way a page-title’s heading (also known as an eyebrow or kicker) is automatically generated.

Currently we render a page-title heading (also known as an eyebrow or kicker) describing the kind of the page on every page. For book-like content this might be less appropriate since the majority of pages are articles. The new @AutomaticTitleHeading directive will allow for customizing this behavior on a given page.

@AutomaticTitleHeading accepts an unnamed parameter containing one of the following:

  • pageKind: A page-title heading will be automatically added based on the page’s kind. This is Swift-DocC’s current default if no automatic subheading generation style is specified.
  • disabled: A page-title heading will not be automatically created for the page. A page-title heading can still be explicitly created with the @TitleHeading directive.

Examples

# ``SlothCreator``

@Options {
  @AutomaticTitleHeading(disabled)
}

Catalog sloths you find in nature and create new adorable virtual sloths.


# ``SlothCreator``

@Options {
  @AutomaticTitleHeading(pageKind)
}

Catalog sloths you find in nature and create new adorable virtual sloths.

Alternatives Considered

I’ve considered several alternative names for this directive and am very interested in feedback here. Some alternatives:

  • @AutomaticPretitle
  • @AutomaticEyebrow
  • @AutomaticSubheading
  • @AutomaticKicker

Unfortunately there doesn’t seem to be wide agreement on what to call this particular UI element. I feel strongly we should name it separately from its current relation to the page’s kind since authors could choose to put any information in the title heading so making this association is likely to lead to confusion.




@TitleHeading

A new @TitleHeading metadata directive for customizing the text of a page-title heading (also known as an eyebrow or kick).

@TitleHeading accepts an unnamed parameter containing containing the page-title’s heading text.

Examples

@Metadata {
  @TitleHeading("Release Notes")
}



@AutomaticSeeAlso

A new @AutomaticSeeAlso option directive for customizing the way See Also sections are automatically generated on a given page.

With the introduction of the navigation sidebar, Swift-DocC’s current automatic see also sections that are based on sibling pages have become somewhat redundant with the sidebar. I still see value in the autogenerated See Also sections, especially for readers on mobile devices where the sidebar is automatically collapsed, but exposing an option to exclude these items is appropriate.



In the future, we could imagine adding additional styles of automatically generated see also sections that authors could opt-in to here – beyond just sibling pages.

@AutomaticSeeAlso accepts an unnamed parameter containing one of the following:

  • siblingPages: The current list of auto-generated “See Also” items based on sibling items. This is Swift-DocC’s current default if an automatic see also style is not specified.
  • disabled: Do not automatically generate any See Also items.

Examples

@Options {
  @AutomaticSeeAlso(disabled)
}


@TopicsVisualStyle

A new @TopicsVisualStyle option directive for customizing the way Topics sections are rendered on a given page.

Swift-DocC’s current list-style of child topics is great for API-style documentation where we expect pages to not have too many children and we expect page titles to be relatively long declarations. But for book-like content where items are divided into chapters and page titles are generally shorter, a grid-like view can offer a better experience. For other cases, it might be better to do away with on-page rendering of child pages entirely and allow the navigator to be the singular-stop for navigation. The new @TopicsVisualStyle directive will allow for customizing the way child pages are rendered on the page.

This directive can be used in combination with the below @PageImage directive by providing a page image with a card purpose. If no explicit card image is provided, then the page’s icon will be used to auto-generate a card image.

@TopicsVisualStyle accepts an unnamed parameter containing one of the following:

  • list: The current list of topics we render on page, including their abstracts. This is Swift-DocC’s current default if a topics style is not specified.
  • compactGrid: A grid of items based on the card image for each page. Includes each page’s title and card image but excludes their abstracts.
  • detailedGrid: A grid of items based on the card image for each page, includes the abstract for each page.
  • hidden: Don’t show child pages anywhere on the page.

Examples

# Feeding a Sloth

Find out how to feed a sloth and how to keep your sloth healthy.

@Options {
  @TopicsVisualStyle(compactGrid)
}

## Topics

### Sloth Dietary Preferences

- <doc:Get-Started-Preparing-Sloth-Food>
- <doc:Feeding-your-Sloth-in-the-Winter>
- <doc:Ordering-Food-Delivery>

### Healthy Habits for Sloths

- <doc:Balancing-Meals>
- <doc:Meal-Prep-for-Sloths>
- <doc:Staying-Hydrated>
- <doc:Components>
- <doc:Inputs>
- <doc:Technologies>

### Treats for your Sloth

...


# Feeding a Sloth

Find out how to feed a sloth and how to keep your sloth healthy.

@Options {
  @TopicsVisualStyle(detailedGrid)
}

...



@PageImage

A new @PageImage directive that can be placed inside a page’s @Metadata directive block to allow for customizing the images that will be used to represent the page in the navigator, hero, and elsewhere in the UI.

For article-only catalogs it’s likely helpful to distinguish between articles with custom icons- in the same way we have different icons for different kinds of symbol pages.

@PageImage accepts the following parameters:

  • purpose: The purpose of the image. One of the following:
    • icon: This image should be used in place of wherever a generic icon for the page would usually be rendered. Today that includes page’s hero introduction section and the navigator sidebar.
    • card: This image should be used whenever rendering a card representing this page. Today that includes Topics sections that are styled as grids.
  • source: A string containing the name of an image in your DocC catalog.
  • alt: (optional) A string containing a description of the image.

Examples

# Feeding a Sloth

@Metadata {
  @PageImage(purpose: icon, source: "leaf.svg", alt: "An icon representing a leaf.")
}

# Meal Prep for Sloths

@Metadata {
  @PageImage(purpose: icon, source: "hot-soup.svg", alt: "An icon representing a bowl of hot soup.")
}


# Get Started Preparing Sloth Food

Learn how to meal prep for your sloth and plan ahead for busy weeks.

@Metadata {
   @PageImage(purpose: card, source: "sloth-with-food", alt: "A happy sloth eating a snack.")
}



@CustomMetadata

A new @CustomMetadata directive that accepts an arbitrary key/value pair and emits it into the metadata of the created RenderJSON describing the page. This adds some flexibility for developers who might want to extend Swift-DocC’s functionality with another tool that acts on the emitted JSON – without requiring modification to the DocC compiler itself.

@CustomMetadata accepts the following parameters:

  • key: A key to identify the piece of metadata.
  • value: The value for the metadata.

Future Directions

This general framework of directives allows for some exciting possibilities such as:

@AutomaticPagination

A new @AutomaticPagination option directive to allow for automatic generation of next and previous page navigation that is commonly found in book-like documentation sites like The Swift Programming Language:

@NextPage and @PreviousPage

New @NextPage and @PreviousPage metadata directives that allow for explicitly specifying next and previous page navigation for a given page.

@AutomaticTopics

A new @AutomaticTopics option directive that allows for controlling the way child topics are automatically organized. This could be a great way to address Default to order by source line number instead of alphabetically for default layout · Issue #340 · apple/swift-docc · GitHub.

/// Something here
///
/// @Options {
///   @AutomaticTopics(sourceOrder)
/// }
enum A {
    case one
    case two
    case three
}

@PageColor

A new @PageColor metadata directive that would behave much like the pitched @PageImage directive and allow for specifying what color should be used to represent the given page.

@ChildOptions

A new @ChildOptions directive to go with the @GlobalOptions and @Options directive. This directive would be used to set options for pages that are organized as children of the page that owns this directive.

This would allow for authors to more easily create subsections of their documentation that are generated in a more book-like fashion (with pagination for example) while using different options for the more API-like part of their documentation.



These changes create a framework to allow Swift-DocC to address the needs of many different kinds of documentation while still supporting a great out-of-the-box experience for the most common use cases.

11 Likes

Hey Ethan, this looks nice! I need some more time to digest the individual options, but I have some thoughts on the Options, GlobalOptions, and future ChildOptions container directives.

On the directives existing at all: I think in general it would be useful to come up with some guiding principals for parameterless container directives. They definitely serve a purpose, but there are many that aren't strictly/technically necessary and so they need to provide significant value to outweigh the indentation + discovery + complexity cost. I regret many of the container directives I added that contributed to the deep nesting, composability, progressive authoring, and syntax discovery issues in tutorials. I'm also skeptical that the Metadata directive ended up being really necessary and useful though that ship has mostly sailed. This is not meant to imply that I think we shouldn't ship the proposed containers; only that we should apply extra consideration.

On the behavior of the directives, if we decide they should exist: This part of the proposal raised a bit of a caution flag for me. I think that when there's no natural home for authoring particular content, it can feel awkward to use.

I've got a few different ideas; I don't really have a favorite and they don't individually solve all the problems but perhaps they can in some combination...

A. Instead of three directives, one Options(<scope>) directive. This is probably the most straightforward change from your current proposal and I think it improves the syntax though it doesn't fix any other problems.
B. No GlobalOptions, instead Options when written in a framework/technology root apply globally.
C. Similar to B, but Options are inherited when placed at any level in the tree. I don't know if this is possible because I think pages can have multiple parents, but it would be neat if possible.
D. Alternatively instead of using the content tree for inheritance, use the canonical url tree: all articles inherit from the tech root, symbols inherit according to the symbol graph. This might be confusing though.
E. For GlobalOptions, introduce a configuration file or extend the SPM manifest or extend the Info.plist.

5 Likes

Hi Jack,

Thank you for all of this great feedback! Looking forward to your future thoughts post-digestion as well.

This is a great point. In general with Supporting more dynamic content in Swift-DocC reference documentation, I'm trying to push towards directives always semantically matching 1-1 with the resulting content when used in reference docs. The location of the directive should matter and affect the way content renders in the same way the location of any other markdown syntax matters. Like you mentioned, the way directives are used in Tutorials is much more abstract and makes use of containers in a way that I don't think makes sense when intermixing directives with a document that should be majority vanilla markdown content outside of any directive.

One of the primary reasons I think we should use the @Options container is to further enforce that the directives that go in an @Options are explicitly out-of-the-ordinary and a clear departure from the norm. All other directives should be creating content on the page and, thus, outside of a container. This is the same reason that metadata directives belong in a container.

I don't foresee needing any future container directives besides @Options and @Metadata but I think they both justify themselves since their location in the text document doesn't matter in the way it should for all other directives in a regular non-tutorial .md file.

I like this idea! I think this fits in better with the rest of the directive structure and allows for more flexibility moving forward.

@Options(scope: local) {
  ...
}
@Options(scope: global) {
  ...
}

With local being the default if no scope is specified.


I don't think I agree with B through E. The problem I see with (B) is that I think it will be quite common for folks to want style their top-level page differently from its children. For (C), I like that idea as an option (Options(scope: children)) but I think making it the default is a little fraught for the reason you mentioned: a child can have multiple parents. For (D), I agree that would be confusing since it's a departure from the usual DocC tree structure.

(E) is definitely reasonable, but personally I think that @Options(scope: global) is more intuitive and discoverable. It also allows folks to customize options globally without adding a DocC catalog. I wouldn't want DocC to become too dependent on SPM for configuring settings like this.

- Ethan

3 Likes

I agree with @ethankusters that proposal (B) is problematic. I think that this feature making book-like content possible means that we are bound to see more such content mixed with reference docs within the same package. I think our current styling is what most people would prefer for reference docs, but not what they would want for book-like content.
I like option (C) conceptually but as you alluded to, I think things could get hairy for multi-curated pages. Nonetheless I imagine we might see a lot of technology root curations looking something like the following:

## Topics
@Options { <!--- stuff that's book-like content friendly --> }

### Book Like Stuff
<!--- Some curated interesting content -->

### Reference Docs
- ``MyFramework`` <!--- this page would reset options to reference documentation friendly styles -->

I do think (E) is reasonable, but maybe we could keep the discoverability element by only allowing users to customise global options from the root page?

2 Likes

Thank you for the great feedback here! Support for the majority of the pitched changes has landed in a nightly toolchain.

Notably, the syntax for @Options has changed from the original pitch to merge both @Options and @GlobalOptions into a single directive with a scope parameter:

Of the pitched directives, only support for @TitleHeading and @CustomMetadata is still pending – tracked with apple/swift-docc#408 and apple/swift-docc#405 respectively.

If you're interested in these enhancements, please download the latest nightly toolchain and provide any feedback here. Thank you!

- Ethan


4 Likes