diff --git a/apps/cli/sync.ts b/apps/cli/sync.ts index aafee66..98de8b4 100755 --- a/apps/cli/sync.ts +++ b/apps/cli/sync.ts @@ -181,6 +181,45 @@ const TOPIC_ALIASES: Record = { "Hash-Based Lookup": "Hash Table", }; +/** + * Sidebar icon per group, keyed by the (post-alias) topic name — the same key an + * unplanned problem's category group uses, so `Stack` the category and `Stack` + * the topic share one row. Lucide names; Blume resolves them at build time and + * reports a diagnostic for a typo, but an unmapped group is caught here first. + */ +const TOPIC_ICONS: Record = { + // README topics, in curriculum order + "Hash Table": "hash", + "Prefix Sum": "sigma", + "Two Pointers": "arrow-left-right", + "Sliding Window": "app-window", + "Binary Search": "search", + Stack: "layers", + "Monotonic Stack": "trending-up", + "Linked List": "link", + "Hybrid Structures": "combine", + "Matrix Index Math": "grid-3x3", + "Tree DFS": "folder-tree", + "Tree BFS": "network", + "Binary Search Tree": "git-fork", + "Heap / Priority Queue": "triangle", + Trie: "spell-check", + "Graph DFS": "waypoints", + "Graph BFS": "radar", + "Topological Sort": "workflow", + "Union-Find": "merge", + Backtracking: "undo-2", + "1-D Dynamic Programming": "columns-3", + "Multi-D / Grid DP": "table", + Greedy: "coins", + Intervals: "calendar-range", + // work/1 categories that can surface as an unplanned group + Array: "brackets", + String: "type", + Tree: "network", + "Depth-First Search": "folder-tree", +}; + /** LC number -> topic. A problem listed under two topics keeps the first. */ async function readmeTopics(): Promise> { const md = await Bun.file(join(ROOT, "README.md")).text(); @@ -197,12 +236,17 @@ async function readmeTopics(): Promise> { } /** - * Blume folder meta: exact topic label (the folder slug loses `/` and case) and - * curriculum order. An unplanned category group (warm-ups) has no README - * number and sorts at 0, ahead of topic 1, so the sidebar opens on the basics. + * Blume folder meta: exact topic label (the folder slug loses `/` and case), + * curriculum order, and the group's sidebar icon. An unplanned category group + * (warm-ups) has no README number and sorts at 0, ahead of topic 1, so the + * sidebar opens on the basics. */ function groupMeta(topic: Topic): string { - return `import { defineMeta } from "blume";\n\nexport default defineMeta({ title: ${JSON.stringify(topic.name)}, order: ${topic.order ?? 0} });\n`; + const icon = TOPIC_ICONS[topic.name]; + if (!icon) console.warn(`no icon for group ${JSON.stringify(topic.name)}: add it to TOPIC_ICONS`); + const fields = [`title: ${JSON.stringify(topic.name)}`, `order: ${topic.order ?? 0}`]; + if (icon) fields.push(`icon: ${JSON.stringify(icon)}`); + return `import { defineMeta } from "blume";\n\nexport default defineMeta({ ${fields.join(", ")} });\n`; } // ── inline-code formatting heuristics ────────────────────────────