refactor(blog): Return the /blog page to use server rendering

This commit is contained in:
Thomas 2025-03-22 09:17:13 +00:00
parent a2a2b6a0e0
commit b7fffca916
No known key found for this signature in database
12 changed files with 347 additions and 152 deletions

View file

@ -0,0 +1,44 @@
---
title: I think I was wrong about notes
date: 2025-03-22T09:05:21.497Z
slug: 2025-03-22-i-think-i-was-wrong-about-notes
author: Thomas Wilson
---
# I think I was wrong about notes
A few weeks back [I wrote](/blog/2025-02-19-should-i-be-taking-notes), somewhat skeptically, asking if I should be taking notes while reading. I asked if doing so would ever be worth the investment. Well, to be precise, I said:
> I have a job, and a human body, and a marriage to maintain. I do not have the time to make notes. I would not enjoy making notes. What would I do them for? Nobody has ever asked me about teleology. Not even once.
Since then, I have picked up Sönke Ahren's "[*How to Take Smart Notes*](https://www.soenkeahrens.de/en/takesmartnotes)" - a oft-cited, compact book filled with opinions about what "good" note-taking practice looks like. I think I picked it up because I was wondering at my own incredulity: are people really advocating for these sprawling "digital gardens" of notes? Other than hawking a course, what do people get out of this ?
I found that I didn't really understanding note taking, which might be obvious to you if you read through my earlier-quoted post. This was quite an exciting revelation - I learned that I had made a straw man argument against a practice that isn't really being advocated for. Or at least, isn't being advocated for in good faith.
One of Ahren's core messages is that making notes should be a force for understanding. Understanding the core idea, without immediately caveating it with nuance, its source, its strength, or its counters. These latter things matter (a lot), but only in addition to, or in the context of, that original point.
This comes hand-in-hand with the argument that writing is a tool for thinking, more than a means of making text. I have been writing posts for some version of this blog for almost six years. I started my first blog during my postgrad studies eleven years ago. I *understand* that writing is useful.
What's worse is that I *enjoy* writing now (much the same as anyone who "enjoys" writing enjoys it: writing is awful, having written is wonderful.)
Looking back on my note-taking practice, I often fell into notes which re-created or stored lists, or statistics, or acronyms. Or I simply underlined passages in text. These practices aren't oriented around the idea of extracting more abstract ideas from specific texts - they're about remembering specific texts.
I was making notes on a certain source (a book, or paper), and wasn't taking a step back.
As an example, it's important to me and my job to understand the systems that build and maintain software systems. For a long time, I have understood these through the lenses of "lean" and "agile" processes. I have been a strong proponent of these ideas for several years, and have used some of the specialist vocabulary (e.g. "value stream", "failure demand", "feedback loop") in my workplace to justify decisions or understand problems. I think I've used them effectively to build things well.
Fortunately (?) a lot of software engineering professionals rely almost exclusively on their intuitive feelings and their own experience. By doing even a small amount of reading and research (quite literally reading *a* book did this for me), I've been able to adopt the authority of a pre-existing (and quite well respect in the field) system of thinking.
What I had done, in retrospect, was take one person's experience and opinion, and made it my own. Luckily that author had done the hard work, and I adopted some pretty good opinions.
Looking back, I was adopting the language and posturing of someone who understood the ideas. This isn't the same as understanding. Being generous, I could say this this is part of learning: learning just enough to become intolerably opinionated. Being pessimistic, I could say that it's a good thing I didn't have to interact with someone who actually did understand these ideas.
When I asked, enraged at a reality I had invented, what I would even *do* with notes, I was reading like an early explorer: plucking words and ideas up from their natural habitat, and putting them in cases and on display, without any real coherent order. I was putting examples of ancient writing together: an accounting ledger, a love letter, and a legal code. They went together because they look, externally, like the same thing.
What I was failing to do was examine them. I had mistaken having a lot of seemingly (but not actually) organised ideas in a collection that I could draw up, like a trump card, for learning and understanding.
It is frankly baffling to get this far (and this qualified) in my life and career and just to realise that now. I think I've been pulled through by a brain that's *just* good enough at synthesising these things in the background, with just enough recall to use the right vocabulary. Heaven knows I wouldn't have these spare resources if I had more urgent things to worry about.
It's almost like it's functionally impossible to assess for "real" understanding in education and work. So long as you're doing the motions, you'll go un-noticed. And these motions *look* (to the unconcerned) like understanding.
Even worse: I tricked *myself* into thinking that I was doing it, and that to do even more would be an unfruitful waste of effort.

