Welcome to my little slice of the internet. If you want to know more about me there’s an about page. Want to send me a message? Get in touch here.

Some things on this site:

Recently on builtwith.coffee:

  • The Platinum is in Another Castle

    [Jan 20, 2022]

    I played two games recently: Death’s Door and The Forgotten City, both on the Playstation 5. Both were good (Death’s Door I’d even say great), but one thing they have in common that I don’t love about modern video games is the notion that the story is over, but wait, there’s more! More quests, or endings, or collectibles, or twists on playing the game you just played but slightly differently.

    I kind of liked old video games where there was an end, and you could say you beat it, or, more likely with some Nintendo-hard games, didn’t.

    The Contra end screen.

  • Hygge

    [Jan 18, 2022]

    Maggie and Jake on the couch together.

  • Annual maintenance

    [Jan 15, 2022]

    In January of 2020 we bought a new car, a 2019 Volkswagen GTI. It’s become a little touchpoint on the pandemic over the years.


    We bought the car because, while we had gotten by with only one car for a few years at that point, Andrea had started a new job that required one, and while I was already mostly remote, various kid related tasks were outside the range of convenience for seriously considering walking.


    Volkswagen sets its service schedule as 10,000 miles or 1 year. If it had been used to commute every day, plus various side trips, it probably would have been around that mileage mark. Instead, after having it for 2 months, we found out we wouldn’t be commuting for a while, which became a long while, which by January was looking pretty much like never, ever again. It had under 3,000 miles on it. I waited at the dealership for the service because at that point it was a bit of a novelty to be out in public. It was still pre-vaccine so everyone waiting was avoiding the other people waiting, and most of the people working at the dealership were over it. They had a VW Bug by the front door. I went to Trader Joe’s on the way home, and there was a queue out front to limit the number of people in the store at any time. A cheerful Trader Joe’s employee handed you a cart when they let you in.

    a sketch of the VW Bug I drew while waiting last year.


    2 years, or 20,000 miles. We’re still well under 10,000 miles despite using the GTI for every “trip” we’ve taken in the last two years. They replaced the VW Bug in the lobby with a VW Bus. Everyone is, once again, wearing masks, but it doesn’t feel weird to drink coffee while waiting, or have a conversation at a reasonable, human distance. There’s a 2022 GTI in the dealership in the flat-gray color I wanted to get ours in1, but two years later I wouldn’t even think about a new gas powered car. We’ll drive the two we have until they become dinosaurs running on dinosaurs. I went to Trader Joe’s on the way home. There was no queue outside, but the store was its normal weekend levels of packed. I had to get my own cart.

    What’s my best guess for January, 2023? No masks. Finally hit 10,000 miles but still well under 20,000 and impossibly far from 30,000. I’ll still go to Trader Joe’s after. I’ll be smart enough to do this on a weekday so I don’t get stuck in the weekend crowd. Things change, but things stay the same.

    1. One upside to buying a car where model year = current year -1 is that they can be cheaper, or have better financing deals. The downside is you’re usually stuck picking from black or white for colors.
  • Adding search to a static site

    [Jan 10, 2022]

    This blog post is based off this tweet reply. Because I read it and thought “oh, I already did write about this!” then tried searching for it on the site and couldn’t find it by clicking around, so I searched in the files for the site on my computer and couldn’t find it, then searched across my entire computer and found a long blog post about using Lunr to search gifs that I never finished1.

    So, here’s a finished version in a similar vein: let’s add search functionality to this site using Lunr. We need to build an index for Lunr at build time, which means we need some piece of code that will be triggered to begin looking for items to add. I did this by creating a new Search page (/search). I copied the logic from my index page and removed some logic that isn’t relevant, but the code here gets all of the markdown files in my content folder and then loops through them. In this case we want to build a “document” structure for Lunr to be able to search through:

    1export async function getStaticProps({ ...ctx }) { 2 //get posts & context from folder 3 const posts = ((context) => { 4 const keys = context.keys(); 5 const values = keys.map(context); 6 const data = keys.map((key, index) => { 7 const value = values[index]; 8 // Parse yaml metadata & markdownbody in document 9 const document = matter(value.default); 10 document.data.date = singleDateFormat(document.data.date); 11 if (document.data.finished) { 12 document.data.finished = singleDateFormat(document.data.finished); 13 } 14 15 return { 16 title: document.data.title || "", 17 body: document.content || "", 18 slug: key.replace(".md", "").substring(1), 19 }; 20 }); 21 22 return data; 23 })(require.context("../content", true, /\.\/.*\.md$/)); 24 25 return { 26 props: { 27 searchData: posts, 28 }, 29 }; 30}

    In the actual Search page component itself, we build the Lunr index from the searchData prop:

    1 const lunrIndex = lunr(function () { 2 this.ref("title"); 3 this.field("title"); 4 this.field("body"); 5 this.field("slug"); 6 7 props.searchData.forEach(function (doc) { 8 this.add(doc); 9 }, this); 10});

    After this we wire up the Search page to have a text input that, on change, searches the Lunr index. This is the code for the entire page2:

    1function Search(props) { 2 const [searchString, setSearchString] = useState(""); 3 const [searchResults, setSearchResults] = useState(); 4 var lunrIndex = lunr(function () { 5 this.ref("title"); 6 this.field("title"); 7 this.field("body"); 8 this.field("slug"); 9 10 props.searchData.forEach(function (doc) { 11 this.add(doc); 12 }, this); 13 }); 14 15 const searchLunr = (e) => { 16 setSearchString(e.target.value); 17 setSearchResults(lunrIndex.search(e.target.value)); 18 }; 19 20 const searchResultsList = () => { 21 if (!searchResults || !searchResults.length) { 22 return null; 23 } 24 return searchResults.map((result) => { 25 const slug = props.searchData.find( 26 (data) => data.title === result.ref 27 ).slug; 28 return ( 29 <li key={slug}> 30 <p>{result.ref}</p> 31 <Link href={`/${slug}`}>{slug}</Link> 32 </li> 33 ); 34 }); 35 }; 36 37 return ( 38 <Layout> 39 <ReadingContent> 40 <h1>Search</h1> 41 <input type="text" value={searchString} onChange={searchLunr} /> 42 <h2>Results</h2> 43 <ul>{searchResultsList()}</ul> 44 </ReadingContent> 45 </Layout> 46 ); 47}

    So that’s cool, and it works, but one thing I noticed here is that this Lunr search runs against the full text of the content, but once you click through, if the post is long it’s not immediately obvious where the term you were searching for appears. Let’s do something about that, at least for blog content.

    First, let’s append a query parameter to the link we get from our search results, like this:

    1<Link href={`/${slug}?searchterm=${searchString}`}>{slug}</Link>

    This blog is built with Next so for me getting that query parameter on any given page looks something like:

    1import { useRouter } from "next/router"; 2 3// inside the component 4const { query } = useRouter(); 5const searchTerm = query.searchterm;

    All of the post content is written in Markdown, so we can pass the post body to a function that looks for the search term in the string and replaces it, like this:

    1const wrapSearchTerm = (string) => { 2 if (searchTerm) { 3 const regex = new RegExp(`${searchTerm}`, "g"); 4 return string.replace( 5 regex, 6 `<span class="searchTerm">${searchTerm}</span>` 7 ); 8 } 9 return string; 10 };

    One thing I realized was doing it this way breaks links that might have the search term in it, because the searching is happening on the unprocessed Markdown. Running it post processing would be better, but to be lazy, let’s just search for the word with a reasonable set of boundaries to ignore, like this:

    1const regex = new RegExp(`${searchTerm}(?![-_])`, "g");`

    which eliminates most slugified versions of the word. It’s still case sensitive but the 80/20 among its users (me) says it’s good enough to ship.

    1. For good reason, the original post was about recreating Giphy using Lunr and lambda functions. Those parts were fine, but I think I lost the thread trying to make a Slackbot to tie them together.
    2. This is React with some wrapping components like Layout and ReadingContent that only handle styling.
  • Klara And The Sun

    [Jan 8, 2022]
  • ⎋ Using a mild Twitter addiction to actually get things done @ nick.comer.io

    [Jan 3, 2022]

    That means that I should just marshal some self-control and just tone down the usage of these apps with nothing but my will, right?! Wroooong! We’re gonna make the technology do it for us!

    Check it out
  • A Sunday Post that is not a Weeknotes Post

    [Jan 2, 2022]

    I said in my last weeknotes post I didn’t want to do weeknotes any more, so this isn’t that, it’s just a post that happens to be on a Sunday that maybe talks about the past week.

    • We were at my parents house on Monday and Tuesday. This was considerably better than last year’s Zoom-Christmas for lots of reasons but I have to say one big reason is because my mother makes lasagna for Christmas and that’s very hard to reproduce digitally.
    • As far as we know, no one has gotten COVID in this house. That said school starts back up tomorrow and based on the numbers it seems like either we’ve already had it and we just didn’t notice or we will by the end of January.
    • I read _If You Lived Here You’d Be Home By Now _ yesterday, and posted some bits I liked from the book. It’s an interesting story — I liked Christopher Ingraham’s posts for the Washington Post and had read the original article that started the whole story so I enjoyed the book. The book was written pre-Pandemic, and a fair amount of his complaints about living in the greater DC area revolved around commuting. Housing costs were the other big issue, so maybe that last two years have been a wash but I should go see if he’s had any follow-ups on his thoughts about moving the Minnesota.
    • Starting and finishing a book on January 1st means I’m currently on track to read 365 books this year.
    • Lorelei has restarted and beat Donut County every day for the last five days.
    • It was Andrea’s birthday this week. Like every year, we fail to account for the fact that her birthday is on New Year’s Eve and without at least a few minutes of prior planning, getting food from a restaurant is not going to happen.
    • Also like every year we’ve gone to bed well past midnight almost every night this week, so getting back to our routine tomorrow is going to be painful.
  • If You Lived Here You’d Be Home By Now

    [Jan 1, 2022]
  • Chatter

    [Dec 27, 2021]
  • ⎋ The Future Is Not Only Useless, It’s Expensive @ www.gawker.com

    [Dec 26, 2021]

    NFTs are the human capacity for visual expression as understood by the guy at the vape store. There is the predominance of skulls. There is the melodramatic deployment of the visual language of video games, as if romanticism had been invented by people who never went outside.

    via Robin Rendle

    Check it out
Back to top