Changelog

I have begun keeping a sporadic list of things I change on the website as a timeline/chronology of the evolution of this site. This is primarily for my own use so I can quickly glance back and glean some context about when and why I might have made a change. I’ve opted to post it publicly to the site in case anyone else finds it interesting.

A very minor change to resolve a bit of acute visual nit-picking. The space in eg, ‘p. 56’, ‘Ch. 7’, ‘Vol. 9’, or ‘(3rd edition)’, created by my attribution logic, could cause the two parts to appear on separate lines in a long attribution string or a narrow viewport. Switching to a non-breaking space ‘ ’ (visually indistinguishable from a normal space) means they will always appear side by side.

Unicode codepoint U+00A0, commonly abbreviated as NBSP.

While viewing my nonsense page with the network tab of the developer tools open I noticed the page was transferring a total of 15.8MB to load. The page loads hundreds of posts so this isn’t completely absurd, but it felt excessive. Turns out most of the weight was coming from a handful of PNG posters and screenshots.

In the general case, JPEGs are smaller than PNGs, but I didn’t want to hardcode conversion of all PNGs because there are times when lossless or transparent images are wanted.

I toyed around with a couple of implementations. My first prototype added a convert_to key to the asset manifests, but in the end I decided to simply convert on the basis of the output_path suffix being different than the input_path suffix in a manifest entry, allowing every stage of the build to seamlessly pick up the correct path to reference.

Switching those few heavy-weight PNGs to JPEG output cut the page weight from 15.8MB down to 5.18MB.

The most egregious offender was a lossless poster for Blade Runner (1982) coming in at 2.8MB initially, down to 240kB after conversion to JPEG. Shown below, the only change required in a manifest entry is to change the suffix/file-extension.

[27991f33-f40f-48ab-9690-0003db7b2038]
type = "poster"
title = "The poster for the film Blade Runner (1982)"
filepath = "/home/silas/library/images/posters/films/1982_Blade-Runner.png"
slug = "library/images/posters/films/1982_Blade-Runner.jpg"

The only change I needed to make to the build script was to check the colour-space when converting to JPEG and switch it to RGB in the case that the source colour-space was either ‘RGBA’ or ‘P’:

def process_image_parallel(input_data: Tuple[Path, Path, int]) -> None:
    input_image, output_path, output_width = input_data
    lock_path = output_path.with_suffix(".lock")
    lock = FileLock(str(lock_path))

    try:
        with lock:
            if output_path.exists():
                return

            os.makedirs(output_path.parent, exist_ok=True)

            with Image.open(input_image) as im:
                original_format = im.format
                im = ImageOps.exif_transpose(im)
                output_height = int(im.size[1] * (output_width / im.size[0]))

                with im.resize(
                    (output_width, output_height), Image.Resampling.LANCZOS
                ) as output_image:
                    if (
                        original_format != "JPEG"
                        and str(output_path).endswith("jpg")
                        and output_image.mode in ("RGBA", "P")
                    ):
                        output_image = output_image.convert("RGB")

                    output_image.save(output_path, quality=85, optimize=True)

            logger.debug(f"Processed image: {input_image} -> {output_path}")

    except OSError as e:
        logger.error(f"OS error processing {input_image}: {e}")
    except Exception as e:
        logger.error(f"Error processing {input_image}: {e}")
    finally:
        if lock_path.exists():
            try:
                lock_path.unlink()
            except OSError:
                pass

Added import references, restoring and improving the functionality of the ‘transclude’ reference that I previously deprecated and removed.

Has two modes, both of which support comments.

  • import, transcludes the content of the referenced document as is, without any additional markup or visual cue.
  • aside, creates an aside-type block that also includes an outbound link.

Both support an optional comment at the reference-site that is discarded during substitution.

The following snippet (minus the backslash),

