Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

- **Writing: *Why We Decide Beats What We Decide* (`portfolio/content/writing/why-we-make-decisions.md`, `portfolio/build.mjs`, `scripts/lib/content-rules.mjs`, `scripts/writer_app.mjs`, `scripts/social-cards.mjs`, `portfolio/static/social/why-we-make-decisions.jpg`)**:
- Published the Personal Field Note *Why We Decide Beats What We Decide* (`draft: false`, `noindex: false`, `type: Personal`, `tags: ["personal", "ai", "field notes"]`) on judgment fatigue in the AI era and using explicit values as a decision filter.
- **Writing: *Why We Decide Beats What We Decide* (`portfolio/content/writing/why-we-make-decisions.md`, `portfolio/build.mjs`, `scripts/artifact-cards.mjs`, `portfolio/static/img/writing/why-we-make-decisions.svg`, `scripts/lib/content-rules.mjs`, `scripts/writer_app.mjs`, `scripts/social-cards.mjs`, `portfolio/static/social/why-we-make-decisions.jpg`)**:
- Published the Personal Field Note *Why We Decide Beats What We Decide* (`draft: false`, `noindex: false`, `type: Personal`, `order: 1`, `tags: ["personal", "ai", "field notes"]`) as the featured post on the homepage (`/`) and top entry on `/writing/`, with a basic typographic SVG thumbnail (`/img/writing/why-we-make-decisions.svg`) on the homepage and blog index while keeping the article detail page clean prose without an inline hero banner.
- Updated `featuredNote()` in `portfolio/build.mjs` to prefer `meta.image` over `meta.socialImage` so the homepage featured card displays the clean SVG thumbnail rather than the Open Graph social preview raster card.
- Added `'personal'` to `DOMAIN_TAGS` and exempted `type: Personal` writing entries from mandatory header/inline infographics in `scripts/lib/content-rules.mjs`, while surfacing `meta.type` in the article eyebrow and metadata on `portfolio/build.mjs` and `scripts/writer_app.mjs`, and falling back to `meta.socialImage` in list rows when `meta.image` is omitted.
- Added a 1200x627 portrait social preview card (`why-we-make-decisions.jpg`) and updated `scripts/writer_app.mjs` to render `meta.image` when present and serve static assets from `portfolio/static/`.
- Updated `scripts/social-cards.mjs` to fall back to `channel: 'chrome'` when Playwright's bundled headless shell binary revision is not present in the local cache.
Expand Down
2 changes: 1 addition & 1 deletion LEARNINGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Learning: `validateContent()` in `portfolio/build.mjs` validates internal links

Evidence: `npm --prefix portfolio test` failed at `node build.mjs` with `writing/why-we-make-decisions: broken internal link /writing/ai-saves-the-hour/` until the link was pointed to a published essay (`/writing/can-i-build-an-ai-agent-that-doesnt-write-slop/`), and `scripts/writer_app.mjs` returned `404 Not Found` on `/img/writing/why-we-make-decisions-header.svg` until `portfolio/static/` resolution was added.

Use next time: Only link to published routes (`draft: false`) inside `portfolio/content/**`, even when authoring a draft post, and run `npm --prefix portfolio test` (which runs both unit tests and `node build.mjs`) in addition to `build:writer`.
Use next time: Only link to published routes (`draft: false`) inside `portfolio/content/**`, even when authoring a draft post, and run `npm --prefix portfolio test` (which runs both unit tests and `node build.mjs`) in addition to `build:writer`. When promoting a new Field Note to the Featured slot on `/`, set `order: 1` (and increment existing `order` values in `portfolio/content/writing/*.md`), because `buildHome()` sorts `pinnedWriting` by `a.meta.order ?? Infinity` before date.

## 2026-10-31 - `jsdom@30` and `undici@8` require Node 22+ (`zlib.createZstdDecompress`) and `build-and-smoke` must gate on `package-checks`

