From 815a56941bed7ccd0d1f92498340becc59481b85 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Nov 2026 10:51:08 +0000 Subject: [PATCH] Remove the per-article topics fallback Every article now has its topics in the frontmatter (meaningfool/meaningfool-writing#6), so the list of topics by article id in src/data/topics.ts was dead code that could drift from it. Remove the list, unknownTopicIds and its warning, and the fallback in topicsOf, which is inlined: the home reads article.data.topics. Keep TOPICS, Topic, TOPIC_NAMES, the check of topic-map.json, and the warning for an article with no topics. Update the comments and AGENTS.md that pointed at the fallback. The rendered site is unchanged: dist/ is byte-identical apart from the timestamps in llms.txt and llms-full.txt. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013MdjUeqUuaqYnBoar6wxVr --- AGENTS.md | 2 +- src/content.config.ts | 3 ++- src/data/topics.ts | 43 ++++--------------------------------------- src/pages/index.astro | 10 ++++------ 4 files changed, 11 insertions(+), 47 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 001a092..1019c05 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,7 +10,7 @@ Josselin Perrus's personal site: an Astro 7 static site served by Cloudflare Wor - The page background (`src/components/Background.astro`: the paper texture and the corner pattern) and the topic map (`TopicMap.astro`, inside the `Intro` of Home and About) are drawn on canvases from small images in `src/assets/background/`. `scripts/make-background.py` makes those images, from the map's land in `src/data/topic-map.json`; rerun it after changing either, and commit its output. The site loads no shader library. - `src/content/writing/`: git submodule of [meaningfool-writing](https://github.com/meaningfool/meaningfool-writing), which holds every article, activity log and image. Read its `AGENTS.md` before editing content. - `src/content.config.ts`: the content collection, built from `articles/*.md` and `daily-logs/*.md` in the submodule. -- `src/pages/index.astro`: the home. Its Writing list holds the articles, not the activity logs, and picking a topic on the map shows only the articles about it. An article's topics are its `topics` frontmatter field (`customer-success`, `product`, `ai-agents`: each one that makes up 30% or more of it), or else its entry in `src/data/topics.ts`. +- `src/pages/index.astro`: the home. Its Writing list holds the articles, not the activity logs, and picking a topic on the map shows only the articles about it. An article's topics are its `topics` frontmatter field (`customer-success`, `product`, `ai-agents`: each one that makes up 30% or more of it). - `src/pages/articles/[slug].astro`: the page of every article and activity log. The Markdown goes through the plugins in `src/lib/` set in `astro.config.mjs`: the `# Title` line is dropped, the headings are renumbered from h2, and `rehype-article.mjs` shapes figures, YouTube embeds (moved to youtube-nocookie.com) and tables for `src/styles/prose.css`. In a table, a row whose cells are all bold is highlighted, and a paragraph starting with `Table:` right after it, past a blank line, is its caption. An image alone in its paragraph is captioned with its title, or with its alt text when that has a space. - `src/data/projects.ts`: the projects, with their screenshots in `src/assets/projects/`, and which articles belong to each, by article id. - `docs/document-sharing.md`: how the encrypted portfolio links on `/documents/` work and how to create them. diff --git a/src/content.config.ts b/src/content.config.ts index 1c7cebd..b633b67 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -25,7 +25,8 @@ const writing = defineCollection({ description: z.string().optional(), tags: z.array(z.string()).optional(), // The topics an article is about, each one 30% or more of it: customer-success, product, ai-agents. The home's - // topic map filters Writing with them. Articles without the field take theirs from src/data/topics.ts. + // topic map filters Writing with them. Activity logs don't need the field; the home warns about an article + // without it. topics: z.array(z.enum(TOPICS)).optional(), }), }); diff --git a/src/data/topics.ts b/src/data/topics.ts index cfb81a9..d83b24a 100644 --- a/src/data/topics.ts +++ b/src/data/topics.ts @@ -1,11 +1,9 @@ -// The site's three topics, the three orange dots of the topic map, and which of them each article is about. Picking a -// topic on the home's map shows only the articles about it. +// The site's three topics, the three orange dots of the topic map. Picking a topic on the home's map shows only the +// articles about it. // // An article belongs to a topic when that topic makes up 30% or more of it, so it can belong to more than one. An -// article's topics come from its `topics` frontmatter field in meaningfool-writing when it has one, for example -// `topics: ["product", "ai-agents"]`. Until every article has that field, the list below gives the topics of the 18 -// articles the prototype read (docs/redesign/prototype/maps.js, the weights in each article's `w`), by article id. -import type { CollectionEntry } from 'astro:content'; +// article's topics are its `topics` frontmatter field in meaningfool-writing, for example +// `topics: ["product", "ai-agents"]`, which takes the ids below. import map from './topic-map.json'; /** The topics' ids, which the map's dots carry and the frontmatter's `topics` field takes. */ @@ -19,36 +17,3 @@ if (map.topics.map(({ id }) => id).join() !== TOPICS.join()) { /** The topics' names, as the map shows them: "Customer success", "Product", "AI agents". */ export const TOPIC_NAMES = Object.fromEntries(map.topics.map(({ id, name }) => [id, name])) as Record; - -// The prototype's weights are [customer success, product, AI agents]; each topic at 0.3 or more is listed. -const fromPrototype: Record = { - 'edit-collections-generative-ai': ['ai-agents'], // 0.1, 0.15, 0.75 - 'structured-extraction': ['ai-agents'], // 0.2, 0.1, 0.7 - 'thoughts-on-human-ai-collaboration': ['customer-success', 'product', 'ai-agents'], // 0.3, 0.3, 0.4 - 'detecting-bounding-boxes': ['ai-agents'], // 0.05, 0.1, 0.85 - 'agent-frameworks-for-the-rest-of-us': ['ai-agents'], // 0.05, 0.15, 0.8 - 'how-long-for-that-change': ['product'], // 0.15, 0.7, 0.15 - 'january-in-review': ['ai-agents'], // 0.05, 0.25, 0.7 - 'prolifics-and-frugals': ['product'], // 0.2, 0.7, 0.1 - 'the-slicing-fallacy': ['product'], // 0.25, 0.7, 0.05 - 'we-cant-stop-planning': ['product'], // 0.2, 0.75, 0.05 - cost_of_change_is_agile_ceiling: ['product'], // 0.15, 0.75, 0.1 - compounding_dev_process: ['product', 'ai-agents'], // 0.05, 0.5, 0.45 - antigravity_is_onto_something: ['ai-agents'], // 0.05, 0.25, 0.7 - 'semi-async_valley_of_death': ['product', 'ai-agents'], // 0.1, 0.3, 0.6 - 'spec-driven-development-has-it-backwards': ['product', 'ai-agents'], // 0.1, 0.5, 0.4 - 'ai-engineer-paris-conference-notes': ['ai-agents'], // 0.05, 0.15, 0.8 - 'voice-ai-maven-course-notes': ['ai-agents'], // 0.05, 0.1, 0.85 - 'faster-answers-better-questions': ['customer-success', 'product'], // 0.3, 0.5, 0.2 -}; - -/** An article's topics: its `topics` frontmatter field, or else the list above; undefined when it has neither. */ -export function topicsOf(article: CollectionEntry<'writing'>): Topic[] | undefined { - return article.data.topics ?? fromPrototype[article.id]; -} - -/** The ids in the list above that match no article, for example after an article's file was renamed. */ -export function unknownTopicIds(articleIds: Iterable): string[] { - const ids = new Set(articleIds); - return Object.keys(fromPrototype).filter((id) => !ids.has(id)); -} diff --git a/src/pages/index.astro b/src/pages/index.astro index 2469548..dcad6d9 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,7 +1,8 @@ --- // The home: the intro beside the topic map, then Projects and Writing. Writing lists the articles, newest first; the // activity logs aren't listed, but their pages keep their URLs. Picking an orange dot on the map shows only the -// articles about its topic (src/data/topics.ts), and picking it again, or "Show all", shows them all again. +// articles about its topic (each article's `topics` frontmatter field), and picking it again, or "Show all", shows +// them all again. import { Image } from 'astro:assets'; import { getCollection } from 'astro:content'; import Layout from '../layouts/Layout.astro'; @@ -9,7 +10,7 @@ import Intro from '../components/Intro.astro'; import JsonLd from '../components/JsonLd.astro'; import { projects, projectOf } from '../data/projects'; import { profiles } from '../data/profiles'; -import { TOPIC_NAMES, topicsOf, unknownTopicIds } from '../data/topics'; +import { TOPIC_NAMES } from '../data/topics'; import { newestFirst } from '../lib/sort-posts'; import { readingMinutes } from '../lib/reading-time.mjs'; import { HOME_INTRO, articlesOnly, plural } from '../lib/share'; @@ -19,11 +20,8 @@ const description = "Josselin Perrus's site: a product manager in Paris building AI products with AI agents. His projects, and writing on customer success, product and AI agents."; const articles = articlesOnly(await getCollection('writing')).sort(newestFirst); -for (const id of unknownTopicIds(articles.map((article) => article.id))) { - console.warn(`src/data/topics.ts gives topics to an article that doesn't exist: ${id}`); -} const rows = articles.map((article) => { - const topics = topicsOf(article); + const topics = article.data.topics; // At 30% or more, every article has at least one topic: three shares under 30% can't make a whole. if (!topics?.length) console.warn(`"${article.id}" has no topics, so picking a topic on the home's map hides it: give it a topics field.`); return {