site

design, as with engineering, is about trade-offs. trade-offs involve making decisions; those decisions lead to form. fundamentally, design is a question about what matters.

for the longest time, my website was a portfolio. this gave me clarity; decisions could be anchored against a clear use case and a clear audience.

but a portfolio site and a personal site are not the same thing. a portfolio is built for legibility, palatability, asphyxiating oneself with a neat little bow: it is a marketing tool. what is your story? your impact? your personal brand narrative?

there is an art to it, but i digress.

i realized i wanted a website for myself rather than about myself. i had to build (and scrap) several overwrought website designs to arrive at this realization. the form of this website is the result of decisions made in service of that goal.

in the past, i built my site for the spectacle of being seen by others. now, much of my focus is on the ergonomics of this website as an authoring environment, and the affordances that scaffold that information.

there is a formal, aesthetic dimension to these scaffolds, as well as how they mediate interaction. this website is an exercise in exploring (and attempting to direct attention toward) their essential character.

  • avoid complexity
  • let content evolve
  • learn by prototyping

to be covered

  • transclusion
  • EIAV (Everything is a View)

breadcrumbs

2026-08-05

nearly all web browsers do a poor job contextualizing individual web pages into their active browsing context. we often follow from one link to another in a complex, elongating, and branching path: this is particularly true if we’re browsing for research or learning (think “going down a rabbit hole”). browsers ultimately collapse the nuances of these traces down to a flat collection of things, relying on the user to keep context alive in their own head.

this complaint is not new, and people have also shared no shortage of concepts and demos attempting to address this challenge. ultimately this is not about browsers anyway. but you should now understand why you’ll see a small trail of breadcrumbs tracking your path when you move from page to page. i see it as a meaningful addition and a way to experiment in the ergonomics and structuring of information (which, in a very true sense, is what this entire site is ultimately about).

SvelteKit provides a robust set of navigation interceptors that allow us to do meaningful work when a user navigates from one page to the next.

beforeNavigate fires before a user is navigated to the next page and exposes an event object which provides us information about the request, like which page they are navigating to and they are navigating from:

beforeNavigate((currentPg) => {
    if (!currentPg.to) return;
    if (currentPg.willUnload) return;
    let toId = currentPg.to.route.id || '';
    let toSlug = currentPg.to.params?.slug || '';
    let fromSlug = currentPg.from?.params?.slug || '';
}

note that it’s possible for some of these to be null, hence the escaping. from.route.id can be null if a user is navigating away from an error page, for example. to avoid issues with navigating away to a different page, we also guard against various cases where subsequent logic may cause issues, like when navigating away to another page.

i’m using a combination of Svelte’s stores and sessionStorage to create and persist breadcrumb information across reloads. if you navigate quickly to another domain and then navigate back to the last page you visited on my site, your breadcrumbs (should) be preserved.

in constructing and modifying our breadcrumb path, we need to recognize that the user might be navigating backward in the trail that they had already established. moreover, with addition of feeds to the site, our logic needs to account for different routing paths (e.g. /feed/* or /entry/*):

routeHistory.update((h) => {
    if (toId == '/') {
        return [];
    } else if (h.some((entry) => entry.includes(toSlug))) {
        return h.slice(
            0,
            h.findIndex((entry) => entry.includes(toSlug))
        );
    } else if (isFeed) {
        return [...h, `/feed/${fromSlug}`];
    } else {
        return [...h, `/entry/${fromSlug}`];
    }
});

we subscribe sessionStorage to automatically update as changes are made to the path stored in the Svelte routeHistory store.

import { writable } from 'svelte/store';
import { browser } from '$app/environment';

const initialValue = browser && sessionStorage.getItem('storedHistory')
    ? JSON.parse(sessionStorage.getItem('storedHistory')!)
    : [];

export const routeHistory = writable<string[]>(initialValue);

if (browser) {
    routeHistory.subscribe((value) => {
        sessionStorage.setItem('storedHistory', JSON.stringify(value));
    });
}

we need to appropriately clear routeHistory as needed. for example, in the case where a user navigates to another domain but then chooses to navigate back to the home page of the site, we clear out the breadcrumbs so the path does not erroneously display the last session. this is achieved by clearing the trail every time the user visits the main page:

import { routeHistory } from '$lib/stores/navigationHistory';

routeHistory.update(() => {
    return [];
});

over time, i want to be able to potentially provide a means for for people to navigate forward using the same affordance.