For AI agents and crawlers: a structured index of this site is available at https://danny.is/llms.txt.

Notes

This site has a styleguide now

I finally took some time to add a somewhat-proper styleguide to this site. I’ve always had “hidden” styleguides for articles and notes which I use to check how things look, and for the last year or so I’ve also had a very janky styleguide-cum-component-library at /styleguide.

That’s now a much more useful document containing sections on:

  1. Foundations – The fundamentals of my design system including colours, design tokens, type and spacing scales etc.
  2. Typography – Visual reference and explanation of the typography & text styling.
  3. Content Components – Visual reference and explanation for the components I use in my content.
  4. UI Components – Visual reference for non-content UI components like footer or button.
  5. HTML – Visual reference for native HTML elements which aren’t covered elsewhere.

This is mostly meant as a reference for me but I’m glad I took the time to polish it up enough that I’m happy to share it.

copy / view as markdown

A less painful editing experience in Astro

I write most of my content in Astro Editor, which is intentionally designed to leave all code-like things alone and just help me write prose. One of the most annoying things about this is that whenever I use a component like <Callout> in an MDX file I have to remember to import it at the top of the file before I publish.

I recently stumbled upon Chris Swithinbank’s astro-auto-import, which can automatically import named components into all astro files at build-time.

After a bit of experimentation, I decided to import all the components in my MDX directory and added this to my astrojs.config.mjs

[
AutoImport({
imports: [{ [mdxBarrelPath]: mdxComponentNames }],
})
]

This works like a charm. The only negative side-effect so far is that when running the dev server, all MDX-generated pages include the CSS and client-side JS for all these components whether they’re used or not. This isn’t an issue with production builds though, so I can live with that.

Better editing outside content collections

My notes and articles live in Content Collections but I also have a few text-based pages which don’t – my Now page and my recently-added colophon, AI and Privacy pages. I was previously generating them from .astro files in the same way I would for any other “HTML Pages”:

  • pages
    • now
      • index.astro Imports the MDX file as Content
      • _now.mdx The actual text content
    • colophon
      • index.astro
      • _colophon.mdx

This is nice because I can still think of the page as a normal .astro page and can fully control the HTML and CSS for it. The only reason the _thing.mdx files exists is to make the content I change regularly nicer to edit. This is great for pages with their own design and layout, but all four of the pages I mentioned above essentially share the same layout and had almost identical index.astro’s.

This is precisely what Astro’s individual markdown support is for. And so now these pages are just MDX files:

  • pages
    • now.mdx
    • colophon.mdx
    • ai.mdx
    • privacy.mdx

Here’s what my now page looks like:

now.mdx
---
title: Now
subtitle: What I'm doing now
description: Danny Smith's Now Page
layout: '@layouts/Page.astro'
headingAlign: right
---
<Callout emoji="💡">
This is a [Now Page](https://nownownow.com/about). Thanks to Derek Sivers for the [idea](https://sive.rs/nowff).
</Callout>
- [Consulting](https://betterat.work) on leadership, remote working and operations with a few companies.
- Working on [Taskdn](https://tdn.danny.is), a system for managing tasks and projects as plain markdown files. Includes a desktop app, a CLI, Claude plugin and Obsidian plugin, among other things.
- Working on [Astro Editor](https://astroeditor.danny.is), a markdown editor for Astro content collections.
- Learning a shit ton about how to work with AI to build stuff fast **and well**.
- Occasionally adding to my [collection of tools and frameworks](https://betterat.work/toolbox).
- Playing at Open Mic nights in my local pub.
- Living in a lovely little mews in Islington, North London.
Updated: 2025-10-04

To make this work I added a new Page.astro layout specifically for this kind of page. The title and description are passed through to BaseHead and the title is used for the Heading. If subtitle is present it’s shown under the heading (and slightly changes some styling). headingAlign does what it says on the tin. Thanks to astro-auto-import, I can use all the same components in these files as I would in articles and notes without needing to import them.

Broken component remapping

I remap certain MDX components onto my own by passing this to Astro’s <Content> so certain markdown elements are used in place of the defaults.

export const MDX_COMPONENT_REMAPPING = {
a: SmartLink,
img: BasicImage,
table: WrappedTable,
'markdown-preview': MarkdownBlock,
'file-tree': FileTree,
} as const;

The solution to this is pretty simple. I need to add this after the frontmatter in my .mdx pages:

import { MDX_COMPONENT_REMAPPING } from '@config/mdx-components';
export const components = MDX_COMPONENT_REMAPPING;

Considering the whole point of this is making it nicer to edit these pages, this felt all kinds of wrong. So I ended up with a tiny remark plugin which does it for me!

copy / view as markdown

New AI Statement, Privacy Policy and Colophon Pages

I’ve just added a few new pages to this site, linked from the footer.

  1. An AI Statement which outlines where and how I use AI to work on this site. I suspect this will eventually expand into a more general statement on the topic.
  2. A Privacy Policy which states very simply and in non-legal language what data I collect (which at the moment is none). It seems weird to me that very few people bother to include these on personal sites – not because there’s a strong legal reason – but because a simple explanation like this can go a long way towards reassuring visitors who care about privacy.
  3. A Colophon page which briefly explains how this site is put together. I had one years ago and can’t remember why I removed it, but I absolutely love stumbling across other people’s /colophon pages. I love that we use the word Colophon for these, because history.
copy / view as markdown

Nicer Cover Images

I gave this site’s OG Images a makeover a few weeks back. These are images that show up when you share a link to a post on Slack, Discord, Bluesky and the like. The old ones were hastily thrown together ages ago and looked pretty rubbish. The new ones include a title, my avatar, the post’s URL and my name. Here’s an example from a recent note

Example of an OG Image

They’re generated at build time by og-image-generator.ts which:

  1. Loads the TTF fonts.
  2. Turns the background SVG and avatar into data URIs.
  3. Calls og-templates.ts which returns an element tree including all the text, structure and styling.
  4. Runs Satori to convert the element tree into an SVG.
  5. Uses resvg to convert the SVG into a PNG.

I went with Tree → SVG → PNG because of the fonts. Satori converts all the text into vector paths, so by the time the image is rasterised there are no fonts to resolve. This avoids a whole set of font-rendering issues I ran into before when I was hand-writing SVGs and rasterising them directly with sharp.

Making the title fit

The fiddly bit was the title because it needs to be as big as possible without overflowing, and Satori has no “shrink to fit”. It’s not perfect, but I ended up with some code which roughly estimates how wide each letter is, tries wrapping the title at the biggest size, and then stepping down until it fits its box:

function glyphEm(ch: string): number {
if (ch === ' ') return 0.3;
if (`.,:;'!|iIlj`.includes(ch)) return 0.3; // skinny
if ('MWmw@%'.includes(ch)) return 0.92; // wide
return 0.64; // everything else
}
// Try the biggest size, shrink by 2px until the lines fit.
for (let size = 96; size > 44; size -= 2) {
if (titleFitsAt(size)) return size;
}

It’s deliberately rough and Satori still does the actual wrapping. The URL is easier: it’s set in a monospace font, so every character is the same width and I can work out the right size directly.

Here’s an example of a short title:

short-og-example.png

And here’s a stupidly long title:

long-og-example.png

And here’s the default, which is statically served but can be regenerated based on the same templates with a manual script.

og-default-example.png

The whole thing is now cached too, so runs much faster than it used to. This is a tiny little tweak, but I’m pleased with how they look – especially when shared on Bluesky with my new atproto records

bluesky-og-image-demo.png

copy / view as markdown

Older Notes