|
| 1 | +--- |
| 2 | +phase: quick-10 |
| 3 | +plan: 01 |
| 4 | +type: execute |
| 5 | +wave: 1 |
| 6 | +depends_on: [] |
| 7 | +files_modified: |
| 8 | + - website/docs/.vitepress/theme/composables/useSidebar.ts |
| 9 | + - website/docs/.vitepress/config.mts |
| 10 | + - website/docs/.vitepress/theme/components/docs/DocsSidebarItem.vue |
| 11 | +autonomous: true |
| 12 | +requirements: [] |
| 13 | +must_haves: |
| 14 | + truths: |
| 15 | + - "Each sidebar link has a small icon to its left" |
| 16 | + - "Active sidebar item icon is visible and styled consistently with text" |
| 17 | + - "Icons come from the already-installed lucide-vue-next package" |
| 18 | + artifacts: |
| 19 | + - path: "website/docs/.vitepress/theme/composables/useSidebar.ts" |
| 20 | + provides: "SidebarItem interface with optional icon field" |
| 21 | + contains: "icon?: string" |
| 22 | + - path: "website/docs/.vitepress/theme/components/docs/DocsSidebarItem.vue" |
| 23 | + provides: "Renders icon component inline before item text" |
| 24 | + - path: "website/docs/.vitepress/config.mts" |
| 25 | + provides: "Every leaf sidebar item has an icon name assigned" |
| 26 | + key_links: |
| 27 | + - from: "website/docs/.vitepress/config.mts" |
| 28 | + to: "website/docs/.vitepress/theme/components/docs/DocsSidebarItem.vue" |
| 29 | + via: "icon property on SidebarItem passed through VitePress theme config" |
| 30 | +--- |
| 31 | + |
| 32 | +<objective> |
| 33 | +Add a Lucide icon to every sidebar navigation link in the docs site. |
| 34 | + |
| 35 | +Purpose: Improve visual hierarchy and scannability of the docs sidebar — each item should have a recognizable icon that matches its content topic. |
| 36 | +Output: Updated SidebarItem type, icon-aware DocsSidebarItem component, icon assignments on every config entry. |
| 37 | +</objective> |
| 38 | + |
| 39 | +<execution_context> |
| 40 | +@/Users/sn0w/.claude/get-shit-done/workflows/execute-plan.md |
| 41 | +@/Users/sn0w/.claude/get-shit-done/templates/summary.md |
| 42 | +</execution_context> |
| 43 | + |
| 44 | +<context> |
| 45 | +@.planning/STATE.md |
| 46 | + |
| 47 | +Relevant files: |
| 48 | +- website/docs/.vitepress/config.mts — sidebar config with all items |
| 49 | +- website/docs/.vitepress/theme/composables/useSidebar.ts — SidebarItem interface |
| 50 | +- website/docs/.vitepress/theme/components/docs/DocsSidebarItem.vue — renders each link |
| 51 | + |
| 52 | +Tech: lucide-vue-next ^0.564.0 is already installed (see website/package.json). |
| 53 | + |
| 54 | +<interfaces> |
| 55 | +<!-- Current SidebarItem (useSidebar.ts) --> |
| 56 | +export interface SidebarItem { |
| 57 | + text?: string |
| 58 | + link?: string |
| 59 | + items?: SidebarItem[] |
| 60 | + collapsed?: boolean |
| 61 | + base?: string |
| 62 | + docFooterText?: string |
| 63 | +} |
| 64 | + |
| 65 | +<!-- Current DocsSidebarItem.vue template — renders a plain anchor tag, no icon --> |
| 66 | +<a :href="item.link" class="block rounded-md px-2 py-1.5 text-[13px] ..."> |
| 67 | + {{ item.text }} |
| 68 | +</a> |
| 69 | + |
| 70 | +<!-- Current sidebar leaf items from config.mts --> |
| 71 | +{ text: 'Introduction', link: '/docs/getting-started/' } |
| 72 | +{ text: 'Language Basics', link: '/docs/language-basics/' } |
| 73 | +{ text: 'Type System', link: '/docs/type-system/' } |
| 74 | +{ text: 'Iterators', link: '/docs/iterators/' } |
| 75 | +{ text: 'Concurrency', link: '/docs/concurrency/' } |
| 76 | +{ text: 'Web', link: '/docs/web/' } |
| 77 | +{ text: 'Databases', link: '/docs/databases/' } |
| 78 | +{ text: 'Distributed Actors', link: '/docs/distributed/' } |
| 79 | +{ text: 'Developer Tools', link: '/docs/tooling/' } |
| 80 | +{ text: 'Standard Library', link: '/docs/stdlib/' } |
| 81 | +{ text: 'Testing', link: '/docs/testing/' } |
| 82 | +{ text: 'Syntax Cheatsheet', link: '/docs/cheatsheet/' } |
| 83 | +</interfaces> |
| 84 | +</context> |
| 85 | + |
| 86 | +<tasks> |
| 87 | + |
| 88 | +<task type="auto"> |
| 89 | + <name>Task 1: Extend SidebarItem type and wire icon rendering in DocsSidebarItem</name> |
| 90 | + <files> |
| 91 | + website/docs/.vitepress/theme/composables/useSidebar.ts |
| 92 | + website/docs/.vitepress/theme/components/docs/DocsSidebarItem.vue |
| 93 | + </files> |
| 94 | + <action> |
| 95 | + **useSidebar.ts** — Add `icon?: string` field to the SidebarItem interface. No other changes needed. |
| 96 | + |
| 97 | + **DocsSidebarItem.vue** — Use lucide-vue-next's dynamic component pattern to render the icon. Import `defineAsyncComponent` is not needed — lucide-vue-next exports every icon as a named component. Use the `<component :is="...">` approach with a computed lookup. |
| 98 | + |
| 99 | + Concrete implementation: |
| 100 | + |
| 101 | + ```vue |
| 102 | + <script setup lang="ts"> |
| 103 | + import { computed } from 'vue' |
| 104 | + import { useData } from 'vitepress' |
| 105 | + import { isActive, type SidebarItem } from '@/composables/useSidebar' |
| 106 | + import * as LucideIcons from 'lucide-vue-next' |
| 107 | + |
| 108 | + const props = defineProps<{ |
| 109 | + item: SidebarItem |
| 110 | + }>() |
| 111 | + |
| 112 | + const { page } = useData() |
| 113 | + const active = computed(() => isActive(page.value.relativePath, props.item.link)) |
| 114 | + |
| 115 | + const iconComponent = computed(() => { |
| 116 | + if (!props.item.icon) return null |
| 117 | + return (LucideIcons as Record<string, unknown>)[props.item.icon] ?? null |
| 118 | + }) |
| 119 | + </script> |
| 120 | + |
| 121 | + <template> |
| 122 | + <div> |
| 123 | + <a |
| 124 | + :href="item.link" |
| 125 | + class="flex items-center gap-2 rounded-md px-2 py-1.5 text-[13px] transition-colors" |
| 126 | + :class="[ |
| 127 | + active |
| 128 | + ? 'bg-accent text-foreground font-semibold' |
| 129 | + : 'text-muted-foreground hover:text-foreground hover:bg-accent', |
| 130 | + ]" |
| 131 | + > |
| 132 | + <component |
| 133 | + v-if="iconComponent" |
| 134 | + :is="iconComponent" |
| 135 | + class="size-3.5 shrink-0" |
| 136 | + /> |
| 137 | + {{ item.text }} |
| 138 | + </a> |
| 139 | + <!-- Recursive children with left padding --> |
| 140 | + <ul v-if="item.items?.length" class="flex flex-col gap-0.5 pl-3 mt-0.5"> |
| 141 | + <li v-for="child in item.items" :key="child.text"> |
| 142 | + <DocsSidebarItem :item="child" /> |
| 143 | + </li> |
| 144 | + </ul> |
| 145 | + </div> |
| 146 | + </template> |
| 147 | + ``` |
| 148 | + |
| 149 | + Key decisions: |
| 150 | + - `import * as LucideIcons` gives access to every icon by PascalCase name; no bundler issue since tree-shaking still works per export |
| 151 | + - Icon size `size-3.5` (14px) keeps it compact alongside 13px text |
| 152 | + - `shrink-0` prevents icon squishing on narrow sidebars |
| 153 | + - `flex items-center gap-2` replaces `block` on the anchor so icon and text align |
| 154 | + - Items without `icon` set render unchanged (icon slot stays empty) |
| 155 | + </action> |
| 156 | + <verify>npx tsc --noEmit -p /Users/sn0w/Documents/dev/mesh/website/docs/.vitepress/tsconfig.json 2>/dev/null || echo "no tsconfig — skip type check"</verify> |
| 157 | + <done>DocsSidebarItem renders an icon when item.icon is set; items without icon still render correctly; no TypeScript errors</done> |
| 158 | +</task> |
| 159 | + |
| 160 | +<task type="auto"> |
| 161 | + <name>Task 2: Assign icons to every sidebar item in config.mts</name> |
| 162 | + <files> |
| 163 | + website/docs/.vitepress/config.mts |
| 164 | + </files> |
| 165 | + <action> |
| 166 | + Update every leaf item in the `/docs/` sidebar to include an `icon` property. Use lucide-vue-next PascalCase names. |
| 167 | + |
| 168 | + Mapping (chosen for semantic fit): |
| 169 | + - Introduction → `BookOpen` |
| 170 | + - Language Basics → `Code2` |
| 171 | + - Type System → `Shapes` |
| 172 | + - Iterators → `Repeat` |
| 173 | + - Concurrency → `Workflow` |
| 174 | + - Web → `Globe` |
| 175 | + - Databases → `Database` |
| 176 | + - Distributed Actors → `Network` |
| 177 | + - Developer Tools → `Wrench` |
| 178 | + - Standard Library → `Library` |
| 179 | + - Testing → `FlaskConical` |
| 180 | + - Syntax Cheatsheet → `ClipboardList` |
| 181 | + |
| 182 | + Example diff for the Getting Started section: |
| 183 | + ```ts |
| 184 | + { |
| 185 | + text: 'Getting Started', |
| 186 | + items: [ |
| 187 | + { text: 'Introduction', link: '/docs/getting-started/', icon: 'BookOpen' }, |
| 188 | + ], |
| 189 | + }, |
| 190 | + ``` |
| 191 | + |
| 192 | + Apply the same pattern to all 12 leaf items. Do not add icons to group headers (the `text`-only objects with `items` arrays) — those render via DocsSidebarGroup.vue which uses plain text. |
| 193 | + |
| 194 | + Also extend the VitePress `themeConfig` type declaration if TypeScript complains — the `SidebarItem` from VitePress's own types won't have `icon`. Cast each item object with `as any` inline, or add a short module augmentation at the top of config.mts: |
| 195 | + |
| 196 | + ```ts |
| 197 | + declare module 'vitepress' { |
| 198 | + interface UserConfig { |
| 199 | + // No-op — using custom SidebarItem type from composable |
| 200 | + } |
| 201 | + } |
| 202 | + ``` |
| 203 | + |
| 204 | + Actually the simpler fix: the sidebar array is typed via VitePress's own `DefaultTheme.SidebarItem`. Since the custom `DocsSidebarItem.vue` reads from `item.icon` directly (and our `useSidebar.ts` SidebarItem already has the field), VitePress just passes the raw config object through. TypeScript may warn about the extra property — suppress with `// @ts-expect-error` on each icon line, or cast the entire sidebar array `as any`. Use `as any` on the full sidebar value (cleanest). |
| 205 | + </action> |
| 206 | + <verify>cd /Users/sn0w/Documents/dev/mesh/website && npm run build 2>&1 | tail -20</verify> |
| 207 | + <done>Build succeeds with no errors; all 12 sidebar items have icon names assigned in config.mts</done> |
| 208 | +</task> |
| 209 | + |
| 210 | +</tasks> |
| 211 | + |
| 212 | +<verification> |
| 213 | +After both tasks: |
| 214 | +1. Run `cd /Users/sn0w/Documents/dev/mesh/website && npm run build` — build must complete without errors |
| 215 | +2. Run `npm run preview` and open http://localhost:4173/docs/ to confirm icons appear in sidebar |
| 216 | +3. Verify icons render on both active and inactive items |
| 217 | +4. Verify items without explicit icon set (group headers) are unaffected |
| 218 | +</verification> |
| 219 | + |
| 220 | +<success_criteria> |
| 221 | +- Every leaf sidebar link in the docs has a small Lucide icon to its left |
| 222 | +- Build passes without errors or TypeScript failures |
| 223 | +- Icons are visually aligned with text (flex row, 14px, muted color matching text) |
| 224 | +- No visual regression on mobile sidebar (MobileSidebar.vue also uses DocsSidebarItem) |
| 225 | +</success_criteria> |
| 226 | + |
| 227 | +<output> |
| 228 | +After completion, create `.planning/quick/10-add-icons-to-each-button-in-the-docs-sid/10-SUMMARY.md` |
| 229 | +</output> |
0 commit comments