---
title: Essays
description: "On hypermedia and building for the web."
---

import { getFolder } from '../../lib/content';

export const essayFolder = await getFolder('essays');
export const localEssays = essayFolder.files;

export const externalEssays = [
    {title: "HATEOAS Is For Humans", url: "https://intercoolerjs.org/2016/05/08/hatoeas-is-for-humans.html", author: "Carson Gross", date: "2016-05-08", tags: "foundations"},
    {title: "Taking HTML Seriously", url: "https://intercoolerjs.org/2020/01/14/taking-html-seriously", author: "Carson Gross", date: "2020-01-14", tags: "foundations"},
    {title: "The API Churn/Security Trade-off", url: "https://intercoolerjs.org/2016/02/17/api-churn-vs-security.html", author: "Carson Gross", date: "2016-02-17", tags: "the-case-for-hypermedia"},
    {title: "A Modest Critique of htmx", url: "https://chrisdone.com/posts/htmx-critique/", author: "Chris Done", date: "2024-10-01", tags: "counterpoints"},
    {title: "A Modest Critique of htmx (Response)", url: "https://news.ycombinator.com/item?id=41782080", date: "2024-10-01", tags: "counterpoints"},
    {title: "I tried HTMX, then ditched it", url: "https://ajmoon.com/posts/mesh-i-tried-htmx-then-ditched-it", author: "AJ Moon", tags: "counterpoints"},
    {title: "Is Rendering JSON More Efficient Than HTML?", url: "https://github.com/1cg/html-json-speed-comparison", author: "Carson Gross", tags: "guides"},
    {title: "Is JSON Smaller Than HTML?", url: "https://github.com/1cg/html-json-size-comparison", author: "Carson Gross", tags: "guides"},
    {title: "The Grug Brained Developer", url: "https://grugbrain.dev", author: "Carson Gross", tags: "simplicity"},
    {title: "htmx Implementation Deep Dive", url: "https://www.youtube.com/watch?v=javGxN-h9VQ", author: "Carson Gross", tags: "simplicity"},
    {title: "A File Structure For The Complex, The Changing and the Indeterminate", url: "https://dl.acm.org/doi/pdf/10.1145/800197.806036", author: "Ted Nelson", date: "1965-01-01", tags: "research"},
    {title: "The Mother Of All Demos", url: "https://www.youtube.com/watch?v=B6rKUf9DWRI", author: "Doug Engelbart", date: "1968-01-01", tags: "research"},
    {title: "The First Web Page", url: "http://info.cern.ch/hypertext/WWW/TheProject.html", date: "1991-01-01", tags: "research"},
    {title: "Architectural Styles and the Design of Network-based Software Architectures", url: "https://ics.uci.edu/~fielding/pubs/dissertation/top.htm", author: "Roy Fielding", date: "2000-01-01", tags: "research"},
    {title: "State of the Art Review on Hypermedia Issues and Applications", url: "https://web.archive.org/web/20240428215142/https://paul.luon.net/hypermedia/index.html", date: "2006-01-01", tags: "research"},
    {title: "How Primer changed JavaScript at Facebook", url: "https://youtu.be/BZmfCjtv6cM", author: "Makinde Adeagbo & Tom Occhino", date: "2010-01-01", tags: "research"},
    {title: "Hypermedia Controls: Feral to Formal", url: "https://dl.acm.org/doi/pdf/10.1145/3648188.3675127", author: "ACM HT'24", date: "2024-01-01", tags: "research"},
    {title: "Preserving REST-ful Visibility With Generalized Hypermedia Controls", url: "https://hypermedia.cs.montana.edu/papers/preserving-restful.pdf", author: "ACM SIGWEB", date: "2024-01-01", tags: "research"},
];

export const fmtDate = (d) => {
    if (!d) return undefined;
    return new Date(d).toLocaleDateString('en-US', {year: 'numeric', month: 'short', day: 'numeric'});
};