View file

@ -1,42 +0,0 @@
---
title: 'Cinnamon Dust Linen Shirt'
post_type: 'finished_project'
date: 2023-08-14T16:54:00.000Z
garment_birthday: 2023-08-14
slug: 2023-08-cinnamon-dust-linen-shirt
labour_hours: '10-15'
elapsed_time: '1 week'
cloth_description: 'Cinnamon Dust 185 Linen'
cloth_link: 'https://merchantandmills.com/uk/cinnamon-dust-185-linen-cloth'
pattern_description: 'Wardrobe by Me - Jensen Shirt'
pattern_link: 'https://wardrobebyme.com/products/jensen-shirt-sewing-pattern'
author: Thomas Wilson
images:
- cinnamon-dust-linen-shirt/2023-08-14-cinnamon-shirt.jpeg
---
This is another step in my Wedding Suit project - where I am making each piece of my wedding outfit. Less than twelve months now. The shirt came out really nicely, pretty clean, and I don't have anything else in my wardrobe that's a similar colour - it's a nice break from both cloth (linen, not cotton) and colour (I've a lot of whites, greys, blues)
This shirt is going to go into summer/autumn rotation - I am excited to wear it. In particular I'm pretty proud of:
- Overall construction of cuffs, collars, and buttons - the details are starting to feel less home-made and more hand-made.
- The edge stitching around the cuffs and collar: The lines are getting straighter and more consistent !
- Button positioning and stitching looks nice. I've fluffed this before and you get gathering/bunching of fabric ):
Unfortunately, the garment has come out pretty baggy around the torso, which is great for a breezy summer linen shirt, but I think for a more formal shirt I need to make some more alterations before the next project.
The whole process took about a week, working most evenings and spending a few hours over the weekend to do the hand-finishing details. Long enough that I will appreciate wearing it, but not so long that I got bored.
---
The fit, Good:
1. Using a self-drafted sleeve placket has made for good results, I like the placket size
2. Length of the piece is basically spot on
The fit, To change:
1. For a formal shirt, the piece is _far_ too big on me, I am but a wee lad. I think I can take 6" out the hips/waist, and 2-3" out of the chest.
2. Shoulders are _okay_ when the top button is done up, but could be 0.5-1" narrower
3. Sleeve could be 0.5-1" shorter (cuff comes too far down)

View file

