diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e03da4..ee0b9df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/LEARNINGS.md b/LEARNINGS.md index 7bac1d1..e77025d 100644 --- a/LEARNINGS.md +++ b/LEARNINGS.md @@ -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` diff --git a/portfolio/build.mjs b/portfolio/build.mjs index 852e3c2..791f65c 100644 --- a/portfolio/build.mjs +++ b/portfolio/build.mjs @@ -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 - ? `${escapeHtml(meta.shareImageAlt || meta.imageAlt || meta.title)}` + ? `${escapeHtml(meta.imageAlt || meta.shareImageAlt || meta.title)}` : ''; return ` ${thumb} @@ -1165,7 +1165,7 @@ function detailPage(collection, entry, activeKey) {

${metaLine([meta.org || meta.venue, meta.role, meta.type, meta.period || meta.date])}

${isWriting && meta.summary ? `

${escapeHtml(meta.summary)}

` : ''} ${linkChips(meta.links)} - ${heroImage(meta, collection.name)} + ${meta.type === 'Personal' ? '' : heroImage(meta, collection.name)} ${markdownToHtml(entry.body)} ${articleDisclosure(isWriting)} ${shareLinks(pageUrl, meta.title)} diff --git a/portfolio/content/writing/builder-platforms-grow-by-owning-the-agent-loop.md b/portfolio/content/writing/builder-platforms-grow-by-owning-the-agent-loop.md index 636213c..93bac1d 100644 --- a/portfolio/content/writing/builder-platforms-grow-by-owning-the-agent-loop.md +++ b/portfolio/content/writing/builder-platforms-grow-by-owning-the-agent-loop.md @@ -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 --- diff --git a/portfolio/content/writing/can-i-build-an-ai-agent-that-doesnt-write-slop.md b/portfolio/content/writing/can-i-build-an-ai-agent-that-doesnt-write-slop.md index 05d073e..b96fc22 100644 --- a/portfolio/content/writing/can-i-build-an-ai-agent-that-doesnt-write-slop.md +++ b/portfolio/content/writing/can-i-build-an-ai-agent-that-doesnt-write-slop.md @@ -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 diff --git a/portfolio/content/writing/devex-is-a-growth-discipline.md b/portfolio/content/writing/devex-is-a-growth-discipline.md index 90c5527..df20a42 100644 --- a/portfolio/content/writing/devex-is-a-growth-discipline.md +++ b/portfolio/content/writing/devex-is-a-growth-discipline.md @@ -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. diff --git a/portfolio/content/writing/why-we-make-decisions.md b/portfolio/content/writing/why-we-make-decisions.md index e58d9f3..9eeaefe 100644 --- a/portfolio/content/writing/why-we-make-decisions.md +++ b/portfolio/content/writing/why-we-make-decisions.md @@ -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. diff --git a/portfolio/static/img/writing/why-we-make-decisions.svg b/portfolio/static/img/writing/why-we-make-decisions.svg new file mode 100644 index 0000000..8c3f144 --- /dev/null +++ b/portfolio/static/img/writing/why-we-make-decisions.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + PERSONAL · FIELD NOTES + Why We Decide + + values · principles · human judgment + + + + ryanbaumann.dev/writing/why-we-make-decisions + diff --git a/scripts/artifact-cards.mjs b/scripts/artifact-cards.mjs index def58cb..52de72f 100644 --- a/scripts/artifact-cards.mjs +++ b/scripts/artifact-cards.mjs @@ -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 = [ diff --git a/scripts/writer_app.mjs b/scripts/writer_app.mjs index a12d963..e6009b3 100755 --- a/scripts/writer_app.mjs +++ b/scripts/writer_app.mjs @@ -250,7 +250,7 @@ function renderFullArticleHtml(rawMarkdown, collectionName = 'writing', theme = const tagsHtml = Array.isArray(meta.tags) && meta.tags.length ? `

${meta.tags.map((t) => `${escapeHtml(t)}`).join('')}

` : ''; - const heroHtml = meta.image + const heroHtml = meta.image && meta.type !== 'Personal' ? `${escapeHtml(meta.imageAlt || title)}` : '';