Expand Down
6 changes: 3 additions & 3 deletions portfolio/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -915,12 +915,12 @@ function featuredNote(entry) {
const { meta } = entry;
const url = entryUrl('writing', entry);
const external = Boolean(meta.external);
const image = meta.socialImage || meta.image;
const image = meta.image || meta.socialImage;
const imagePath = image ? join(STATIC_DIR, image.replace(/^\//, '')) : '';
const { width, height } = image && existsSync(imagePath) ? getImageDimensions(imagePath) : { width: 1200, height: 627 };
const longDate = formatLongDate(meta.date);
const thumb = image
? `<img class="featured-note-thumb" src="${rebase(image)}" alt="${escapeHtml(meta.shareImageAlt || meta.imageAlt || meta.title)}" loading="lazy" width="${width}" height="${height}" />`
? `<img class="featured-note-thumb" src="${rebase(image)}" alt="${escapeHtml(meta.imageAlt || meta.shareImageAlt || meta.title)}" loading="lazy" width="${width}" height="${height}" />`
: '';
return `<a class="featured-note" href="${url}" data-analytics-type="writing" data-analytics-id="${escapeHtml(entry.slug)}"${external ? ' target="_blank" rel="noopener noreferrer"' : ''}>
${thumb}
Expand Down Expand Up @@ -1165,7 +1165,7 @@ function detailPage(collection, entry, activeKey) {
<p class="article-meta">${metaLine([meta.org || meta.venue, meta.role, meta.type, meta.period || meta.date])}</p>
${isWriting && meta.summary ? `<p class="lede">${escapeHtml(meta.summary)}</p>` : ''}
${linkChips(meta.links)}
${heroImage(meta, collection.name)}
${meta.type === 'Personal' ? '' : heroImage(meta, collection.name)}
${markdownToHtml(entry.body)}
${articleDisclosure(isWriting)}
${shareLinks(pageUrl, meta.title)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ shareTitle: Own the Agent Feedback Loop
shareSummary: A DevX strategy for making critical developer tasks work across changing models and agent harnesses.
shareImageAlt: Social card for Own the Agent Feedback Loop with a compact context, eval, outcome, and distribution loop.
tags: ["developer experience", "ai", "evals"]
order: 2
order: 3
draft: false
noindex: false
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ updated: 2026-08-18
canonical: https://ryanbaumann.dev/writing/can-i-build-an-ai-agent-that-doesnt-write-slop/
aliases: ["/writing/why-i-fine-tuned-a-26b-model-on-my-laptop-instead-of-prompting-frontier-apis/"]
tags: ["ai", "evals", "field notes"]
order: 1
order: 2
draft: false
noindex: false
image: /img/writing/can-i-build-an-ai-agent-that-doesnt-write-slop-header.svg
Expand Down
2 changes: 1 addition & 1 deletion portfolio/content/writing/devex-is-a-growth-discipline.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ shareTitle: DevX Is a Growth Function
shareSummary: Docs, code samples, advocacy, tutorials, and even talking to customers all have a limit. Own the friction, ship the fix, distribute the path, and measure the outcome.
shareImageAlt: Social preview reading DevX Is a Growth Discipline beside a product, distribution, and measurement loop.
tags: ["developer experience", "growth", "ai"]
order: 3
order: 4
---

Between early 2025 and 2026, our open-source ecosystem more than doubled its unique active users, and API engagement for our open-source client libraries grew strongly. We moved those metrics by treating DevX as a growth discipline, not a documentation queue. Our product, engineering, UX, and technical writing teams treated product, distribution, and measurement as one system, because presence in a workflow is not proof of adoption.
Expand Down
3 changes: 3 additions & 0 deletions portfolio/content/writing/why-we-make-decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ summary: AI makes generating options nearly free, which shifts the bottleneck to
date: 2026-11-01
updated: 2026-11-01
canonical: https://ryanbaumann.dev/writing/why-we-make-decisions/
order: 1
image: /img/writing/why-we-make-decisions.svg
imageAlt: Artifact card reading Personal Field Notes, Why We Decide, and values, principles, and human judgment.
socialImage: /social/why-we-make-decisions.jpg
shareTitle: Why We Decide Matters More
shareSummary: AI makes options cheap, which maxes out judgment fatigue across work and life. Writing down explicit values is how you keep loud defaults from choosing for you.
Expand Down
45 changes: 45 additions & 0 deletions portfolio/static/img/writing/why-we-make-decisions.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions scripts/artifact-cards.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ const CARDS = [
lines: ['official platform context for compatible coding agents'],
footer: 'mapsplatform.google.com',
},
{
file: 'writing/why-we-make-decisions.svg',
eyebrow: 'PERSONAL · FIELD NOTES',
title: 'Why We Decide',
lines: ['values · principles · human judgment'],
footer: 'ryanbaumann.dev/writing/why-we-make-decisions',
},
];

const FLOWS = [
Expand Down
2 changes: 1 addition & 1 deletion scripts/writer_app.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ function renderFullArticleHtml(rawMarkdown, collectionName = 'writing', theme =
const tagsHtml = Array.isArray(meta.tags) && meta.tags.length
? `<p class="card-tags" style="margin: 0.5rem 0 1.25rem;">${meta.tags.map((t) => `<span>${escapeHtml(t)}</span>`).join('')}</p>`
: '';
const heroHtml = meta.image
const heroHtml = meta.image && meta.type !== 'Personal'
? `<img class="article-hero" src="${escapeHtml(meta.image)}" alt="${escapeHtml(meta.imageAlt || title)}" />`
: '';

Expand Down
Loading