Plan Categories & Topic Cleanup Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Add 6 filterable categories to the plans page, merge fragmented topics, fix sort ordering with full timestamps, and clean up orphaned files.
Architecture: A Node.js migration script batch-updates all plan frontmatter (category, topic, publishedOn). PlanGrid’s sort already handles dates correctly — we just need real timestamps. The existing CategoryFilter component will render once plans have category values.
Tech Stack: Node.js script with gray-matter for frontmatter parsing, git log for timestamp extraction.
Task 1: Write the migration script
Files:
- Create:
scripts/migrate-plan-frontmatter.mjs
Step 1: Create the script
#!/usr/bin/env node/** * Migrates plan frontmatter: * 1. Adds `category` based on topic→category mapping * 2. Merges fragmented topics * 3. Converts publishedOn from date to full ISO timestamp (from git history) */import { readdir, readFile, writeFile } from "node:fs/promises";import { execSync } from "node:child_process";import matter from "gray-matter";
const PLANS_DIR = "app/src/content/plans";
// Topic → category mappingconst TOPIC_TO_CATEGORY = { // code-quality "api-code-audit": "code-quality", "app-code-audit": "code-quality", "app-audit-fixes": "code-quality", "code-quality-fixes": "code-quality", "css-audit": "code-quality", "codebase-cleanup": "code-quality", "remove-apply": "code-quality",
// live-window "live-window-refactor": "live-window", "live-window-class-refactor": "live-window", "live-window-playground": "live-window", "public-playground": "live-window", "live-window-test-page": "live-window", "live-window-readability-audit": "live-window", "idle-blinds": "live-window", "weather-code-audit": "live-window", "weather-id-system": "live-window", "weather-visual-fixes": "live-window", "weather-api-proxy": "live-window", "sky-gradient": "live-window", "stars-layer": "live-window", "sun-moon-layers": "live-window",
// chat "clientid-identity": "chat", "signed-client-identity": "chat", "chat-auto-page-context": "chat", "admin-help-command": "chat", "admin-moderation-tools": "chat", "idle-websocket-disconnect": "chat",
// styling "color-standardization": "styling", "subheader-color": "styling", "neon-cursor": "styling", "card-component": "styling", "syntax-highlighting": "styling", "responsive-tailwind": "styling", "mobile-styles-audit": "styling", "mobile-styles-fix": "styling",
// layout "sidebar-redesign": "layout", "info-panel": "layout", "command-palette-enhancements": "layout", "collection-pagination": "layout", "site-footer": "layout", "home-page-redesign": "layout", "home-styling": "layout", "spotify-player": "layout",
// content "meta-seo": "content", "meta-card-image": "content", "link-metadata": "content", "post-url-nesting": "content", "markdown-plugins": "content", "version-post": "content", "plans-collection": "content", "plan-categories": "content", "dead-link-detection": "content", "image-audit": "content",};
// Topic merges: old topic → new topicconst TOPIC_MERGES = { "app-audit-fixes": "app-code-audit", "code-quality-fixes": "app-code-audit", "mobile-styles-fix": "mobile-styles", "mobile-styles-audit": "mobile-styles", "live-window-class-refactor": "live-window-refactor", "public-playground": "live-window-playground", "signed-client-identity": "client-identity", "clientid-identity": "client-identity", "meta-card-image": "meta-seo", "home-styling": "home-page-redesign", "subheader-color": "color-standardization",};
function getGitTimestamp(filePath) { const filename = filePath.split("/").pop();
// Try the original docs/plans/ path first (files were migrated from there) const originalPath = `docs/plans/${filename}`; try { const result = execSync( `git log --all --format=%aI -- "${originalPath}"`, { encoding: "utf-8" } ).trim(); if (result) return result.split("\n").pop(); // earliest commit } catch { // ignore }
// Fall back to current path (for files created directly in content/plans/) try { const result = execSync( `git log --diff-filter=A --format=%aI -- "${filePath}"`, { encoding: "utf-8" } ).trim(); if (result) return result.split("\n").pop(); } catch { // ignore } return null;}
async function main() { const files = (await readdir(PLANS_DIR)).filter((f) => f.endsWith(".md")); let updated = 0;
for (const file of files) { const filePath = `${PLANS_DIR}/${file}`; const raw = await readFile(filePath, "utf-8"); const { data, content } = matter(raw);
let changed = false;
// 1. Add category from topic const topic = data.topic; if (topic && !data.category) { const category = TOPIC_TO_CATEGORY[topic]; if (category) { data.category = category; changed = true; } else { console.warn(` WARN: no category mapping for topic "${topic}" in ${file}`); } }
// 2. Merge topics if (topic && TOPIC_MERGES[topic]) { const newTopic = TOPIC_MERGES[topic]; console.log(` MERGE: ${topic} → ${newTopic} in ${file}`); data.topic = newTopic; // Update category for the new topic too if (TOPIC_TO_CATEGORY[newTopic]) { data.category = TOPIC_TO_CATEGORY[newTopic] ?? data.category; } changed = true; }
// 3. Convert publishedOn and updatedOn to full ISO timestamps for (const field of ["publishedOn", "updatedOn"]) { const currentDate = data[field]; if (!currentDate) continue;
const dateStr = currentDate instanceof Date ? currentDate.toISOString() : String(currentDate);
// If it's date-only (no time component), get git timestamp if (!dateStr.includes("T") || dateStr.endsWith("T00:00:00.000Z")) { const gitTimestamp = getGitTimestamp(filePath); if (gitTimestamp) { data[field] = new Date(gitTimestamp); changed = true; } } }
if (changed) { const output = matter.stringify(content, data); await writeFile(filePath, output); updated++; console.log(` UPDATED: ${file}`); } }
console.log(`\nDone. Updated ${updated}/${files.length} files.`);}
main().catch(console.error);Step 2: Commit the script
git add scripts/migrate-plan-frontmatter.mjsgit commit -m "feat: add plan frontmatter migration script"Task 2: Install gray-matter and run the migration
Step 1: Install gray-matter as a dev dependency
cd /Users/thalida/Documents/Repos/thalida.comnpm install --save-dev gray-matterStep 2: Run the migration script
node scripts/migrate-plan-frontmatter.mjsExpected: all ~97 plan files updated with category, merged topic values, and full ISO timestamps.
Step 3: Spot-check a few files
Verify that:
app/src/content/plans/2026-03-05-app-audit-fixes.mdnow hastopic: "app-code-audit"andcategory: "code-quality"app/src/content/plans/2026-03-07-public-playground-design.mdnow hastopic: "live-window-playground"andcategory: "live-window"app/src/content/plans/2026-03-08-plan-categories-design.mdnow hascategory: "content"- All files have full ISO timestamps in
publishedOn
Step 4: Commit the migrated files
git add app/src/content/plans/git commit -m "feat: add categories and merge topics across all plan files"Task 3: Delete orphaned docs/plans/ and clean up ignore files
Files:
- Delete:
docs/plans/(entire directory) - Modify:
.markdownlintignore— removedocs/plans/line - Modify:
.prettierignore— removedocs/plans/line - Delete:
scripts/migrate-plans.mjs(the original migration script, no longer needed)
Step 1: Delete docs/plans/
rm -rf docs/plans/Step 2: Update .markdownlintignore
Remove the docs/plans/ line. File should only contain:
app/src/content/plans/Step 3: Update .prettierignore
Remove the docs/plans/ line. Keep all other entries.
Step 4: Delete the old migration script if it exists
rm -f scripts/migrate-plans.mjsStep 5: Commit
git add -A docs/plans/ .markdownlintignore .prettierignore scripts/migrate-plans.mjsgit commit -m "chore: delete orphaned docs/plans/ and clean up ignore files"Task 4: Build and verify
Step 1: Run the build
just app::buildExpected: build succeeds with no errors.
Step 2: Serve and check the plans page
just app::serveVerify:
- Plans page shows category filter buttons: Code Quality, Live Window, Chat, Styling, Layout, Content
- Clicking a category filters to only those topics
- Topic cards still group design + implementation plans correctly
- Plan-categories topic appears first (newest timestamp)
- Merged topics show combined plans in a single card (e.g., app-code-audit has 3+ plans)
Step 3: Commit any fixes if needed
Task 5: Clean up migration script
Step 1: Remove the migration script (one-time use)
rm scripts/migrate-plan-frontmatter.mjsgit add scripts/migrate-plan-frontmatter.mjsgit commit -m "chore: remove one-time plan migration script"