export const allEssays = [
    ...localEssays.map(e => ({
        title: e.frontmatter.title,
        url: e.url,
        author: e.frontmatter.authors?.join(', '),
        date: fmtDate(e.frontmatter.created),
        rawDate: e.frontmatter.created,
        tags: Array.isArray(e.frontmatter.tags) ? e.frontmatter.tags.join(' ') : '',
        external: false,
    })),
    ...externalEssays.map(e => ({
        title: e.title,
        url: e.url,
        author: e.author,
        date: fmtDate(e.date),
        rawDate: e.date ? new Date(e.date) : null,
        tags: e.tags,
        external: true,
    })),
].sort((a, b) => {
    if (!a.rawDate && !b.rawDate) return 0;
    if (!a.rawDate) return 1;
    if (!b.rawDate) return 1;
    return new Date(b.rawDate).getTime() - new Date(a.rawDate).getTime();
});

export const GROUPS = [
    {id: 'foundations', label: 'Hypermedia and REST'},
    {id: 'the-case-for-hypermedia', label: 'Why Hypermedia?'},
    {id: 'case-studies', label: 'Real World htmx Experiences'},
    {id: 'counterpoints', label: 'On The Other Hand…'},
    {id: 'guides', label: 'Building Hypermedia Applications'},
    {id: 'simplicity', label: 'Complexity Very, Very Bad'},
    {id: 'research', label: 'Hypermedia Research'},
];

export const recentEssays = allEssays.filter(e => e.rawDate).slice(0, 5);
export const grouped = GROUPS.map(g => ({
    ...g,
    essays: allEssays.filter(e => (e.tags || '').includes(g.id)),
})).filter(g => g.essays.length > 0);

export const essayRow = (essay) => ({
    class: "stretch-link",
    'data-filter': `${essay.title} ${essay.author || ''} ${essay.tags || ''}`.toLowerCase(),
});

<div class="not-prose mb-6">
    <input
        type="text"
        placeholder="Search essays..."
        autocomplete="off"
        class="w-full px-3 py-2 text-sm border border-neutral-200 dark:border-neutral-800 rounded-md bg-white dark:bg-neutral-900 text-neutral-800 dark:text-neutral-200 placeholder:text-neutral-400 dark:placeholder:text-neutral-500 focus:outline-none focus:ring-2 focus:ring-neutral-300/50 dark:focus:ring-neutral-600/50 focus:border-neutral-300 dark:focus:border-neutral-600"
        _="install ActiveSearch"
    />
</div>

<section role="group">

<h2 id="most-recent">Most Recent</h2>

<table>
    <tbody>
        {recentEssays.map(essay => (
            <tr {...essayRow(essay)}>
                <td><a href={essay.url} {...(essay.external && {target: "_blank", rel: "noopener noreferrer"})}>{essay.title}{essay.external && ' ↗'}</a></td>
                <td class="whitespace-nowrap text-neutral-650 dark:text-neutral-350">{essay.author || ''}</td>
                <td class="whitespace-nowrap tabular-nums text-neutral-650 dark:text-neutral-350">{essay.date}</td>
            </tr>
        ))}
    </tbody>
</table>

</section>

{grouped.map(group => (
<section role="group">

<h2 id={group.id}>{group.label}</h2>

<table>
    <tbody>
        {group.essays.map(essay => (
            <tr {...essayRow(essay)}>
                <td><a href={essay.url} {...(essay.external && {target: "_blank", rel: "noopener noreferrer"})}>{essay.title}{essay.external && ' ↗'}</a></td>
                <td class="whitespace-nowrap text-neutral-650 dark:text-neutral-350">{essay.author || ''}</td>
                <td class="whitespace-nowrap tabular-nums text-neutral-650 dark:text-neutral-350">{essay.date || ''}</td>
            </tr>
        ))}
    </tbody>
</table>

</section>
))}