{{ aside::501108be#getting arrested again }\}

Would be substituted with this markup,

{.aside}
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
The first time you get arrested your friends ask you, "What happened?!"\
The second time you get arrested it's already, "What did you do?"\
`<time class="smallcaps">`{=html}[November  3, 2024 3.13PM  Istanbul, Turkey](/2024/11/03/151330)`</time>`{=html}
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

Which would then be rendered into the following later in the build,

The first time you get arrested your friends ask you, “What happened?!”
The second time you get arrested it’s already, “What did you do?”

Both forms of import create a backlink in the referenced document, but only aside creates a visible outward link at the reference-site. This allows documents to be assembled seamlessly where that is a better fit than a call-out, but generally I expect I’ll use the aside mode more often, as I generally prefer for connections to be bi-directional.

Added syntax highlighting for code blocks using the pygments library.

Implementation was pretty straight forward after getting the regex right to treat ‘=html’ blocks as raw.

Changes located in the run_jotdown function and the new highlightcode_ function, as well as fiddling with css.

Click here to see changes to the build script
def highlight_code(code: str, language: str = None) -> str:
    """
    Highlight code using Pygments with specified or guessed language.
    """
    try:
        if language:
            lexer = get_lexer_by_name(language.lower())
        else:
            lexer = guess_lexer(code)

        formatter = HtmlFormatter(
            style=config["syntax_highlighting"]["style"],
            linenos="table"
            if config["syntax_highlighting"].get("line_numbers", False)
            else False,
            cssclass="highlight",
        )
        return highlight(code, lexer, formatter)
    except ClassNotFound:
        # If language isn't found, return code wrapped in pre tags
        return f"<pre><code>{code}</code></pre>"


def run_jotdown(plaintext: str) -> str:
    """
    Modified to handle code blocks with syntax highlighting.
    Fixed to properly handle both raw HTML and HTML code blocks.
    """
    CODE_BLOCK_RE = re.compile(r"\`\`\`\`*(=html|\s*(?:(\w+)\n))?(.*?)\`\`\`\`*", re.DOTALL)
    code_blocks = []
    marker_template = "§CODE_BLOCK_{}§"

    def save_code_block(match):
        raw_html_marker = match.group(1)
        language = match.group(2)
        code = match.group(3).strip()

        # Check if this is a raw HTML block
        if raw_html_marker == "=html":
            return f"\`\`\`=html\n{code}\n\`\`\`"

        # For all other cases, including 'html' language, highlight the code
        highlighted = highlight_code(code, language)
        marker = marker_template.format(len(code_blocks))
        code_blocks.append(highlighted)
        return f"\`\`\`=html\n{marker}\n\`\`\`"

    # First, replace all code blocks with markers
    processed_text = CODE_BLOCK_RE.sub(save_code_block, plaintext)

    # Run through jotdown
    html = run("jotdown", input=processed_text, text=True, capture_output=True).stdout

    # Replace markers with actual highlighted code
    for i, code in enumerate(code_blocks):
        marker = marker_template.format(i)
        html = html.replace(marker, code)

    return html

Note that in the above snippet, the backticks (`) have been escaped to prevent the markup on this page from breaking, remove the backslashes if you use this code yourself.

Click here to see the CSS I settled on
:root {
  --highlight-font-size: 0.9em;
  --highlight-border-radius: 6px;
  --highlight-padding: 1rem;
  --highlight-line-number-color: #6e7681;
}

.highlight {
  font-size: var(--highlight-font-size, 0.9em);
  border-radius: var(--highlight-border-radius, 6px);
  padding: var(--highlight-padding, 1rem);
}

.highlight pre {
  margin: 0;
  overflow-x: auto;
}

.highlight table {
  border-spacing: 0;
  border: none;
  margin: 0;
}

.highlight table td {
  padding: 0;
  border: none;
}

.highlight .linenos {
  user-select: none;
  padding-right: 1rem;
  color: var(--highlight-line-number-color);
  text-align: right;
  width: 1%;
  white-space: nowrap;
}

@media (prefers-color-scheme: dark) {
  .highlight {
    background: var(--code-background, #262220) !important;
  }
  pre {
    line-height: 125%;
  }
  td.linenos .normal {
    color: #4e4e4e;
    background-color: transparent;
    padding-left: 5px;
    padding-right: 5px;
  }
  span.linenos {
    color: #4e4e4e;
    background-color: transparent;
    padding-left: 5px;
    padding-right: 5px;
  }
  td.linenos .special {
    color: #8f9494;
    background-color: #ffffc0;
    padding-left: 5px;
    padding-right: 5px;
  }
  span.linenos.special {
    color: #8f9494;
    background-color: #ffffc0;
    padding-left: 5px;
    padding-right: 5px;
  }
  .highlight .hll {
    background-color: #ddd0c0;
  }
  .highlight {
    background: #262220;
    color: #ddd0c0;
  }
  .highlight .c {
    color: #70757a;
  } /* Comment */
  .highlight .err {
    color: #af5f5f;
  } /* Error */
  .highlight .esc {
    color: #ddd0c0;
  } /* Escape */
  .highlight .g {
    color: #ddd0c0;
  } /* Generic */
  .highlight .k {
    color: #919191;
  } /* Keyword */
  .highlight .l {
    color: #af875f;
  } /* Literal */
  .highlight .n {
    color: #ddd0c0;
  } /* Name */
  .highlight .o {
    color: #878787;
  } /* Operator */
  .highlight .x {
    color: #ddd0c0;
  } /* Other */
  .highlight .p {
    color: #ddd0c0;
  } /* Punctuation */
  .highlight .ch {
    color: #8f9f9f;
  } /* Comment.Hashbang */
  .highlight .cm {
    color: #70757a;
  } /* Comment.Multiline */
  .highlight .cp {
    color: #fdd0c0;
  } /* Comment.Preproc */
  .highlight .cpf {
    color: #c9b98f;
  } /* Comment.PreprocFile */
  .highlight .c1 {
    color: #70757a;
  } /* Comment.Single */
  .highlight .cs {
    color: #af5f5f;
  } /* Comment.Special */
  .highlight .gd {
    color: #bb6868;
  } /* Generic.Deleted */
  .highlight .ge {
    color: #ddd0c0;
    font-style: italic;
  } /* Generic.Emph */
  .highlight .ges {
    color: #ddd0c0;
  } /* Generic.EmphStrong */
  .highlight .gr {
    color: #af5f5f;
  } /* Generic.Error */
  .highlight .gh {
    color: #ddd0c0;
  } /* Generic.Heading */
  .highlight .gi {
    color: #849155;
  } /* Generic.Inserted */
  .highlight .go {
    color: #ddd0c0;
  } /* Generic.Output */
  .highlight .gp {
    color: #ddd0c0;
  } /* Generic.Prompt */
  .highlight .gs {
    color: #ddd0c0;
    font-weight: bold;
  } /* Generic.Strong */
  .highlight .gu {
    color: #ddd0c0;
  } /* Generic.Subheading */
  .highlight .gt {
    color: #af5f5f;
  } /* Generic.Traceback */
  .highlight .kc {
    color: #875f5f;
  } /* Keyword.Constant */
  .highlight .kd {
    color: #875f5f;
  } /* Keyword.Declaration */
  .highlight .kn {
    color: #875f5f;
  } /* Keyword.Namespace */
  .highlight .kp {
    color: #919191;
  } /* Keyword.Pseudo */
  .highlight .kr {
    color: #b46276;
  } /* Keyword.Reserved */
  .highlight .kt {
    color: #af875f;
  } /* Keyword.Type */
  .highlight .ld {
    color: #af875f;
  } /* Literal.Date */
  .highlight .m {
    color: #87afaf;
  } /* Literal.Number */
  .highlight .s {
    color: #c9b98f;
  } /* Literal.String */
  .highlight .na {
    color: #ddd0c0;
  } /* Name.Attribute */
  .highlight .nb {
    color: #ddd0c0;
  } /* Name.Builtin */
  .highlight .nc {
    color: #875f5f;
  } /* Name.Class */
  .highlight .no {
    color: #af8787;
  } /* Name.Constant */
  .highlight .nd {
    color: #fdd0c0;
  } /* Name.Decorator */
  .highlight .ni {
    color: #ddd0c0;
  } /* Name.Entity */
  .highlight .ne {
    color: #877575;
  } /* Name.Exception */
  .highlight .nf {
    color: #fdd0c0;
  } /* Name.Function */
  .highlight .nl {
    color: #ddd0c0;
  } /* Name.Label */
  .highlight .nn {
    color: #ddd0c0;
  } /* Name.Namespace */
  .highlight .nx {
    color: #ddd0c0;
  } /* Name.Other */
  .highlight .py {
    color: #dfaf87;
  } /* Name.Property */
  .highlight .nt {
    color: #87afaf;
  } /* Name.Tag */
  .highlight .nv {
    color: #ddd0c0;
  } /* Name.Variable */
  .highlight .ow {
    color: #878787;
  } /* Operator.Word */
  .highlight .pm {
    color: #ddd0c0;
  } /* Punctuation.Marker */
  .highlight .w {
    color: #ddd0c0;
  } /* Text.Whitespace */
  .highlight .mb {
    color: #87afaf;
  } /* Literal.Number.Bin */
  .highlight .mf {
    color: #87afaf;
  } /* Literal.Number.Float */
  .highlight .mh {
    color: #87afaf;
  } /* Literal.Number.Hex */
  .highlight .mi {
    color: #87afaf;
  } /* Literal.Number.Integer */
  .highlight .mo {
    color: #87afaf;
  } /* Literal.Number.Oct */
  .highlight .sa {
    color: #dfaf87;
  } /* Literal.String.Affix */
  .highlight .sb {
    color: #c9b98f;
  } /* Literal.String.Backtick */
  .highlight .sc {
    color: #c9b98f;
  } /* Literal.String.Char */
  .highlight .dl {
    color: #c9b98f;
  } /* Literal.String.Delimiter */
  .highlight .sd {
    color: #878787;
  } /* Literal.String.Doc */
  .highlight .s2 {
    color: #c9b98f;
  } /* Literal.String.Double */
  .highlight .se {
    color: #af5f5f;
  } /* Literal.String.Escape */
  .highlight .sh {
    color: #c9b98f;
  } /* Literal.String.Heredoc */
  .highlight .si {
    color: #af5f5f;
  } /* Literal.String.Interpol */
  .highlight .sx {
    color: #fdd0c0;
  } /* Literal.String.Other */
  .highlight .sr {
    color: #af5f5f;
  } /* Literal.String.Regex */
  .highlight .s1 {
    color: #c9b98f;
  } /* Literal.String.Single */
  .highlight .ss {
    color: #af5f5f;
  } /* Literal.String.Symbol */
  .highlight .bp {
    color: #87afaf;
  } /* Name.Builtin.Pseudo */
  .highlight .fm {
    color: #fdd0c0;
  } /* Name.Function.Magic */
  .highlight .vc {
    color: #ddd0c0;
  } /* Name.Variable.Class */
  .highlight .vg {
    color: #ddd0c0;
  } /* Name.Variable.Global */
  .highlight .vi {
    color: #ddd0c0;
  } /* Name.Variable.Instance */
  .highlight .vm {
    color: #ddd0c0;
  } /* Name.Variable.Magic */
  .highlight .il {
    color: #87afaf;
  } /* Literal.Number.Integer.Long */
}

@media (prefers-color-scheme: light) {
  .highlight {
    background: var(--code-background, #e7daca) !important;
  }
  pre {
    line-height: 125%;
  }
  td.linenos .normal {
    color: inherit;
    background-color: transparent;
    padding-left: 5px;
    padding-right: 5px;
  }
  span.linenos {
    color: inherit;
    background-color: transparent;
    padding-left: 5px;
    padding-right: 5px;
  }
  td.linenos .special {
    color: #000000;
    background-color: #ffffc0;
    padding-left: 5px;
    padding-right: 5px;
  }
  span.linenos.special {
    color: #000000;
    background-color: #ffffc0;
    padding-left: 5px;
    padding-right: 5px;
  }
  .highlight .hll {
    background-color: #ffffcc;
  }
  .highlight {
    background: #ffffff;
  }
  .highlight .c {
    color: #999988;
    font-style: italic;
  } /* Comment */
  .highlight .err {
    color: #a61717;
    background-color: #e3d2d2;
  } /* Error */
  .highlight .k {
    font-weight: bold;
  } /* Keyword */
  .highlight .o {
    font-weight: bold;
  } /* Operator */
  .highlight .ch {
    color: #999988;
    font-style: italic;
  } /* Comment.Hashbang */
  .highlight .cm {
    color: #999988;
    font-style: italic;
  } /* Comment.Multiline */
  .highlight .cp {
    color: #999999;
    font-weight: bold;
  } /* Comment.Preproc */
  .highlight .cpf {
    color: #999988;
    font-style: italic;
  } /* Comment.PreprocFile */
  .highlight .c1 {
    color: #999988;
    font-style: italic;
  } /* Comment.Single */
  .highlight .cs {
    color: #999999;
    font-weight: bold;
    font-style: italic;
  } /* Comment.Special */
  .highlight .gd {
    color: #000000;
    background-color: #ffdddd;
  } /* Generic.Deleted */
  .highlight .ge {
    font-style: italic;
  } /* Generic.Emph */
  .highlight .ges {
    font-weight: bold;
    font-style: italic;
  } /* Generic.EmphStrong */
  .highlight .gr {
    color: #aa0000;
  } /* Generic.Error */
  .highlight .gh {
    color: #999999;
  } /* Generic.Heading */
  .highlight .gi {
    color: #000000;
    background-color: #ddffdd;
  } /* Generic.Inserted */
  .highlight .go {
    color: #888888;
  } /* Generic.Output */
  .highlight .gp {
    color: #555555;
  } /* Generic.Prompt */
  .highlight .gs {
    font-weight: bold;
  } /* Generic.Strong */
  .highlight .gu {
    color: #aaaaaa;
  } /* Generic.Subheading */
  .highlight .gt {
    color: #aa0000;
  } /* Generic.Traceback */
  .highlight .kc {
    font-weight: bold;
  } /* Keyword.Constant */
  .highlight .kd {
    font-weight: bold;
  } /* Keyword.Declaration */
  .highlight .kn {
    font-weight: bold;
  } /* Keyword.Namespace */
  .highlight .kp {
    font-weight: bold;
  } /* Keyword.Pseudo */
  .highlight .kr {
    font-weight: bold;
  } /* Keyword.Reserved */
  .highlight .kt {
    color: #445588;
    font-weight: bold;
  } /* Keyword.Type */
  .highlight .m {
    color: #009999;
  } /* Literal.Number */
  .highlight .s {
    color: #bb8844;
  } /* Literal.String */
  .highlight .na {
    color: #008080;
  } /* Name.Attribute */
  .highlight .nb {
    color: #999999;
  } /* Name.Builtin */
  .highlight .nc {
    color: #445588;
    font-weight: bold;
  } /* Name.Class */
  .highlight .no {
    color: #008080;
  } /* Name.Constant */
  .highlight .ni {
    color: #800080;
  } /* Name.Entity */
  .highlight .ne {
    color: #990000;
    font-weight: bold;
  } /* Name.Exception */
  .highlight .nf {
    color: #990000;
    font-weight: bold;
  } /* Name.Function */
  .highlight .nn {
    color: #555555;
  } /* Name.Namespace */
  .highlight .nt {
    color: #000080;
  } /* Name.Tag */
  .highlight .nv {
    color: #008080;
  } /* Name.Variable */
  .highlight .ow {
    font-weight: bold;
  } /* Operator.Word */
  .highlight .w {
    color: #bbbbbb;
  } /* Text.Whitespace */
  .highlight .mb {
    color: #009999;
  } /* Literal.Number.Bin */
  .highlight .mf {
    color: #009999;
  } /* Literal.Number.Float */
  .highlight .mh {
    color: #009999;
  } /* Literal.Number.Hex */
  .highlight .mi {
    color: #009999;
  } /* Literal.Number.Integer */
  .highlight .mo {
    color: #009999;
  } /* Literal.Number.Oct */
  .highlight .sa {
    color: #bb8844;
  } /* Literal.String.Affix */
  .highlight .sb {
    color: #bb8844;
  } /* Literal.String.Backtick */
  .highlight .sc {
    color: #bb8844;
  } /* Literal.String.Char */
  .highlight .dl {
    color: #bb8844;
  } /* Literal.String.Delimiter */
  .highlight .sd {
    color: #bb8844;
  } /* Literal.String.Doc */
  .highlight .s2 {
    color: #bb8844;
  } /* Literal.String.Double */
  .highlight .se {
    color: #bb8844;
  } /* Literal.String.Escape */
  .highlight .sh {
    color: #bb8844;
  } /* Literal.String.Heredoc */
  .highlight .si {
    color: #bb8844;
  } /* Literal.String.Interpol */
  .highlight .sx {
    color: #bb8844;
  } /* Literal.String.Other */
  .highlight .sr {
    color: #808000;
  } /* Literal.String.Regex */
  .highlight .s1 {
    color: #bb8844;
  } /* Literal.String.Single */
  .highlight .ss {
    color: #bb8844;
  } /* Literal.String.Symbol */
  .highlight .bp {
    color: #999999;
  } /* Name.Builtin.Pseudo */
  .highlight .fm {
    color: #990000;
    font-weight: bold;
  } /* Name.Function.Magic */
  .highlight .vc {
    color: #008080;
  } /* Name.Variable.Class */
  .highlight .vg {
    color: #008080;
  } /* Name.Variable.Global */
  .highlight .vi {
    color: #008080;
  } /* Name.Variable.Instance */
  .highlight .vm {
    color: #008080;
  } /* Name.Variable.Magic */
  .highlight .il {
    color: #009999;
  } /* Literal.Number.Integer.Long */
}

Changed body font to Source Serif Pro.

I was playing around with serif fonts a bit and, landing on Source Serif, I found something that just fit with my style in a way that no others did.

Visually quite a big change, the previous body font was the very modern Inter, a serif font, which I have kept for eg. titles for now.

Just noticed that Source Serif Pro isn’t handling left-single-smart-quotes correctly when they’re in italics, instead falling back to the right-quote/apostrophe. A pretty niche case, but I care about those little details. I’ll have to investigate whether it’s just missing from the font altogether, or if I inadvertently removed it when sub-setting it for my uses.

’example’

(If the above is no longer wrong, it’s because I’ve fixed the issue.)

Switched map tiles provider from Thunder Forest Outdoors to maptiler.

Much prefer the topographic styling of these maps. They even offer a set of satellite tiles if I decide I want that option in the future.

Before and after:

.then(() => {
    var map = L.map('map');
    L.tileLayer('https://tile.thunderforest.com/outdoors/{z}/{x}/{y}.png?apikey=APIKEY', {
        maxZoom: 19,
        attribution: null
    }).addTo(map);
.then(() => {
    var map = L.map('map');
    L.tileLayer('https://api.maptiler.com/maps/topo-v2/{z}/{x}/{y}.png?key=APIKEY', {
        maxZoom: 19,
        attribution: null
    }).addTo(map);

Created a ‘main’ feed to supplement automatic collections

Previously the main feed on the site, linked to from the <head>, was ‘everything’ but this is a bit of a firehose with all my quotes, notes, nonsense, and links et al, so I’ve created a ‘main’ feed with a few chosen collections included. Currently it mimics the front page.

You can of course still pick and choose which specific collection/tag feeds you want to subscribe to from the feeds page.

Switched to TOML.

Previously I was using a pseudo-YAML for document frontmatter and JSON for the asset manifests. I’ve switched both to TOML, partly for it’s simpler parsing but really entirely because I much prefer its readability.

Now every document and asset is contained in TOML documents. A blog post is itself a completely valid TOML document that easily serialises into a Python struct. This has the advantage of bringing stricter validation to all data, and also opens up opportunities to build documents from structured data — something I’ve been wanting to do with data for the walk among other things.

The only issue I have with TOML is that there is no spec compliant way to have multiple TOML documents in a single file. Eventually this limitation may drive me back to YAML, but I don’t regret the migration at all. Now that I’m using a strict schema it will be trivial to migrate to JSON5, YAML, or even XML in the future if I wanted to.

Replaced my previous hand rolled site search that brute-forced an ungainly blob of JSON.

Opted for pagefind as I knew I wanted to stick with static search that I can compile at build time. Will tweak styles later but you can see it working already at the search page.

Much faster and more robust.

By default it grabs the title from the first <h1> on the page which strikes me as odd. Not all my pages — like those I call nonsense — have a <h1>. Workaround: added one of its data attributes to the title element in my document head template and set the result icon to my site favicon for all pages that have no images:

<head>
    {% block head -%}
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="generator" content="The humble hands of Silas" />
    <meta data-pagefind-default-meta="image[content]" content="/favicon.png" />
<title data-pagefind-meta="title">
    {%- block title -%}
        {% if page -%}
            {{ page.title -}}
Added dependency:    pagefind
Removed dependency:  my terrible javascript skills

Monday, 19th November 2024 20:44:22-08:00

Added the data-pagefind-ignore attribute to collections so that collection pages don’t rank in search results.

<section data-pagefind-ignore class="collection-section">
{% for item in items %}
<article class="collection {{ style }}">

Used Anthropics Claude 3.5 Sonnet LLM to iterate on a new JavaScript lightbox/gallery for my site that solves the quirks and annoyances of my previous attempt. Namely it now closes on browser-back as users expect, prevents scroll when the lightbox is open, and a couple other things. Simon Willison is right, this level of assistance is a magical boost when it comes to quick development of discrete code/tools.

Removed dependency: lightboxjs

Deprecated and removed the somewhat ambiguous id:: substitution reference in favour of slug:: so that it is always clear what a reference will be substituted with. This change also obviated the need for the misc:: handler, so that was culled from the code too.

Replaced author metadata element with creator as I gradually formalise the metadata terms used in my own infrastructure. creator generalises much better to a broader media set. For example: if I’m referencing a video then author does not make a great deal of sense.

Added the lightgallery library to the site for multi-image galleries, replacing my previous self built solution.

While I slightly mourn adding a dependency, it’s all self-hosted, and the resulting lightbox is much more robust and suited to the growing audience of this site.

Switched from markdown to Djot (See SEP 13: Switch to a more reasonable source markup language) for lightweight document markup. Djot offers many conveniences in terms of consistency and reasonability.

This change required syntactical tweaks be made to virtually all documents, but the pay off in terms of having a thoughtfully considered, simple, robust syntax feels significant.

In Djot raw HTML has to be explicitly declared. This has numerous advantages, again mostly having to do with predictability. No longer is it a guessing game as to whether output will be mangled or needs escaping.

Speed of HTML generation has been improved despite a rough implementation that currently shells out to an external Djot parser (Jotdown) as there is no Djot parser currently written for Python.

Not logging a specific change, simply recording here that the average full site build time now exceeds 2.5 seconds. This is with a total of 84457 words published to the site across 5 document types, 17 document classes, 28 document tags, and with 182 internal links between documents. Not any kind of problem, but thinking long term it makes sense to finish implementing SEP 35: Incremental builds in order to maintain the tight, iterative feedback loop of revise/rebuild that works so well for me.

10:52:29 Imported modules in 0.143 seconds
10:52:29 Setup environment in 0.0 seconds
10:52:29 Defined functions in 0.0 seconds
10:52:30 Ingested documents in 0.552 seconds
10:52:30 Inserted substitutes in 0.139 seconds
10:52:31 Generated HTML in 1.191 seconds
10:52:31 Built backlinks in 0.003 seconds
10:52:31 Built collections in 0.002 seconds
10:52:32 Wrote out HTML files in 0.403 seconds
10:52:32 Built feed stylesheet in 0.002 seconds
10:52:32 Built feeds in 0.129 seconds
10:52:32 Built sitemap in 0.003 seconds
10:52:32 Copied assets in 0.015 seconds
10:52:32 Processed assets in 0.015 seconds
10:52:32 Built in 2.604 seconds

Partial rewrite, significant changes to major subsystems.

Overhaul backlink extraction in order to drop BeautifulSoup as a dependency, (See ✅ SEP 16: Extract backlinks from plaintext). New system works on the plaintext documents directly which offers greater flexibility, consistency, as well as being lighter and faster.

Add collision checking (✅ SEP 14: UUID collision checking) amongst UUIDs, necessary for several reasons including effects on SEPs 12 and 16.

Add insert_substitutes(): function for all pre-processing of plaintext prior to HTML conversion. The major component within is currently the implementation of internal UUID referencing.

Overhaul build_html() function into a new, leaner, simpler generate_html() function made possible by the changes above.

Secondary benefit: 30% faster builds

Added the Inter font, prefer it’s spacing and readability. Currently only supporting WOFF2 as unsupported clients can always fall back to system defaults. My only gripe with Inter is I feel its italic type is much too subtle for a sans-serif font.

Reverted change to feeds made on Mar 20, 2022.

Take silasjelley.com/feeds/notes for example, why not place its feed at silasjelley.com/notes/feed? In that example I agree that the latter structure is just as elegant and useful as the former. But what if I want to create arbitrary feeds such as a feed that combines the notes and glossary collections? Where would I put that? Situating all feeds beneath silasjelley.com/feeds allows for taxonomical freedom. In this case: silasjelley.com/feeds/notes+glossary.