# About this site

My website has been online since 2013.
It serves as a personal wiki where I collect bookmarks, excerpts, and thoughts.
Some pages are rougher and mostly useful to me, while others are more polished and intended for other readers.
Cam Pegg&#39;s defunct &#34;Notes to Self&#34;
[listed the site](https://web.archive.org/web/20201022004958/https://notes.campegg.com/digital-gardens/)
as a
[&#34;digital garden&#34;](https://maggieappleton.com/garden-history).

In its current incarnation,
the site is a collection of interlinked HTML files produced by a custom static site generator and served by [Caddy](/caddy).
The result is a wiki that only one person can edit.


## Contents


## Influences

Influences on the site include
Wikipedia,
the [Tcler&#39;s Wiki](https://wiki.tcl-lang.org/),
[Ward Cunningham&#39;s wiki](!W &#34;WikiWikiWeb&#34;),
the [Memex](!W)
and [Ted Nelson](!W)&#39;s writings,
[TiddlyWiki](https://en.wikipedia.org/wiki/TiddlyWiki),
[TV Tropes](https://tvtropes.org/),
[Everything2](!W),
and [Everything Shii Knows](https://shii.bibanon.org/shii.org/knows/Everything_Shii_Knows.html).

The common theme of these sites is writing evolving pages instead of finished blog posts.
Other themes include
internal linking,
organizing link collections into documents (similar to Memex &#34;trails&#34;),
and one page per subject.

The biggest influence is [Gwern.net](https://gwern.net/).
Watching its development was what made me turn a stalled blog into a personal wiki.
Besides the [philosophy](https://gwern.net/about) of perpetual drafts and writing for your future self,
the influence also shows in the design.
I have borrowed many [design elements](https://gwern.net/design) from Gwern.net that are not visible to the reader,
such as Pandoc, link archival, and the interwiki link syntax (`[favicon](!W)` to link to &#34;Favicon&#34; on Wikipedia),
as well as visible design elements such as the [subscript dates](https://gwern.net/subscript) and link icons.


## Short URLs

I experimented with long URLs derived from the page title by a simple algorithm.
What I learned was that URLs should be short.
They should also use a minimal set of characters in the path (`[/A-Za-z0-9.-_]`) and avoid [query strings](!W &#34;Query string&#34;).

In the [Fossil incarnation](#fossil) of the site, I decided at first to embrace Wikipedia-style page naming: my page URLs would include the full title.
I thought it was neat: one less thing to worry about, less friction when creating pages.

My Fossil URLs started out as

&gt; [https://dbohdan.com/wiki?name=How+&lt;wbr&gt;Internet+&lt;wbr&gt;communities+&lt;wbr&gt;function](https://dbohdan.com/wiki?name=How+Internet+communities+function)

and evolved to

&gt; [https://dbohdan.com/wiki/How+&lt;wbr&gt;Internet+&lt;wbr&gt;communities+&lt;wbr&gt;function](https://dbohdan.com/wiki/How+Internet+communities+function)

and then

&gt; &lt;https://dbohdan.com/wiki/internet-communities&gt;

First, I realized the query part of a URL is too easily lost.
Looking for alternatives, I discovered Fossil allowed `/wiki/Baz+foo` instead of `/wiki?name=Baz+foo`.
I didn&#39;t link to my site from many places,
yet I still noticed that spaces encoded as `+` in the path part of the URL were sometimes a problem for linking and automatic URL detection.
Encoding spaces as `%20` worked more often but looked even uglier.
Eventually, I gave up on the idea of page names with the full title.

When I migrated off Fossil, I dropped the `/wiki/` part.
My current URL format uses one or more lowercase English words joined with dashes:

&gt; &lt;https://dbohdan.com/internet-communities&gt;

### See also

- &lt;!-- 2011-05-10 --&gt; Sam Hughes, [&#34;On short URLs&#34;](https://qntm.org/urls){.no-link-icon}, &lt;https://qntm.org/urls&gt; (2011)
- &lt;!-- 2022-05-08 --&gt; Derek Sivers, [&#34;Short URLs: why and how&#34;](https://sive.rs/su){.no-link-icon}, &lt;https://sive.rs/su&gt; (2022)
- &lt;!-- 2023 --&gt; Gwern Branwen, [&#34;Design Graveyard: Long URLs&#34;](https://gwern.net/design-graveyard#long-urls){.no-link-icon}, &lt;https://gwern.net/design-graveyard#long-urls&gt; (2023)
- [Clean URL](!W)

I cannot fully endorse Derek Sivers&#39;s approach.
I think regular words are preferable to single letters or initialisms.
Words are easier to remember.
They are easier to type on mobile devices with autocomplete.
Typos are more obvious in common words.
With words instead of single letters, typos are less likely to take you to an existing but incorrect page.


## Web design {#design}

For almost a decade, the site used lightly customized
[Bootstrap 3](https://getbootstrap.com/docs/3.3/):
at first plain,
then with the theme [Sandstone](https://bootswatch.com/3/sandstone/) from Bootswatch.
Now the site uses its own stylesheet that partially imitates the look it had with Bootstrap.
The site&#39;s current palette is built around a tweaked subset of colors from Sandstone.

The site [favicon](!W) is taken from the original [Windows File Manager](https://github.com/microsoft/winfile) (`winfile.exe`).
In 2018, Windows File Manager was released as free software under the MIT License.

### Dark mode

The site implements [dark mode](!W) using the [`prefers-color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/prefers-color-scheme) media query with a switch to override the OS/browser preference.
It avoids the mistake of having a switch with only to states, light and dark, and allows the visitor to return to their OS/browser preference.
The switch and the approach to dark mode are modeled on [Gwern.net&#39;s](https://gwern.net/design#dark-mode).
The switch replaces the `media` attibute on a dedicated `&lt;style&gt;` element for dark-mode CSS.

Loading and storing the settings and replacing the media query is implemented in [`dark-mode.ts`](/assets/dark-mode.ts);
the switch logic is implemented in [`dark-mode-switch.ts`](/assets/dark-mode-switch.ts).
`dark-mode.ts` stores the visitor&#39;s preference in [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
While `dark-mode-switch.ts` is compiled from [TypeScript](!W) and [bundled](/assets/bundle.js) with other JavaScript, `dark-mode.ts` is compiled and inlined in the page source, as is dark-mode CSS.
This is done to prevent a flash of light on page load.


## The build process {#build}

The site is built with a static site generator.

First, it bundles CSS, then compiles TypeScript to JavaScript with [`tsgo`](https://github.com/microsoft/typescript-go) and bundles JavaScript.
CSS and JavaScript are bundled by concatenation; they are not minified.

The generator then performs the following steps for each page:

#. Take Pandoc Markdown text and [TOML](https://toml.io) metadata
#. Expand [Jinja](https://jinja.palletsprojects.com/) [macros]{#macros} in the Markdown (if enabled for the page)
#. Convert Markdown to HTML with Pandoc
#. Inject the HTML into a page template configured using site and page metadata


## Deployment {#deploy}

The site is deployed using rsync over a [multiplexed SSH connection](https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Multiplexing).

Somewhat to my surprise, I have reduced the build-and-deploy time for the current version of the website to under three seconds.
I&#39;ve had to double-check changes to confirm they were actually deployed.
This is the result of cumulative performance improvements:
parallelism in the build step,
switching from building a Debian package to rsync deployment,
multiplexing SSH connections a different task,
and most recently [dprint](#templates).


## Technical history

&lt;figure&gt;
[![A webcomic panel showing points on a graph with a hyperbolic relationship between the number of blog posts and the number of posts about elaborate blog setups. &#34;WordPress setup from 2004&#34; leads on the number of blog posts, while &#34;Authors of custom static site generators&#34; leads on the number of posts about elaborate blog setups.](/media/about/honestly-undefined-blogging.jpg)](/media/about/honestly-undefined-blogging.jpg)
&lt;figcaption&gt;
[&#34;Blogging vs. blog setups&#34;](https://rakhim.org/honestly-undefined/19/), _Honestly Undefined_ (2020)
&lt;/figcaption&gt;
&lt;/figure&gt;

The initial version of the site (later labeled 0.x) was a static HTML page with links to my profiles on other sites and a contact form.
The links survive in the [&#34;elsewhere&#34;](/#elsewhere) section of the index page,
and the [contact form](/contact) is largely unchanged.
I wrote the pages in Markdown and converted them to HTML manually with John Gruber&#39;s [`Markdown.pl`](https://daringfireball.net/projects/markdown/).
To store the page source code, I created a Git repository.

### Tclssg

The manual process was soon replaced as I started developing the [Tclssg](https://github.com/tclssg/tclssg) static site generator.
The focus of the site became its newly added blog, which lasted from 2014 to 2016.
Blogging didn&#39;t necessarily encourage me to write and publish.
The blog is [preserved](/old-blog).
Tclssg remains in use outside my website.
It has been [adopted](https://wiki.tcl-lang.org/page/Tclssg#351e4f233e715226538baffea227e6c696c6a27433b94b8238ee3c7afdf963ca) by some Tcl users and projects, including [core.tcl-lang.org](https://core.tcl-lang.org/).

### Fossil SCM {#fossil}

The second major version (2020–2022) was a personal wiki served by [Fossil SCM](https://fossil-scm.org/).
After examining different wiki software and finding flaws in each, I went with one I was already using.
I had noticed Fossil&#39;s curious potential beyond source control.
Fossil had enough wiki and theming features to serve a customized website.
It let me edit the site&#39;s contents in the browser.
I joked that Fossil SCM was secretly &#34;Fossil [CMS](!W &#34;Content management system&#34;)&#34;.
This marked a temporary break from storing content in Git.
I have keep the content and the code versioned separately since.

&lt;details&gt;
&lt;summary&gt;Packaging Fossil&lt;/summary&gt;
For deployment, I started building a [Debian package](!W &#34;Deb (file format)&#34;) with the Fossil binary, Caddy configuration, and static files.
When installed, the package replaced the previous version of the site as a whole; if installation failed, changes were automatically reverted.

A series of polemic comments by HN user Annatar describing his partices was what made me commit to this approach.
Annatar later summaried the practices in a [comment](https://news.ycombinator.com/item?id=34578389).
&lt;/details&gt;

It took some hacks to make Fossil do what I wanted.
The wiki lacked categories and [transclusion](!W).
At the time, it could not generate a table of contents for a page.
I created a simple notation for tags and began generating a [&#34;tag page&#34;](/tags) using a [Tcl script](/archive/dbohdan.com/artifact/8297b54f5d).
The script ran when I synchronized my local Fossil repository with the server.
(A Fossil synchronization is like a Git pull followed by a push.)
The TOC was generated [in JavaScript](/archive/dbohdan.com/artifact/d81bb60a0e) in the reader&#39;s browser.

I enjoyed my time with a Fossil-based site.
Being able to edit the wiki in the browser removed friction compared to a static site generator.
However, I kept feeling Fossil&#39;s limitations as a wiki engine.
Beyond built-in categories and TOCs,
I wanted the ability to version pages together.
It&#39;s easier to understand your site&#39;s edit history when related changes to multiple pages are grouped together.
Using the [`/doc/` page feature](https://fossil-scm.org/home/doc/tip/www/embeddeddoc.wiki) was an option
but would have negated Fossil&#39;s advantage of live wiki editing.
I wrote [fossil-wiki-export](https://github.com/dbohdan/fossil-wiki-export) to have a migration path off Fossil and eventually used it.

### Custom SSG {#custom-ssg}

I initially (2022) wrote the third major version of the static site generator in Clojure.
The site was still a wiki, only static.
The code ran in [Babashka](https://github.com/babashka/babashka) and rendered a page template with Django-inspired [Selmer](https://github.com/yogthos/Selmer).

Not long after (2023), I ported the static site generator to Python to take advantage of the libraries and good optional static typing thanks to [Pyright](https://github.com/microsoft/pyright).
The start with Selmer made it easy to translate the template to [Jinja](https://jinja.palletsprojects.com/).
Deployment still used a Debian package, now with only static files in it, until I migrated the site to [BoxyBSD](/boxybsd) in 2025.
With that migration, I switched to rsync deployment and kept it after leaving BoxyBSD.

I was quite happy with the Clojure-to-Python port.
By using [`ThreadPoolExecutor`](https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ThreadPoolExecutor) in Python and leaning on [Pandoc Lua filters](https://pandoc.org/lua-filters.html), I reduced the build time of the site to single-digit seconds.
(I also wrote an experimental `async`/`await` version and discarded it after testing that it was slower than a thread pool.)

### Macros

I consider [shortcodes](https://codex.wordpress.org/Shortcode) or macros essential for managing complex pages.
They make a page easier to create and especially to maintain over time compared to raw HTML markup.

The macros in page Markdown went through several iterations.
Tclssg allows you to write Tcl code in pages,
which I used for transclusion and a more compact syntax for images.
Those macros were gone with the switch to Fossil.

Macros in major version 3 of the site began as [edn](https://github.com/edn-format/edn) elements between `&lt;&lt;&lt; ... &gt;&gt;&gt;` [delimiters](/delimiters).
The first term determined the macro function to call, such as `include`, and the rest was passed as arguments.
The string returned from the function replaced the delimited expression.
These macros were only simple shortcodes without control flow.
With the migration to Python, I replaced edn with [`shlex`](https://docs.python.org/3/library/shlex.html).
The content required minimal changes because I didn&#39;t use complex edn.

Most recently, I replaced custom macros with Jinja.
This improved the macro system and, at the same time, reduced the line count in the SSG.
What pushed me to do it was the need to optimize the size and image quality of the [_Chrontendo_](/chrontendo) page,
which had grown large with the addition of episode covers.
A little testing of different image formats showed I both reduce the size and improve the quality with [AVIF](!W).
However, I needed a fallback for browsers that didn&#39;t support the still-new image format.
Jinja let me define a macro in the body of the page
that generated [`&lt;picture&gt;`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture) tags for AVIF images with JPEG fallback.

This is the macro:

```jinja
&lt;% macro cover(alt, src_prefix, attrs) %&gt;
&lt;picture&gt;
    &lt;source srcset=&#34;&lt;%= src_prefix =%&gt;.avif&#34; type=&#34;image/avif&#34; /&gt;
    &lt;img alt=&#34;&lt;%= alt =%&gt;&#34; src=&#34;&lt;%= src_prefix =%&gt;.jpg&#34; &lt;%= attrs =%&gt; /&gt;
&lt;/picture&gt;
&lt;% endmacro -%&gt;

```

The delimiters are `&lt;% ... %&gt;` and `&lt;%= ... =%&gt;`
in place of Jinja&#39;s standard `{% ... %}` and `{{ ... }}`
to avoid conflicts with things like the [Caddy](/caddy) templates.

### Code-based templates {#html-dsl}

I experimented with writing HTML in Python.
After the Jinja template grew in complexity, I started thinking about how to make it more maintainable, and especially how to detect problems early.
I looked into other template languages and was drawn to writing HTML in a Python [DSL](!W &#34;Domain-specific language&#34;).
This meant I could use standard tools like [Ruff](https://docs.astral.sh/ruff/) and [Pyright](https://github.com/microsoft/pyright) to check the template.

I compiled a list of [Python HTML DSLs](https://github.com/stars/dbohdan/lists/python-html-dsls) and later picked [htpy](https://github.com/pelme/htpy/) as the most mature.
I kept Jinja for [macros](#macros).
The switch resulted in a mild performance improvement.
Ruff&#39;s linting and Pyright&#39;s type checking were immediately useful with htpy.
The Ruff Formatter was not.
It didn&#39;t format `foo[bar, baz,]` as multiline the way it would `foo(bar, baz,)`.
I quickly wrapped the layout in `# fmt: off` and `# fmt: on` to disable automatic formatting.
To make the code more compact like HTML, I decided to indent with two spaces.

&lt;details&gt;
&lt;summary&gt;Formatting minified HTML&lt;/summary&gt;
htpy only output minified HTML, and I wanted the final HTML to be readable and indented consistently.
Consistent indentation was something I didn&#39;t originally have with Pandoc output inserted in a Jinja template.
Since whitespace matters in HTML, I looked for a formatter that wouldn&#39;t break significant whitespace.
My old go-to [tidy-html5](https://github.com/htacg/tidy-html5) had problems with it.
The most proven option I found was [Prettier](https://github.com/prettier/prettier).
I was hesitant to add a Node.js dependency to the project for logistics reasons.
It turned out to not be a hassle to manage.
The formatting step only took a couple of seconds.

Later in 2025, there was a series of [supply chain attacks](!W &#34;Supply chain attack&#34;) on NPM.
Prettier wasn&#39;t affected.
Nonetheless, I decided it was best to move away from NPM for something I didn&#39;t watch closely.
I found a suitable replacement in [dprint](https://github.com/dprint/dprint).
While Rust also has a culture of many small dependencies, it hasn&#39;t seen a similar level of attacks.
(Yet. Growth mindset.)
&lt;/details&gt;

In the end, I didn&#39;t find htpy code particuarly easy to read or edit.
I replaced htpy with a custom HTML DSL that was more readable to me and soon reverted to Jinja with regular HTML.

For comparison, here is a fragment of the page template in Jinja, htpy, and my custom DSL.
Jinja first:

```html
{% if show_metadata %}
&lt;footer&gt;
  &lt;div class=&#34;box metadata&#34;&gt;
    {% if &#34;index&#34; not in tags %}
    &lt;div class=&#34;metadata-dates&#34;&gt;
      Published {{ published }}, updated {{ updated }}.
    &lt;/div&gt; &lt;!-- .metadata-dates --&gt;
    {% endif %}

    &lt;!-- ... --&gt;
  &lt;/div&gt; &lt;!-- .box .metadata --&gt;
&lt;/footer&gt;
{% endif %}
```

htpy:

```python
page.show_metadata and footer[
  div(&#34;.box.metadata&#34;)[
    page.tags and &#34;index&#34; not in page.tags
    and div(&#34;.metadata-dates&#34;)[
      f&#34;Published {page.published}, updated {page.updated}.&#34;
    ],

    # ...
  ]
]
```

My custom DSL:

```python
footer(
    div(
        f&#34;Published {page.published}, updated {page.updated}.&#34;,
        class_=&#34;metadata-dates&#34;,
        if_=page.tags and &#34;index&#34; not in page.tags,
    ),

    # ...

    class_=&#34;box metadata&#34;,
    if_=page.show_metadata,
),
```


## Code license

Unless indicated otherwise, all of my code on the pages of this site is distributed under the [MIT License](/mit-license).

License pages like [/mit-license/2026](/mit-license/2026) are generated dynamically in an approach inspired by [mit-license.org](https://mit-license.org/).
This is documented on the [Caddy page](/caddy#licenses).


## Credits

The site is built with open-source software.
I am grateful to the authors and contributors of the following projects:
[Caddy](https://caddyserver.com/),
[Combine](https://combine.dropseed.dev/) ([`include_raw` for Jinja](https://github.com/dropseed/combine/blob/a3555dca7691cb76cd2b329b0758ddd70d6a6927/combine/jinja/include_raw.py)),
[dprint](https://dprint.dev/),
[GoatCounter](https://www.goatcounter.com/),
[highlight.js](https://highlightjs.org/),
[highlightjs-copy](https://github.com/arronhunt/highlightjs-copy),
[Jinja](https://jinja.palletsprojects.com/),
[Luacheck](https://github.com/lunarmodules/luacheck),
[net.lua](https://github.com/golgote/neturl),
[Pandoc](https://pandoc.org/),
[Poe the Poet](https://poethepoet.natn.io/),
[Python](https://python.org/),
[rsync](!W),
[Ruff](https://docs.astral.sh/ruff/),
[SingleFile CLI](https://github.com/gildas-lormeau/single-file-cli),
[StyLua](https://github.com/JohnnyMorganz/StyLua),
and [uv](https://docs.astral.sh/uv/).

## Page metadata

URL: <https://dbohdan.com/about.md>

Published 2023-11-06, updated 2026-03-22.

Tags:

- essay
- history
- me
- meta
- web
- web development
