💾 Archived View for auragem.letz.dev › devlog › 20240326.gmi captured on 2024-05-26 at 14:41:25. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2024-05-10)

-=-=-=-=-=-=-

2024-03-26 The Case for a 4th-Level Heading

This is part of my series on re-assessing the designs of Gemini and Gopher:

2024-03-22 Gopher's Uncontextualized Directories vs. Gemini's Contextualized Directories

2024-03-23 What Gemini Gets Wrong With Anti-Extensibility

2024-03-24 The Necessary Semantics behind Emphasis and Strong

2024-03-25 The Simplicity of List Nesting: How AsciiDoc Does It

Headings have long been settled in Geminispace, so why are we re-assessing them? I believe that headings need to be re-assessed because the choice of limiting the levels to just three seems arbitrary and is counter to heading usage in the real world.

If we are to create a usable markup language, we must adhere to a power-to-weight ratio that takes into account what is common or expected within real usage, but that which also doesn't cause too much weight. The current restriction of three levels does not cover most common heading usage outside of Geminispace, and sometimes people within Geminispace resort to bad practices as workarounds.

Heading Usage

Let's start with an easy one: traditional books. We know that books usually have chapters, and some books even have sections! Classical literature, however, can often have "Books" as well. They are sections of the novel as a whole. Books can be in parts or chapters, and parts can be in books or chapters:

# The Lord of the Rings - J.R.R. Tolkein
## Prologue
## Note on the Shire Records
## Book I
### Chapter 1: A Long-expected Party
### Chapter 2: The Shadow of the Past
...
## Book II
### Chapter 1: Many Meetings
...

Sometimes books have both books and parts!

# The Brothers Karamazov - Fyodor Dostoyevsky
## Forward
## Part One
### Book I: The History of the Family
#### 1. Fyodor Karamazov
#### 2. He Gets Rid of His Eldest Son
...
### Book II: An Unfortunate Gathering
...
## Part Two
### Book IV: Lacerations
#### 1. Father Ferapont
...

And sometimes chapters contain sub-sections that head paragraphs or multiple paragraphs. This is very common in Abraham Heschel's works, and they are not always part of the Table of Contents, but sometimes they are:

# God In Search of Man: A Philosophy of Judaism - Abraham Heschel
## I. God
### 1. Self-Understanding of Judaism
#### To Recover the Questions
#### Philosophy and Theology
#### Situational Thinking
...
### Ways to His Presence
#### The Bible is Absent
#### Memory and Insight
...
## II. Revelation
...

This pattern of three levels *under the main title* is also common in Textbooks:

# A Brief Introduction to the Old Testament (3rd Ed.) - Michael D. Coogan (OUP)
## Preface
## Abbreviations
## Credits
## Part One: Introductory
### 1. What is the Old Testament?
#### The Jewish Canon
#### The Christian Canons
#### The Study of the Bible
#### A Look Back and Ahead
...
## Part Two: Cosmic Origins
### 3. Creations: Genesis 1-3
#### The Book of Genesis
#### Genesis 1 and the Sabbath
#### The Second Account of Creation (Gen 2.4b-3.24)
...

It turns out, if we look at a famous Academic book publisher, they explicitly talk about restricting works to *three-heading levels* in the section on "Preparing your manuscript":

Keep the structure simple, using no more than three heading levels (unless you are writing a practitioner law title or have cleared additional heading levels with your OUP editorial contact).

OUP: Headings

And the OUP book I referenced above certainly follows that rule. Three levels of heading *underneath the title of the work* is very common! But now let us look at online writing, because we are online, not in print, so that is also important.

Wikipedia explicitly states that all six levels of headings are available:

Headings follow a six-level hierarchy, starting at 1 and ending at 6. The level of the heading is defined by the number of equals signs on each side of the title. Heading 1 (= Heading 1 =) is automatically generated as the title of the article, and is never appropriate within the body of an article. Sections start at the second level (== Heading 2 ==), with subsections at the third level (=== Heading 3 ===), and additional levels of subsections at the fourth level (==== Heading 4 ====), fifth level, and sixth level. Sections should be consecutive, such that they do not skip levels from sections to sub-subsections; the exact methodology is part of the Accessibility guideline.

You can see even Wikipedia implicitly distinguishes between the headings 1-3 and headings 4-6 ("and *additional* levels of subsections")!

However, what's available does not make what is commonly used. Here's one article that uses four levels in total (the article title plus three levels under that):

Greco-Persian Wars: Origins of the conflict

Obviously that's just one that I found very quickly, but it doesn't encompass what's common among all of Wikipedia. However, the fact that there is one instance shows at least some level of need.

The Rust Programming Book also uses a total of four headings:

# The Rust Programming Language
## Forward
## Introduction
## Getting Started
### Installation
#### Installing rustup on Linux or macOS
#### Installing rustup on Windows
#### Troubleshooting
### Hello, World!
...
## 2. Programming a Guessing Game
...
...

