This commit is contained in:
Joe Carstairs
2025-01-28 18:06:53 +00:00
19 changed files with 2940 additions and 1748 deletions

7
.zed/settings.json Normal file
View File

@@ -0,0 +1,7 @@
// Folder-specific settings
//
// For a full list of overridable settings, and general information on folder-specific settings,
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
{
"soft_wrap": "editor_width"
}

4254
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,14 +8,14 @@
"astro": "cd website && astro" "astro": "cd website && astro"
}, },
"dependencies": { "dependencies": {
"@astrojs/rss": "^4.0.5", "@astrojs/rss": "^4.0.10",
"@astrojs/sitemap": "^3.1.2", "@astrojs/sitemap": "^3.2.1",
"astro": "^4.11.5", "astro": "^5.1.1",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"typescript": "^5.4.3" "typescript": "^5.4.3"
}, },
"devDependencies": { "devDependencies": {
"@astrojs/check": "^0.5.10", "@astrojs/check": "^0.9.4",
"@types/markdown-it": "^14.1.1" "@types/markdown-it": "^14.1.1"
} }
} }

View File

@@ -2,6 +2,7 @@
:root { :root {
--colour-primary-10: #060300; --colour-primary-10: #060300;
--colour-primary-15: #150800;
--colour-primary-20: #1f1400; --colour-primary-20: #1f1400;
--colour-primary-30: #3c2b00; --colour-primary-30: #3c2b00;
--colour-primary-40: #5c4300; --colour-primary-40: #5c4300;
@@ -26,19 +27,22 @@
--colour-primary-fg: var(--colour-primary-90); --colour-primary-fg: var(--colour-primary-90);
--colour-primary-fg-accent: var(--colour-primary-80); --colour-primary-fg-accent: var(--colour-primary-80);
--colour-primary-bg: var(--colour-primary-10); --colour-primary-bg: var(--colour-primary-10);
--colour-code-fg: var(--colour-primary-90);
--colour-code-bg: var(--colour-primary-15);
--colour-hyperlink: var(--colour-hyperlink-80); --colour-hyperlink: var(--colour-hyperlink-80);
--font-size-sm: 1rem; --font-size-sm: 1rem;
--font-size-base: 1.25rem; --font-size-base: 1.125rem;
--font-size-md: 1.75rem; --font-size-md: 1.5rem;
--font-size-lg: 2.5rem; --font-size-lg: 2rem;
--font-size-xl: 3.5rem; --font-size-xl: 3rem;
--spacing-block-xs: 0.5rem; --spacing-block-xs: 0.5rem;
--spacing-block-sm: 1.75rem; --spacing-block-sm: 1.75rem;
--spacing-block-md: 2.5rem; --spacing-block-md: 2.5rem;
--spacing-block-lg: 3.5rem; --spacing-block-lg: 3.5rem;
--spacing-block-xl: 5rem; --spacing-block-xl: 5rem;
--spacing-inline-xs: 0.25rem;
--spacing-inline-sm: 0.5rem; --spacing-inline-sm: 0.5rem;
--spacing-inline-md: 1.5rem; --spacing-inline-md: 1.5rem;
--spacing-inline-lg: 3rem; --spacing-inline-lg: 3rem;
@@ -92,9 +96,10 @@ body {
img { img {
margin-inline: auto; margin-inline: auto;
height: auto;
} }
@media (min-width: 36rem) { @media (min-width: 48rem) {
body { body {
display: grid; display: grid;
grid-template-columns: grid-template-columns:
@@ -108,7 +113,7 @@ img {
--body-margin-inline-end: 6rem; --body-margin-inline-end: 6rem;
--grid-margin-inline: 6rem; --grid-margin-inline: 6rem;
--grid-total-width: 36rem; --grid-total-width: 48rem;
--grid-max-content-width: calc( --grid-max-content-width: calc(
var(--grid-total-width) var(--grid-total-width)
- var(--body-margin-inline-start) - var(--body-margin-inline-start)
@@ -133,12 +138,6 @@ img {
} }
} }
@media (min-width: 48rem) {
body {
--grid-total-width: 48rem;
}
}
/** Headings */ /** Headings */
h1 { h1 {
@@ -262,3 +261,28 @@ blockquote :is(i, em) {
.small-caps { .small-caps {
font-variant: small-caps; font-variant: small-caps;
} }
/** Pre-formatted blocks */
pre {
border: 2px solid var(--colour-primary-fg);
border-radius: 2px;
background-color: var(--colour-code-bg) !important;
margin-block-start: var(--spacing-block-sm);
padding-inline: var(--spacing-inline-sm);
padding-block: var(--spacing-block-xs);
}
/** Code blocks */
code {
border: 2px solid var(--colour-primary-fg);
border-radius: 2px;
padding-inline: var(--spacing-inline-xs);
color: var(--colour-code-fg);
background-color: var(--colour-code-bg);
font-size: var(--font-size-sm);
}
pre code {
border: none;
border-radius: none;
padding: none;
}

View File

@@ -64,7 +64,7 @@ const canonicalBlogUrl = new URL('blog', Astro.site)
<ul> <ul>
{ posts.filter(matchesYear(year)).sort(sortByPubDateDescending).map(post => ( { posts.filter(matchesYear(year)).sort(sortByPubDateDescending).map(post => (
<li class="h-entry"> <li class="h-entry">
<a class="u-url p-name" href={`/blog/${post.slug}`}>{post.data.title}</a>. <a class="u-url p-name" href={`/blog/${post.id}`}>{post.data.title}</a>.
<Fragment set:html={post.data.description} /> <Fragment set:html={post.data.description} />
Added: <FormattedDate date={post.data.pubDate} /> Added: <FormattedDate date={post.data.pubDate} />
</li> </li>

View File

@@ -45,6 +45,7 @@
<a href="https://www.facebook.com/joe.carstairs.5" rel="me">Facebook</a>, <a href="https://www.facebook.com/joe.carstairs.5" rel="me">Facebook</a>,
<a href="https://mastodon.social/@joe_carstairs" rel="me">Mastodon</a>, <a href="https://mastodon.social/@joe_carstairs" rel="me">Mastodon</a>,
<a href="https://www.linkedin.com/in/joe-carstairs-0aa936277" rel="me">LinkedIn</a>, <a href="https://www.linkedin.com/in/joe-carstairs-0aa936277" rel="me">LinkedIn</a>,
<a href="https://bsky.app/profile/joeacarstairs.bsky.social" rel="me">BlueSky</a>,
or <a href="https://github.com/joeacarstairs" rel="me">GitHub</a>. or <a href="https://github.com/joeacarstairs" rel="me">GitHub</a>.
</small> </small>
</p> </p>

View File

@@ -1,8 +1,9 @@
import { glob } from 'astro/loaders';
import { defineCollection, z } from 'astro:content'; import { defineCollection, z } from 'astro:content';
const blog = defineCollection({ const blog = defineCollection({
type: 'content', loader: glob({ pattern: '**/*.(md|mdx|html)', base: './src/content/blog' }),
schema: z.object({ schema: z.object({
title: z.string(), title: z.string(),
hidden: z.optional(z.boolean()), hidden: z.optional(z.boolean()),
description: z.string(), description: z.string(),

View File

@@ -91,7 +91,7 @@ She uses what her Maker has endowed her<br>
with: her recorder skills are off the charts;<br> with: her recorder skills are off the charts;<br>
youse think Im joking, but I wouldnt doubt her!<br> youse think Im joking, but I wouldnt doubt her!<br>
This lass of the land of the Rot-Gold-Schwarz<br> This lass of the land of the Rot-Gold-Schwarz<br>
will soon depart, though long we might beseech ya<br> will soon depart, though long we might beseech you<br>
to stay. Of course, youll break all of our hearts,<br> to stay. Of course, youll break all of our hearts,<br>
but mine most of all. Any time, Felicia,<br> but mine most of all. Any time, Felicia,<br>
Creag Meagaidh calls, I know routes up the rear<br> Creag Meagaidh calls, I know routes up the rear<br>

View File

@@ -0,0 +1,193 @@
---
title: How I read things on the Internet now (no, of course I don't leave the terminal!)
description: >-
I've been sprucing up how I follow what's happening on the Internet. I can
now read all the things I want to read pretty efficiently, and enjoy doing
it, which is exciting.
pubDate: 2025-01-19
---
## The problem
I like to read things on the Internet sometimes. Or listen to things. Or watch
things.
Some interesting people write blog posts. Some beautiful people make music. Some
silly people make comedy sketches. Sometimes my friends are sharing stuff with
their friends, which means me (WIP on that front: I'll get back to that).
I want to know about it. I want to read interesting things that will educate
me. I want to hear beautiful music. I want to hear about what my friends are up
to.
I can't read everything as it comes in, it's too much: I'll need to sift a lot
of it out quickly. I'll need to sift through it regularly to stay on top of it:
like, several times a week, if not daily. And sometimes, while I'm doing my
daily sifting, I'll find something I really want to read, but I haven't got
time right now: I'll save this for the weekend.
For a while, I've been hosting [CommaFeed][commafeed] on [PikaPods][pikapods].
This has been OK ([OMG, RSS is cool][rss]), but the interface is just clunky
enough to make it a chore to use. That means I don't sift through stuff
regularly, and that means my feed piles hundreds of unread posts high.
It also hasn't been any good for distinguishing between stuff I don't want to
read *ever*, and stuff I don't want to read *right now* but will get round to
later. I could in theory use the bookmarking feature built-in to my browser,
but removing things after I've read them is too clunky, so I don't do it.
## The solution
Every morning, I open my terminal and run [newsboat][newsboat].
![newsboat showing how many unread posts I have at a glance in the opening view](./newsboat.webp)
I know the unread count is pretty fresh, because I've set up a systemd service
to run newsboat at startup to fetch the feeds.
I press `l` twice to open a post. Then I press `n` to navigate to the next
unread post until I run out of unread posts.
![newsboat displaying a post](./newsboat-post.webp)
If I encounter something I want to read later, but don't have time right now, I
press `b`, which runs a home-made bookmarking script. Here it is:
```bash
### readlater.sh ###
# newsboat passes a few arguments:
# the first one is the post URL
url="$1"
# I turn the URL into a suitable filename
filename=$( \
echo $url | \
sed "s/.*:\/\///" | \
sed "s/\//./g" | \
sed "s/\.html\$//g" \
)
filename="$filename".html
# If I've already got this in my reading list,
# I don't add it again
if [[ -a "$HOME/readlist/unread/$filename" ]]
then
exit 0
fi
# Otherwise, I download the post with curl
# and pipe it to a file in my reading list
# folder, ~/readlist/unread
curl "$url" > "$HOME/readlist/unread/$filename"
```
I can also run this script manually and pass it a URL of my choice at any time,
say, if I find an interesting article while browsing the Web.
When I want to read from my reading list, I run `readnow.sh`, which simply
opens my reading list folder, `~/readlist/unread`, in my terminal file browser
of choice: namely, [ranger][ranger].
![ranger showing the contents of my reading list with a preview](./ranger.webp)
Although ranger has a preview, I'll typically open the file up in my terminal
web browser of choice, which is [w3m][w3m] (plus a couple of custom
key-bindings). I've configured this to be my preferred web browser in ranger by
shifting it to the top of the list of HTML browsers in
`~/.config/ranger/rifle.conf`.
```conf
### ~/.config/ranger/rifle.conf ###
...
ext x?html?, has w3m, terminal = w3m "$@"
ext x?html?, ...
...
```
Having configured my default web browser in my ranger config, all
I need to do is press `l`.
![A post displaying in w3m](./w3m.webp)
No ads, no cookie popups, no giant banner images taking 2 seconds to load and
shifting the content all over the place: just the text I want to read. Isn't it
beautiful?
Once I'm finished reading the post, I'll press `q` to quit w3m and return to
ranger. Assuming I don't need to read it again, I'll press `dm` to move the
post to `~/readlist/read` - my way of marking a post as 'read'.
I've done this by writing a super simple script, `markread.sh`:
```bash
### markread.sh ###
filename="$(basename $1)"
if [[ -a "$HOME/readlist/unread/$filename" ]]
then
mv "$HOME/readlist/unread/$filename" "$HOME/readlist/read/$filename"
echo "Marked $filename as read."
else
echo "I couldn't find a file in ~/readlist/unread with the name: $filename"
fi
```
...and hooking it onto the custom keybinding, `dm`, in ranger:
```conf
### ~/.config/ranger/rc.conf ###
...
# map `dm` to run markread.sh in the shell, providing the active filename
# as the first and only argument
map dm shell markread.sh %f
...
```
## The result
I can now keep up to date, and I enjoy doing it.
I get not everyone likes to live in the terminal. I think the key takeaways
are:
- Make it really easy to sift through new posts
- When you sift, sift through every post, and for each one, either read it
straight away, or add it to your reading list
- Sift daily
- Make it really easy to add things to your reading list
- Make it really easy to browse, read things, and mark things read in your
reading list
- Set aside time to catch up on your reading list
- Make the whole thing joyful (both because joy is good, and because you won't
do it otherwise)
## Next steps
I still haven't really figured out social media. I'd like to stay up to date
with what my friends are doing, especially the ones I don't see very often. If
my friends are posting stuff on the Internet, I'd love to see it.
I still need to know:
- Are my friends posting stuff on the Internet?
- If so, where?
- What's the best way of subscribing to their posts - even if they live on
different websites and in different formats?
- What's the best way of sending and receiving comments/replies/reactions?
TBC. Answers on a postcard please.
[commafeed]: https://www.commafeed.com
[pikapods]: https://www.pikapods.com
[newsboat]: https://newsboat.org
[ranger]: https://ranger.github.io
[rss]: /blog/2024/05/02/no_more_youtube
[w3m]: https://w3m.sourceforge.net

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -0,0 +1,139 @@
---
title: Why scientists need philosophers
description: This is a practice essay, OK? Don't shoot me if it's no good.
pubDate: 2025-01-24
---
I've recently been working at a
[Philosophy of Science MOOC]([phil-sci-coursera]) on Coursera, the online
courses website. Later on, they set you an exercise to write a short essay
addressing how philosophy can contribute to science.
So this is my punt. Life is too short to revise or, hell, even research this, so
don't take any of this as my settled opinion or as my best work. Whatever. With
that caveat in mind, it might still be entertaining at least, or maybe even
spark some thoughts.
---
Walk into a particular room in the Science Museum in Kensington, and you will
find yourself enveloped in a cavern of ironmongery and miscellanea. The shelves
droop under the weight of bolts, files, screws, bits, grinders, saws,
protractors, clamps and pins. The inventory scrolls endlessly past you, voices
reciting the lists of trinkets like an incantation.
This is the workshop of James Watt, meticulously reconstructed from the
original as it was left in his home in Birmingham after his death. This
entrancing space invites you to imagine a tireless creative, endlessly
tinkering away at his next contraption.
And yet for all that - and for all his immense valorisation as the lynchpin of
Britain's industrial revolution - Watt was reluctant to think of himself as the
engineer everyone else loved. He aspired to be remembered not as an engineer
but as a scientist.
To understand why James Watt, one of the most admired engineers who ever lived,
wished he were famed as a scientist instead, is to understand something
essential about modern Western thought.
In Watt's lifetime, scientists increasingly became the elite of society. In the
nineteenth and twentieth century, this trend only gathered pace. We constructed
our modern public health infrastructure on the advice of pathologists and made
medicine scientific. We funded scientific expeditions to map the world, even to
its remotest corners (in part to help us conquer it). We adopted radical new
economic policies in response to scientific economic theories. We built vast
infrastructure networks to communicate waves of invisible energy discovered by
pioneering physicists, and built nuclear plants to generate more of the stuff
by means of nuclear science. We even designed social programmes on the basis of
scientific anthropology. By the end of the twentieth century, scientists were
our prophets, priests and kings. Or so we thought.
In the 1990s, at the so-called 'end of history', it was assumed that there
would be no more need for social upheaval. Humanity had arrived at the ideal
system of social organisation. And among other ideologies - secularism,
libertarianism, democracy - an essential part of the package is that science
was our ultimate and incontestible way of securing knowledge about the world.
Yet now, that certainty is broken. Religious fundamentalism, whether Christian,
Muslim, Hindu, or else besides, is politically empowered in many places,
together with its rejection of science.
At the same time, the myth of science is ever more punctured. The supposedly
scientific West has increasingly come to appreciate that their scientific
heritage also includes much we would rather ignore - phrenology, race science,
systematic blindness to female bodies in medicine.
Meanwhile, scientists themselves are noticing that their holy calling has
turned out to be rather less holy than they had hoped. They find science
pulled between the competing demands of truth and tenure. Scientific knowledge
is locked behind the paywalls of exclusive journals, which even many academics
struggle to access, never mind the general public.
What then for science in the twenty-first century?
Yet there is another story available. It starts with confessing that the old
stories got things wrong in important ways. When we put ideology aside, science
has not been on an uncontested march to universal acceptance since Galileo.
There has been continual change, continual conflict, continual readjustment of
our ideas to the changing demands of the age.
In Galileo's day, it may have been a fight to establish that there was much to
be seen by simply looking. As empirical observation started to prove its worth
in the early modern period, thinkers wrestled with new problems: how to
reconcile the evidence of Scripture with the evidence of the senses? How to
understand how sensation can give us knowledge at all, granted that any
observer may be vulnerable to illusions, tricks and dreams? And if that's how
sense data work, what then for our mathematical or logical knowledge, which
seems to already bind the world even before we start experiencing it?
This centuries-long struggle culminated in the work of Kant, who in his 1786
magnum opus, the _Critique of Pure Reason_, set out a masterful - if infamously
obscure - system, which enabled thinkers to understand just how empirical
knowledge might work.
Yet two generations later, Charles Darwin lit the flame under new controversy
about the relationship between scientific and religious knowledge. His bizarre
and wildly imaginative theory of evolution by natural selection challenged the
Genesis Creation accounts, and this was soon to be followed up with the theory
of tectonic shift.
Some said that where scientists contradicted the authority of Scripture, the
word of God must always win. Others said that science alone had the keys to
knowledge, and if what the Bible said couldn't be proven scientifically, then
it couldn't be accepted. Some said that science and religion were two
incommensurable attempts to study the same subject matter, while others said
that they covered completely separate spheres.
Gradually, all of these views moved to the extremes. Now, most people (though
not all) agree that science and religion have overlapping spheres, and can
inform one another, but neither the Book of Nature nor the Book of Scripture
has the decisive final say.
Now, in our post-Christendom Western context, it's more important than ever to
understand how science and religion can talk to one another. Religious
minorities - as all religions now are in the West - are vulnerable to the risk
of becoming epistemic islands, cut off from the knowledge of the rest of the
community, unless we can find ways that science can talk across creedal
differences.
We need, too, for scientifically marginalised communities, such as non-white
people, whom science has ignored, or worse, to be more tightly integrated into
science, both so that knowledge might increase, and so that the benefits
knowledge gives might be fairly shared.
In light of these urgent needs, today's philosophers are considering science
not just as an epistemic problem, but as a social problem. As philosophers once
established science as the bedrock of modern knowledge, so philosophers today
have the task of figuring out how science can glue together our societies.
Science has been at its most dangerous when it hasn't been questioned. At all
times, as long as we practice science, we need to consider what it means, what
it means to do science well, how it can generate knowledge, and how it ought to
be used as a powerful instrument of change.
And perhaps that might justify James Watt in his obsession to be seen as a
scientist: since we can't get by just with practitioners. We need people who
can see our practices from the outside and shine a mirror back on us. If we
want science, then we need philosophers.
[phil-sci-coursera]: https://www.coursera.org/learn/philosophy-physical-sciences

View File

@@ -134,6 +134,12 @@ const LINKS: Link[] = [
description: 'Handy for the next time you develop a CLI or TUI. Also handy as a user: now I know about <a href="https://readline.kablamo.org/emacs.html">readline key bindings</a>, which are everywhere apparently.', description: 'Handy for the next time you develop a CLI or TUI. Also handy as a user: now I know about <a href="https://readline.kablamo.org/emacs.html">readline key bindings</a>, which are everywhere apparently.',
isoDateAdded: '2024-12-20', isoDateAdded: '2024-12-20',
}, },
{
href: 'https://www.bankofengland.co.uk/statistics/research-datasets',
title: "Bank of England's 'Millenium of Macroeconomic Data'",
description: 'There was no long-term price inflation from 1200 (when these data begin) until 1550. WHAT?!',
isoDateAdded: '2025-01-23',
},
]; ];
export default LINKS; export default LINKS;

View File

@@ -1,20 +1,20 @@
--- ---
import { type CollectionEntry, getCollection } from 'astro:content'; import { type CollectionEntry, getCollection, render } from 'astro:content';
import BlogPost from '../../layouts/BlogPost.astro'; import BlogPost from '../../layouts/BlogPost.astro';
export async function getStaticPaths() { export async function getStaticPaths() {
const posts = await getCollection('blog'); const posts = await getCollection('blog');
return posts.map((post) => ({ return posts.map((post) => ({
params: { slug: post.slug }, params: { slug: post.id },
props: post, props: post,
})); }));
} }
type Props = CollectionEntry<'blog'>; type Props = CollectionEntry<'blog'>;
const post = Astro.props; const post = Astro.props;
const { Content } = await post.render(); const { Content } = await render(post);
--- ---
<BlogPost {...post.data}> <BlogPost {...post.data}>
<Content /> <Content />
</BlogPost> </BlogPost>

View File

@@ -23,9 +23,9 @@ export async function GET(context: APIContext) {
site: path.join(site.toString(), 'blog'), site: path.join(site.toString(), 'blog'),
trailingSlash: false, trailingSlash: false,
items: posts.map((post) => ({ items: posts.map((post) => ({
link: `/blog/${post.slug}`, link: `/blog/${post.id}`,
title: post.data.title, title: post.data.title,
content: mdParser.render(post.body), content: mdParser.render(post.body ?? ''),
pubDate: post.data.pubDate, pubDate: post.data.pubDate,
description: post.data.description, description: post.data.description,
author: 'Joe Carstairs', author: 'Joe Carstairs',

View File

@@ -1,6 +1,11 @@
{ {
"extends": "astro/tsconfigs/strict", "extends": "astro/tsconfigs/strict",
"compilerOptions": { "compilerOptions": {
"strictNullChecks": true "strictNullChecks": true
} },
} "include": [
".astro/types.d.ts",
"**/*"
],
"exclude": ["dist"]
}