@ -65,13 +65,11 @@ export class BlogController {
async getAllBlogPosts(
pageSize?: number
): Promise<Array<BlogPostListItem | BookReviewListItem | SnoutStreetStudiosPostListItem>> {
console.log('getAllBlogPosts');
console.log({ pageSize });
const blogPosts = await this._markdownRepository.blogPosts;
const blogPosts = this._markdownRepository.blogPosts;
const bookReviews = await this._markdownRepository.bookReviews;
const bookReviews = this._markdownRepository.bookReviews;
const snoutStreetStudiosPosts = await this._markdownRepository.snoutStreetStudiosPosts;
const snoutStreetStudiosPosts = this._markdownRepository.snoutStreetStudiosPosts;
const blogPostListItems: BlogPostListItem[] = blogPosts.blogPosts.map((blogPost) => {
return this.blogPostToBlogPostListItem(blogPost);
@ -90,7 +88,6 @@ export class BlogController {
);
if (pageSize === undefined) {
console.log('returning all blog posts');
return allBlogPosts;
}
@ -146,7 +143,7 @@ export class BlogController {
image: bookReview.image,
score: bookReview.score,
slug: bookReview.slug,
content: 'bookReview.html',
content: bookReview.html,
content_type: 'book_review',
};
}

View file

@ -2,26 +2,13 @@ import { BlogController } from '$lib/blog/BlogController.js';
import type { Load } from '@sveltejs/kit';
import { differenceInCalendarDays, getYear } from 'date-fns';
/**
* TODO: Return this to `true`, which will mean moving the `tag` searchParams from this route
* to another, e.g. /blog/tagged/:tag, however in the interest of moving quickly, I'm leaving
* it here for now. 2025-03-16
*/
export const prerender = false;
export const prerender = true;
export const load: Load = async ({ params, url }) => {
const controller = await BlogController.singleton();
const tags = url.searchParams.getAll('tag');
let posts = [];
if (tags.length > 0) {
posts = await controller.getBlogPostsByTags(tags);
} else {
posts = await controller.getAllBlogPosts();
}
const posts = await controller.getAllBlogPosts();
const currentYear = getYear(new Date());
console.log({ posts });
const mostRecentPost = posts[0];
const daysSinceLastPublish = differenceInCalendarDays(new Date(), new Date(mostRecentPost.date));
@ -35,7 +22,6 @@ export const load: Load = async ({ params, url }) => {
).length;
return {
tags,
posts,
firstPost,
averageDaysBetweenPosts,

View file

@ -1,7 +1,10 @@
<script lang="ts">
import type { PageData } from "./$types.js";
import Navbar from "$lib/components/Navbar.svelte";
import BlogHead from "./BlogHead.svelte";
import BlogHeader from "./BlogHeader.svelte";
import BlogPostListItem from "./BlogPostListItem.svelte";
import { BlogPost } from "$lib/blog/BlogPost.js";
interface Props {
data: PageData;
@ -19,72 +22,32 @@
} = $derived(data);
</script>
<svelte:head>
<!-- Primary Meta Tags -->
<title>Blog | thomaswilson.xyz</title>
<meta name="title" content="Blog | thomaswilson.xyz" />
<meta
name="description"
content="I write about software and how I should have built it, and sometimes other things."
/>
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://www.thomaswilson.xyz/blog" />
<meta property="og:title" content="Blog | thomaswilson.xyz" />
<meta
property="og:description"
content="I write about software and how I should have built it, and sometimes other things."
/>
<!-- Twitter -->
<meta property="twitter:title" content="Blog | thomaswilson.xyz" />
<meta
property="twitter:description"
content="I write about software and how I should have built it, and sometimes other things."
/>
</svelte:head>
<BlogHead />
<Navbar />
<main class="thomaswilson-container">
<section class="thomaswilson-strapline section heading">
<h1 class="page-title">Blog</h1>
<p class="heading__text">
It has been been
<span
class="days-since"
class:days-since-success={daysSinceLastPublish === 0}
>
{daysSinceLastPublish}
</span>
{daysSinceLastPublish === 1 ? "day" : "days"} since I last published something.
</p>
<p class="heading__text">
I have written {numberOfBlogPostsThisYear}
{numberOfBlogPostsThisYear === 1 ? "piece" : "pieces"} so far this year. On
average I publish something every {averageDaysBetweenPosts} days ({numberOfPosts}
posts in {daysSinceFirstPost} days).
</p>
<a href="/blog/feed">RSS Feed</a>
</section>
<BlogHeader
title="Weblog"
showRssFeed={true}
showLinkToAllPosts={false}
{numberOfBlogPostsThisYear}
{daysSinceFirstPost}
{numberOfPosts}
averageDaysBetweenPosts={Number(averageDaysBetweenPosts)}
{daysSinceLastPublish}
/>
<section class="section">
{#if data.tags.length > 0}
<h2>Tags: {data.tags.join(", ")}</h2>
{:else}
<h2>All Writing</h2>
{/if}
<h2>All Writing</h2>
<ul class="posts">
{#each posts as post, index}
<BlogPostListItem
{index}
content_type={post.content_type}
book_review={post.book_review}
book_review={post.content_type === "book_review"}
date={post.date}
numberOfPosts={posts.length}
preview={post.preview}
preview={(post as any).preview ?? ""}
slug={post.slug}
title={post.title}
/>
@ -123,34 +86,4 @@
gap: var(--spacing-xl);
max-width: 100%;
}
.days-since {
color: var(--brand-orange);
font-weight: 300;
border: 1px solid var(--brand-orange);
border-radius: 4px;
padding: 8px;
margin: 0 4px;
font-family: monospace;
font-size: inherit;
}
.days-since-success {
color: var(--brand-green);
border: 1px solid var(--brand-green);
animation-name: pulse_green;
animation-duration: 5.2s;
animation-iteration-count: infinite;
background: rgba(54, 130, 127, 0.05);
}
@keyframes pulse_green {
0% {
box-shadow: 0 0 0 0px rgba(54, 130, 127, 1);
}
20%,
100% {
box-shadow: 0 0 0 5px rgba(54, 130, 127, 0);
}
}
</style>

View file

@ -0,0 +1,25 @@
<svelte:head>
<!-- Primary Meta Tags -->
<title>Blog | thomaswilson.xyz</title>
<meta name="title" content="Blog | thomaswilson.xyz" />
<meta
name="description"
content="I write about software and how I should have built it, and sometimes other things."
/>
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://www.thomaswilson.xyz/blog" />
<meta property="og:title" content="Blog | thomaswilson.xyz" />
<meta
property="og:description"
content="I write about software and how I should have built it, and sometimes other things."
/>
<!-- Twitter -->
<meta property="twitter:title" content="Blog | thomaswilson.xyz" />
<meta
property="twitter:description"
content="I write about software and how I should have built it, and sometimes other things."
/>
</svelte:head>

View file

@ -0,0 +1,84 @@
<script lang="ts">
interface Props {
title: string;
showRssFeed: boolean;
showLinkToAllPosts: boolean;
daysSinceLastPublish: number;
numberOfBlogPostsThisYear: number;
averageDaysBetweenPosts: number;
numberOfPosts: number;
daysSinceFirstPost: number;
}
const {
title,
showLinkToAllPosts,
showRssFeed,
daysSinceLastPublish,
numberOfBlogPostsThisYear,
averageDaysBetweenPosts,
numberOfPosts,
daysSinceFirstPost,
}: Props = $props();
</script>
<section class="thomaswilson-strapline section heading">
<h1 class="page-title">{title}</h1>
<p class="heading__text">
It has been been
<span
class="days-since"
class:days-since-success={daysSinceLastPublish === 0}
>
{daysSinceLastPublish}
</span>
{daysSinceLastPublish === 1 ? "day" : "days"} since I last published something.
</p>
<p class="heading__text">
I have written {numberOfBlogPostsThisYear}
{numberOfBlogPostsThisYear === 1 ? "piece" : "pieces"} so far this year. On average
I publish something every {averageDaysBetweenPosts} days ({numberOfPosts}
posts in {daysSinceFirstPost} days).
</p>
{#if showLinkToAllPosts}
<a href="/blog" class="heading__link">All Posts</a>
{/if}
{#if showRssFeed}
<a href="/blog/feed" class="heading__link">RSS Feed</a>
{/if}
</section>
<style>
.days-since {
color: var(--brand-orange);
font-weight: 300;
border: 1px solid var(--brand-orange);
border-radius: 4px;
padding: 8px;
margin: 0 4px;
font-family: monospace;
font-size: inherit;
}
.days-since-success {
color: var(--brand-green);
border: 1px solid var(--brand-green);
animation-name: pulse_green;
animation-duration: 5.2s;
animation-iteration-count: infinite;
background: rgba(54, 130, 127, 0.05);
}
@keyframes pulse_green {
0% {
box-shadow: 0 0 0 0px rgba(54, 130, 127, 1);
}
20%,
100% {
box-shadow: 0 0 0 5px rgba(54, 130, 127, 0);
}
}
</style>

View file

@ -4,7 +4,7 @@ import { BlogController } from '../../../lib/blog/BlogController.js';
export const load: Load = async ({ params, fetch }) => {
const controller = await BlogController.singleton();
const slug = params['slug'] as string;
const post = await controller.getBlogPostBySlug(slug);
const post = await controller.getAnyKindOfContentBySlug(slug);
if (!post) {
return error(404, 'Post not found');

View file

@ -56,7 +56,7 @@
<div class="post-tags">
{#each post.tags as tag}
<a
href={`/blog?tag=${encodeURIComponent(tag)}`}
href={`/blog/tag/${encodeURIComponent(tag)}`}
class="post-tags__tag">#{tag}</a
>
{/each}

View file

@ -21,6 +21,14 @@
const slugifiedTitle = slugifyString(title);
slug = `${dateAsString}-${slugifiedTitle}`;
}
function handleContentChange(value: string) {
const maybeTitle = value.trim();
if (maybeTitle.startsWith(`#`) && !title) {
title = content.split("\n")[0].replace("#", "").trim();
handleTitleChange();
}
}
</script>
<section class="new-blog-post">
@ -62,6 +70,7 @@
rows="10"
cols="50"
bind:value={content}
onchange={(e) => handleContentChange(e.currentTarget.value ?? "")}
></textarea>
</div>

View file

@ -0,0 +1,41 @@
import { BlogController } from '$lib/blog/BlogController.js';
import type { Load } from '@sveltejs/kit';
import { differenceInCalendarDays, getYear } from 'date-fns';
/**
* TODO: Return this to `true`, which will mean moving the `tag` searchParams from this route
* to another, e.g. /blog/tagged/:tag, however in the interest of moving quickly, I'm leaving
* it here for now. 2025-03-16
*/
export const prerender = false;
export const load: Load = async ({ params, url }) => {
const controller = await BlogController.singleton();
const tag = params['tag'];
const posts = await controller.getBlogPostsByTags([tag]);
const currentYear = getYear(new Date());
console.log({ posts });
const mostRecentPost = posts[0];
const daysSinceLastPublish = differenceInCalendarDays(new Date(), new Date(mostRecentPost.date));
const numberOfPosts = posts.length;
const firstPost = posts[numberOfPosts - 1];
const daysSinceFirstPost = differenceInCalendarDays(new Date(), new Date(firstPost.date));
const averageDaysBetweenPosts = Number(daysSinceFirstPost / numberOfPosts).toFixed(2);
const numberOfBlogPostsThisYear: number = posts.filter(
(post) => getYear(new Date(post.date)) === currentYear
).length;
return {
tag,
posts,
firstPost,
averageDaysBetweenPosts,
daysSinceFirstPost,
daysSinceLastPublish,
numberOfPosts,
numberOfBlogPostsThisYear,
};
};

View file

@ -0,0 +1,118 @@
<script lang="ts">
import type { PageData } from "./$types.js";
import BlogHead from "../../BlogHead.svelte";
import Navbar from "$lib/components/Navbar.svelte";
import BlogHeader from "../../BlogHeader.svelte";
import BlogPostListItem from "../../BlogPostListItem.svelte";
interface Props {
data: PageData;
}
let { data }: Props = $props();
let {
posts,
tag,
numberOfPosts,
daysSinceLastPublish,
daysSinceFirstPost,
averageDaysBetweenPosts,
numberOfBlogPostsThisYear,
} = $derived(data);
</script>
<BlogHead />
<Navbar />
<main class="thomaswilson-container">
<BlogHeader
title={`Posts tagged #${tag}`}
showLinkToAllPosts={true}
showRssFeed={false}
{numberOfBlogPostsThisYear}
{daysSinceFirstPost}
{numberOfPosts}
averageDaysBetweenPosts={Number(averageDaysBetweenPosts)}
{daysSinceLastPublish}
/>
<section class="section">
<h2>#{tag}</h2>
<ul class="posts">
{#each posts as post, index}
<BlogPostListItem
{index}
content_type={post.content_type}
book_review={post.book_review}
date={post.date}
numberOfPosts={posts.length}
preview={post.preview}
slug={post.slug}
title={post.title}
/>
{/each}
</ul>
</section>
</main>
<style lang="scss">
.page-title {
font-size: 2.5rem;
margin: 0;
line-height: 100%;
}
.heading {
padding: 0;
gap: var(--spacing-base);
display: grid;
grid-template-columns: 100%;
}
.heading__text {
font-family: sans-serif;
margin: 0;
line-height: 150%;
font-size: 1.25rem;
}
.posts {
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: 100%;
gap: var(--spacing-xl);
max-width: 100%;
}
.days-since {
color: var(--brand-orange);
font-weight: 300;
border: 1px solid var(--brand-orange);
border-radius: 4px;
padding: 8px;
margin: 0 4px;
font-family: monospace;
font-size: inherit;
}
.days-since-success {
color: var(--brand-green);
border: 1px solid var(--brand-green);
animation-name: pulse_green;
animation-duration: 5.2s;
animation-iteration-count: infinite;
background: rgba(54, 130, 127, 0.05);
}
@keyframes pulse_green {
0% {
box-shadow: 0 0 0 0px rgba(54, 130, 127, 1);
}
20%,
100% {
box-shadow: 0 0 0 5px rgba(54, 130, 127, 0);
}
}
</style>