The Rust Programming Language

There is one final one I want to look at: Programming Documentation. When you go to pkg.go.dev, you will see that there are two presentations of the headings. The sidebar presents Functions and Types as separate sections, but the Index does not. Let's look at the bufio documentation.

bufio Documentation

From the Sidebar:

# Documentation
## Index
### Examples
## Constants
## Variables
## Functions
### ScanBytes(data, atEOF)
...
## Types
### type ReadWriter
#### NewReadWriter(r, w)
### type Reader
#### NewReader(rd)
#### NewReaderSize(rd, size)
...

In the Index, however, the Types and Functions sections are flattened, which is expected of Indexes. In fact, the use of level-4 headings is a problem when one wants to convert golang documentation to gemtext! Here's a capsule that does just this. This is how it presents the bufio documentation:

Bufio on godocs.io Gemini Capsule

It has to flatten level-4 and level-3 headings because Gemini lacks level-4 headings, even though level-4 headings are extremely common not only online, but in books in general!

Workarounds

There are a couple of workarounds that we must use in Gemini because of its arbitrary lack of a level-4 heading:

There are problems with all three of these workarounds:

The Solution

Do I really need to write this section? Add a freaking level-4 heading! And standardize level-1 headings to just the document's title, and a section break for subsequent level-1 headings that should be considered not part of the main page's content (this could be useful for Wikis that want to place a footer, perhaps). Or better yet, specify that all documents should have only one level-1 heading, and any others should be ignored or something.

About the 3-characters to get the linetype rule: It's arbitrary. It could be 4 characters just as easily as it could be 3 characters. One more byte in an array on the stack is not exactly space or cpu inefficient. Most people probably don't do that anyways - they probably just check whether a line starts with a whole string or not, like I do in my code below. Even C has a function for this called `strncmp` (which you can use with `strlen` of the prefix), and because our prefixes are always one-byte codepoints, this will work with UTF-8 just fine.

About the where does it end argument: It ends with what's most common. Three levels under the work's title is common online, in books, in papers and manuscripts, and in the publishing industry. This means there's more power than weight in adding a level-4 heading, and we stop there because the others aren't necessary or common, and even Wikipedia imples that the other headings (5-6) are rare. The rule of three happens to be a well-known rule in writing for a reason! Lastly, people actually need level-4 headings within Geminispace, because some people have used workarounds. The use of these workarounds shows a need.

We shouldn't make our decisions purely on theoreticals. Just like in Theology, where doctrine is informed by practice, our standards and specs should be informed by practice, by usage, by need.

Addendum: Subtitles

One last consideration that needs to be taken into account is subtitles. There are many works that have subtitles, and using a level-2 heading for them conflates semantics, confuses the organization of the document, and reduces the levels of headings one can use for the first section. There are three solutions:

Parsing in Golang and Rust

Note: The Golang and Rust code below is licensed as MIT.

Here's my code in Rust, which uses a GeminiLine enum, since it separates parsing from printing to the terminal:

fn parse_heading(s &str) -> GeminiLine {
	return if line.starts_with("####") {
	    GeminiLine::Heading(line.trim_start_matches("####").trim_start(), 4)
	} else if line.starts_with("###") {
	    GeminiLine::Heading(line.trim_start_matches("###").trim_start(), 3)
	} else if line.starts_with("##") {
	    GeminiLine::Heading(line.trim_start_matches("##").trim_start(), 2)
	} else if line.starts_with("#") {
	    GeminiLine::Heading(line.trim_start_matches("#").trim_start(), 1)
	}
}

The GeminiLine enum in Rust:

#[derive(Debug)]
pub enum GeminiLine<'a> {
    Heading(&'a str, usize),
    Link(&'a str, &'a str, bool),
    PromptLink(&'a str, &'a str, bool),
    Bullet(&'a str),
    Quote(&'a str),
    PreformatToggle(&'a str),
    Text(&'a str),
    None,
}

Headings are also easy to parse in Golang, although the code above is not as compact as it should be:

if strings.HasPrefix(line, "####") {
	text := strings.TrimSpace(strings.TrimPrefix(line, "####"))
	if title == "" || lastTitleLevel > 4 {
		title = text
		lastTitleLevel = 4
	}
	if headings != nil {
		*headings = append(*headings, Heading{4, text})
	}

	multiline := wordWrapper_heading4(text)
	lines := strings.Split(multiline, "\n")
	for i, line := range lines {
		if i == 0 {
			color.Cyan("%s#### %s\n", indentationString, line)
		} else {
			color.Cyan("%s     %s\n", indentationString, line)
		}
	}
}

The golang code is used by my Scroll-Term client:

Scroll Software

Continue the Series

Here are the next articles in this series:

2024-03-27 Who Controls Presentation? Presentation vs. Semantics

2024-03-28 Headers, Footers, Sidebars, and Footnotes