spb/spboucher.ai Public
spboucher.ai — personal website of Simon-Pierre Boucher.
TypeScript 93.4%
HTML 5.5%
CSS 1%
1/*2 blog.ts3 spboucher.ai Web4 Author: Simon-Pierre Boucher5 Mail: contact@spboucher.ai6*/78import fs from "node:fs";9import path from "node:path";1011export interface BlogPostMeta {12 slug: string;13 file: string;14 title: string;15 excerpt: string;16 date: string; // ISO date17 dateLabel: string;18 tags: string[];19}2021export type BlogBlock =22 | { type: "h2"; text: string }23 | { type: "p"; text: string }24 | { type: "quote"; text: string }25 | { type: "ul"; items: string[] }26 | { type: "math"; tex: string };2728export interface BlogPost extends BlogPostMeta {29 blocks: BlogBlock[];30 readingMinutes: number;31}3233/** Registry of published essays — sources live in /blog/*.txt. */34const registry: BlogPostMeta[] = [35 {36 slug: "macroeconomics-of-white-collar-automation",37 file: "blog4.txt",38 title: "The Macroeconomics of White-Collar Automation",39 excerpt:40 "White-collar workers sit near the center of developed economies — their incomes support taxes, mortgages, consumption, and urban housing. If AI automates cognitive work faster than institutions redistribute the gains, the shock becomes macroeconomic, not merely occupational.",41 date: "2026-08-09",42 dateLabel: "August 9, 2026",43 tags: ["AI", "Economics", "Macroeconomics"],44 },45 {46 slug: "cost-of-intelligence",47 file: "blog1.txt",48 title: "What Happens When the Cost of Intelligence Approaches Zero?",49 excerpt:50 "For most of economic history, useful cognitive work required scarce, educated humans. AI breaks that coupling — and when the price of a fundamental input collapses, the entire economy reorganizes around whatever remains scarce.",51 date: "2026-08-09",52 dateLabel: "August 9, 2026",53 tags: ["AI", "Economics"],54 },55 {56 slug: "who-owns-ai-wealth",57 file: "blog2.txt",58 title: "If AI Creates Enormous Wealth, Who Owns It?",59 excerpt:60 "Growth and the distribution of growth are two different things. AI may create extraordinary wealth while shifting the defining economic divide from skilled versus unskilled labor to those who sell labor versus those who own productive intelligence.",61 date: "2026-08-09",62 dateLabel: "August 9, 2026",63 tags: ["AI", "Economics", "Ownership"],64 },65 {66 slug: "ai-prevents-its-own-singularity",67 file: "blog3.txt",68 title: "What If AI Prevents Its Own Singularity?",69 excerpt:70 "The intelligence explosion assumes fresh information. But as machines generate more of the internet they learn from, recursive contamination could turn the exponential into a plateau — unless AI becomes radically more empirical.",71 date: "2026-08-09",72 dateLabel: "August 9, 2026",73 tags: ["AI", "Data", "Scaling"],74 },75];7677/** Parse the constrained Markdown subset used by the essays. */78function parseBlocks(raw: string): BlogBlock[] {79 const lines = raw.split("\n");80 const blocks: BlogBlock[] = [];81 let listItems: string[] | null = null;82 let mathLines: string[] | null = null;8384 const flushList = () => {85 if (listItems && listItems.length > 0) {86 blocks.push({ type: "ul", items: listItems });87 }88 listItems = null;89 };9091 for (const rawLine of lines) {92 const line = rawLine.trim();9394 // Display math: $$ ... $$ on one line, or a fenced multi-line block.95 if (mathLines !== null) {96 if (line === "$$" || line.endsWith("$$")) {97 if (line !== "$$") mathLines.push(line.slice(0, -2).trim());98 blocks.push({ type: "math", tex: mathLines.join("\n").trim() });99 mathLines = null;100 } else {101 mathLines.push(line);102 }103 continue;104 }105 if (line.startsWith("$$")) {106 flushList();107 const inner = line.slice(2);108 if (inner.endsWith("$$") && inner.length >= 2) {109 blocks.push({ type: "math", tex: inner.slice(0, -2).trim() });110 } else {111 mathLines = inner.trim() ? [inner.trim()] : [];112 }113 continue;114 }115 if (line.length === 0) {116 flushList();117 continue;118 }119 if (line.startsWith("# ")) {120 // Post title — carried by the registry, not repeated in the body.121 flushList();122 continue;123 }124 if (line.startsWith("## ")) {125 flushList();126 blocks.push({ type: "h2", text: line.slice(3).trim() });127 continue;128 }129 if (line.startsWith("> ")) {130 flushList();131 blocks.push({ type: "quote", text: line.slice(2).trim() });132 continue;133 }134 if (line.startsWith("* ")) {135 (listItems ??= []).push(line.slice(2).trim().replace(/,$/, ""));136 continue;137 }138 flushList();139 blocks.push({ type: "p", text: line });140 }141 flushList();142 if (mathLines !== null && mathLines.length > 0) {143 blocks.push({ type: "math", tex: mathLines.join("\n").trim() });144 }145 return blocks;146}147148function loadPost(meta: BlogPostMeta): BlogPost {149 const raw = fs.readFileSync(150 path.join(process.cwd(), "blog", meta.file),151 "utf8",152 );153 const blocks = parseBlocks(raw);154 const words = raw.split(/\s+/).filter(Boolean).length;155 return { ...meta, blocks, readingMinutes: Math.max(1, Math.round(words / 220)) };156}157158/** All posts, newest first (registry order breaks ties). */159export function getBlogPosts(): BlogPost[] {160 return [...registry]161 .map(loadPost)162 .sort((a, b) => (a.date < b.date ? 1 : a.date > b.date ? -1 : 0));163}164165export function getBlogPost(slug: string): BlogPost | undefined {166 const meta = registry.find((p) => p.slug === slug);167 return meta ? loadPost(meta) : undefined;168}169170export function getBlogSlugs(): string[] {171 return registry.map((p) => p.slug);172}173