@thanhvn14/csvibe 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (392) hide show
  1. package/.github/agents/schemas/base-output.schema.json +88 -0
  2. package/.github/agents/schemas/brainstorm-output.schema.json +88 -0
  3. package/.github/agents/schemas/scout-output.schema.json +60 -0
  4. package/.github/agents/scripts/fetch-copilot-tools.js +245 -0
  5. package/.github/agents/scripts/lib/parse-agent-file.js +275 -0
  6. package/.github/agents/scripts/package-lock.json +78 -0
  7. package/.github/agents/scripts/package.json +22 -0
  8. package/.github/agents/scripts/schemas/agent-frontmatter.schema.json +83 -0
  9. package/.github/agents/scripts/validate-agent-all.js +157 -0
  10. package/.github/agents/scripts/validate-agent-frontmatter.js +96 -0
  11. package/.github/agents/scripts/validate-agent-handoffs.js +169 -0
  12. package/.github/agents/scripts/validate-agent-output.js +157 -0
  13. package/.github/agents/scripts/validate-agent-tools.js +278 -0
  14. package/.github/skills/.env.example +100 -0
  15. package/.github/skills/.install-state.json +23 -0
  16. package/.github/skills/README.md +149 -0
  17. package/.github/skills/ai-multimodal/.env.example +204 -0
  18. package/.github/skills/ai-multimodal/scripts/.coverage +0 -0
  19. package/.github/skills/ai-multimodal/scripts/check_setup.py +305 -0
  20. package/.github/skills/ai-multimodal/scripts/document_converter.py +395 -0
  21. package/.github/skills/ai-multimodal/scripts/gemini_batch_process.py +1184 -0
  22. package/.github/skills/ai-multimodal/scripts/media_optimizer.py +506 -0
  23. package/.github/skills/ai-multimodal/scripts/requirements.txt +26 -0
  24. package/.github/skills/better-auth/scripts/.coverage +0 -0
  25. package/.github/skills/better-auth/scripts/better_auth_init.py +521 -0
  26. package/.github/skills/better-auth/scripts/requirements.txt +15 -0
  27. package/.github/skills/chrome-devtools/scripts/README.md +272 -0
  28. package/.github/skills/chrome-devtools/scripts/__tests__/selector.test.js +210 -0
  29. package/.github/skills/chrome-devtools/scripts/aria-snapshot.js +362 -0
  30. package/.github/skills/chrome-devtools/scripts/click.js +83 -0
  31. package/.github/skills/chrome-devtools/scripts/console.js +79 -0
  32. package/.github/skills/chrome-devtools/scripts/evaluate.js +53 -0
  33. package/.github/skills/chrome-devtools/scripts/fill.js +76 -0
  34. package/.github/skills/chrome-devtools/scripts/inject-auth.js +229 -0
  35. package/.github/skills/chrome-devtools/scripts/install-deps.sh +181 -0
  36. package/.github/skills/chrome-devtools/scripts/install.sh +83 -0
  37. package/.github/skills/chrome-devtools/scripts/lib/browser.js +318 -0
  38. package/.github/skills/chrome-devtools/scripts/lib/selector.js +178 -0
  39. package/.github/skills/chrome-devtools/scripts/navigate.js +54 -0
  40. package/.github/skills/chrome-devtools/scripts/network.js +106 -0
  41. package/.github/skills/chrome-devtools/scripts/package-lock.json +1589 -0
  42. package/.github/skills/chrome-devtools/scripts/package.json +16 -0
  43. package/.github/skills/chrome-devtools/scripts/performance.js +149 -0
  44. package/.github/skills/chrome-devtools/scripts/screenshot.js +198 -0
  45. package/.github/skills/chrome-devtools/scripts/select-ref.js +131 -0
  46. package/.github/skills/chrome-devtools/scripts/snapshot.js +135 -0
  47. package/.github/skills/common/README.md +120 -0
  48. package/.github/skills/common/api_key_helper.py +411 -0
  49. package/.github/skills/common/api_key_rotator.py +248 -0
  50. package/.github/skills/databases/scripts/.coverage +0 -0
  51. package/.github/skills/databases/scripts/db_backup.py +502 -0
  52. package/.github/skills/databases/scripts/db_migrate.py +425 -0
  53. package/.github/skills/databases/scripts/db_performance_check.py +456 -0
  54. package/.github/skills/databases/scripts/requirements.txt +20 -0
  55. package/.github/skills/debugging/scripts/find-polluter.sh +63 -0
  56. package/.github/skills/devops/.env.example +76 -0
  57. package/.github/skills/devops/scripts/cloudflare_deploy.py +269 -0
  58. package/.github/skills/devops/scripts/docker_optimize.py +331 -0
  59. package/.github/skills/devops/scripts/requirements.txt +20 -0
  60. package/.github/skills/docs-seeker/.env.example +15 -0
  61. package/.github/skills/docs-seeker/package.json +25 -0
  62. package/.github/skills/docs-seeker/scripts/analyze-llms-txt.js +211 -0
  63. package/.github/skills/docs-seeker/scripts/detect-topic.js +172 -0
  64. package/.github/skills/docs-seeker/scripts/fetch-docs.js +213 -0
  65. package/.github/skills/docs-seeker/scripts/utils/env-loader.js +94 -0
  66. package/.github/skills/document-skills/docx/LICENSE.txt +30 -0
  67. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  68. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  69. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  70. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  71. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  72. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  73. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  74. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  75. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  76. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  77. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  78. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  79. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  80. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  81. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  82. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  83. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  84. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  85. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  86. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  87. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  88. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  89. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  90. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  91. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  92. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  93. package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  94. package/.github/skills/document-skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  95. package/.github/skills/document-skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  96. package/.github/skills/document-skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  97. package/.github/skills/document-skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  98. package/.github/skills/document-skills/docx/ooxml/schemas/mce/mc.xsd +75 -0
  99. package/.github/skills/document-skills/docx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  100. package/.github/skills/document-skills/docx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  101. package/.github/skills/document-skills/docx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  102. package/.github/skills/document-skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  103. package/.github/skills/document-skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  104. package/.github/skills/document-skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  105. package/.github/skills/document-skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  106. package/.github/skills/document-skills/docx/ooxml/scripts/pack.py +159 -0
  107. package/.github/skills/document-skills/docx/ooxml/scripts/unpack.py +29 -0
  108. package/.github/skills/document-skills/docx/ooxml/scripts/validate.py +69 -0
  109. package/.github/skills/document-skills/docx/ooxml/scripts/validation/__init__.py +15 -0
  110. package/.github/skills/document-skills/docx/ooxml/scripts/validation/base.py +951 -0
  111. package/.github/skills/document-skills/docx/ooxml/scripts/validation/docx.py +274 -0
  112. package/.github/skills/document-skills/docx/ooxml/scripts/validation/pptx.py +315 -0
  113. package/.github/skills/document-skills/docx/ooxml/scripts/validation/redlining.py +279 -0
  114. package/.github/skills/document-skills/docx/scripts/__init__.py +1 -0
  115. package/.github/skills/document-skills/docx/scripts/document.py +1276 -0
  116. package/.github/skills/document-skills/docx/scripts/templates/comments.xml +3 -0
  117. package/.github/skills/document-skills/docx/scripts/templates/commentsExtended.xml +3 -0
  118. package/.github/skills/document-skills/docx/scripts/templates/commentsExtensible.xml +3 -0
  119. package/.github/skills/document-skills/docx/scripts/templates/commentsIds.xml +3 -0
  120. package/.github/skills/document-skills/docx/scripts/templates/people.xml +3 -0
  121. package/.github/skills/document-skills/docx/scripts/utilities.py +374 -0
  122. package/.github/skills/document-skills/pdf/LICENSE.txt +30 -0
  123. package/.github/skills/document-skills/pdf/scripts/check_bounding_boxes.py +70 -0
  124. package/.github/skills/document-skills/pdf/scripts/check_bounding_boxes_test.py +226 -0
  125. package/.github/skills/document-skills/pdf/scripts/check_fillable_fields.py +12 -0
  126. package/.github/skills/document-skills/pdf/scripts/convert_pdf_to_images.py +35 -0
  127. package/.github/skills/document-skills/pdf/scripts/create_validation_image.py +41 -0
  128. package/.github/skills/document-skills/pdf/scripts/extract_form_field_info.py +152 -0
  129. package/.github/skills/document-skills/pdf/scripts/fill_fillable_fields.py +114 -0
  130. package/.github/skills/document-skills/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
  131. package/.github/skills/document-skills/pptx/LICENSE.txt +30 -0
  132. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  133. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  134. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  135. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  136. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  137. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  138. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  139. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  140. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  141. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  142. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  143. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  144. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  145. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  146. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  147. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  148. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  149. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  150. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  151. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  152. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  153. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  154. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  155. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  156. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  157. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  158. package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  159. package/.github/skills/document-skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  160. package/.github/skills/document-skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  161. package/.github/skills/document-skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  162. package/.github/skills/document-skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  163. package/.github/skills/document-skills/pptx/ooxml/schemas/mce/mc.xsd +75 -0
  164. package/.github/skills/document-skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  165. package/.github/skills/document-skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  166. package/.github/skills/document-skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  167. package/.github/skills/document-skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  168. package/.github/skills/document-skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  169. package/.github/skills/document-skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  170. package/.github/skills/document-skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  171. package/.github/skills/document-skills/pptx/ooxml/scripts/pack.py +159 -0
  172. package/.github/skills/document-skills/pptx/ooxml/scripts/unpack.py +29 -0
  173. package/.github/skills/document-skills/pptx/ooxml/scripts/validate.py +69 -0
  174. package/.github/skills/document-skills/pptx/ooxml/scripts/validation/__init__.py +15 -0
  175. package/.github/skills/document-skills/pptx/ooxml/scripts/validation/base.py +951 -0
  176. package/.github/skills/document-skills/pptx/ooxml/scripts/validation/docx.py +274 -0
  177. package/.github/skills/document-skills/pptx/ooxml/scripts/validation/pptx.py +315 -0
  178. package/.github/skills/document-skills/pptx/ooxml/scripts/validation/redlining.py +279 -0
  179. package/.github/skills/document-skills/pptx/scripts/html2pptx.js +979 -0
  180. package/.github/skills/document-skills/pptx/scripts/inventory.py +1020 -0
  181. package/.github/skills/document-skills/pptx/scripts/rearrange.py +231 -0
  182. package/.github/skills/document-skills/pptx/scripts/replace.py +385 -0
  183. package/.github/skills/document-skills/pptx/scripts/thumbnail.py +450 -0
  184. package/.github/skills/document-skills/xlsx/LICENSE.txt +30 -0
  185. package/.github/skills/document-skills/xlsx/recalc.py +190 -0
  186. package/.github/skills/install.ps1 +1220 -0
  187. package/.github/skills/install.sh +1032 -0
  188. package/.github/skills/markdown-novel-viewer/assets/directory-browser.css +215 -0
  189. package/.github/skills/markdown-novel-viewer/assets/favicon.png +0 -0
  190. package/.github/skills/markdown-novel-viewer/assets/novel-theme.css +818 -0
  191. package/.github/skills/markdown-novel-viewer/assets/reader.js +262 -0
  192. package/.github/skills/markdown-novel-viewer/assets/template.html +80 -0
  193. package/.github/skills/markdown-novel-viewer/package-lock.json +146 -0
  194. package/.github/skills/markdown-novel-viewer/package.json +15 -0
  195. package/.github/skills/markdown-novel-viewer/scripts/lib/http-server.cjs +434 -0
  196. package/.github/skills/markdown-novel-viewer/scripts/lib/markdown-renderer.cjs +272 -0
  197. package/.github/skills/markdown-novel-viewer/scripts/lib/plan-navigator.cjs +509 -0
  198. package/.github/skills/markdown-novel-viewer/scripts/lib/port-finder.cjs +48 -0
  199. package/.github/skills/markdown-novel-viewer/scripts/lib/process-mgr.cjs +150 -0
  200. package/.github/skills/markdown-novel-viewer/scripts/server.cjs +411 -0
  201. package/.github/skills/mcp-builder/LICENSE.txt +202 -0
  202. package/.github/skills/mcp-builder/scripts/connections.py +151 -0
  203. package/.github/skills/mcp-builder/scripts/evaluation.py +373 -0
  204. package/.github/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  205. package/.github/skills/mcp-builder/scripts/requirements.txt +2 -0
  206. package/.github/skills/mcp-management/README.md +219 -0
  207. package/.github/skills/mcp-management/assets/tools.json +3146 -0
  208. package/.github/skills/mcp-management/package-lock.json +6 -0
  209. package/.github/skills/mcp-management/scripts/.env.example +10 -0
  210. package/.github/skills/mcp-management/scripts/cli.ts +195 -0
  211. package/.github/skills/mcp-management/scripts/dist/analyze-tools.js +70 -0
  212. package/.github/skills/mcp-management/scripts/dist/cli.js +160 -0
  213. package/.github/skills/mcp-management/scripts/dist/mcp-client.js +183 -0
  214. package/.github/skills/mcp-management/scripts/mcp-client.ts +230 -0
  215. package/.github/skills/mcp-management/scripts/package.json +20 -0
  216. package/.github/skills/media-processing/scripts/README.md +111 -0
  217. package/.github/skills/media-processing/scripts/batch-remove-background.sh +124 -0
  218. package/.github/skills/media-processing/scripts/batch_resize.py +342 -0
  219. package/.github/skills/media-processing/scripts/media_convert.py +311 -0
  220. package/.github/skills/media-processing/scripts/remove-background.sh +96 -0
  221. package/.github/skills/media-processing/scripts/remove-bg-node.js +158 -0
  222. package/.github/skills/media-processing/scripts/requirements.txt +24 -0
  223. package/.github/skills/media-processing/scripts/video_optimize.py +414 -0
  224. package/.github/skills/payment-integration/README.md +185 -0
  225. package/.github/skills/payment-integration/scripts/.env.example +20 -0
  226. package/.github/skills/payment-integration/scripts/checkout-helper.js +244 -0
  227. package/.github/skills/payment-integration/scripts/package.json +17 -0
  228. package/.github/skills/payment-integration/scripts/polar-webhook-verify.js +202 -0
  229. package/.github/skills/payment-integration/scripts/sepay-webhook-verify.js +193 -0
  230. package/.github/skills/payment-integration/scripts/test-scripts.js +237 -0
  231. package/.github/skills/plans-kanban/assets/dashboard-template.html +119 -0
  232. package/.github/skills/plans-kanban/assets/dashboard.css +1594 -0
  233. package/.github/skills/plans-kanban/assets/dashboard.js +596 -0
  234. package/.github/skills/plans-kanban/assets/favicon.png +0 -0
  235. package/.github/skills/plans-kanban/package-lock.json +123 -0
  236. package/.github/skills/plans-kanban/package.json +13 -0
  237. package/.github/skills/plans-kanban/scripts/lib/dashboard-renderer.cjs +884 -0
  238. package/.github/skills/plans-kanban/scripts/lib/http-server.cjs +310 -0
  239. package/.github/skills/plans-kanban/scripts/lib/plan-metadata-extractor.cjs +489 -0
  240. package/.github/skills/plans-kanban/scripts/lib/plan-parser.cjs +175 -0
  241. package/.github/skills/plans-kanban/scripts/lib/plan-scanner.cjs +272 -0
  242. package/.github/skills/plans-kanban/scripts/lib/port-finder.cjs +48 -0
  243. package/.github/skills/plans-kanban/scripts/lib/process-mgr.cjs +128 -0
  244. package/.github/skills/plans-kanban/scripts/server.cjs +260 -0
  245. package/.github/skills/repomix/scripts/.coverage +0 -0
  246. package/.github/skills/repomix/scripts/README.md +179 -0
  247. package/.github/skills/repomix/scripts/repomix_batch.py +455 -0
  248. package/.github/skills/repomix/scripts/repos.example.json +15 -0
  249. package/.github/skills/repomix/scripts/requirements.txt +15 -0
  250. package/.github/skills/scout-validation/scripts/lib/broad-pattern-detector.cjs +124 -0
  251. package/.github/skills/scout-validation/scripts/lib/path-checker.cjs +66 -0
  252. package/.github/skills/scout-validation/scripts/lib/schema-validator.cjs +45 -0
  253. package/.github/skills/scout-validation/scripts/package.json +11 -0
  254. package/.github/skills/scout-validation/scripts/validate-scout-output.cjs +219 -0
  255. package/.github/skills/scout-validation/test/broad-pattern-output.json +18 -0
  256. package/.github/skills/scout-validation/test/invalid-path-output.json +18 -0
  257. package/.github/skills/scout-validation/test/valid-scout-output.json +26 -0
  258. package/.github/skills/sequential-thinking/.env.example +8 -0
  259. package/.github/skills/sequential-thinking/README.md +183 -0
  260. package/.github/skills/sequential-thinking/package.json +31 -0
  261. package/.github/skills/sequential-thinking/scripts/format-thought.js +159 -0
  262. package/.github/skills/sequential-thinking/scripts/process-thought.js +236 -0
  263. package/.github/skills/shopify/README.md +66 -0
  264. package/.github/skills/shopify/scripts/.coverage +0 -0
  265. package/.github/skills/shopify/scripts/requirements.txt +19 -0
  266. package/.github/skills/shopify/scripts/shopify_init.py +423 -0
  267. package/.github/skills/skill-creator/LICENSE.txt +202 -0
  268. package/.github/skills/skill-creator/scripts/init_skill.py +303 -0
  269. package/.github/skills/skill-creator/scripts/package_skill.py +110 -0
  270. package/.github/skills/skill-creator/scripts/quick_validate.py +65 -0
  271. package/.github/skills/ui-styling/LICENSE.txt +202 -0
  272. package/.github/skills/ui-styling/canvas-fonts/ArsenalSC-OFL.txt +93 -0
  273. package/.github/skills/ui-styling/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
  274. package/.github/skills/ui-styling/canvas-fonts/BigShoulders-Bold.ttf +0 -0
  275. package/.github/skills/ui-styling/canvas-fonts/BigShoulders-OFL.txt +93 -0
  276. package/.github/skills/ui-styling/canvas-fonts/BigShoulders-Regular.ttf +0 -0
  277. package/.github/skills/ui-styling/canvas-fonts/Boldonse-OFL.txt +93 -0
  278. package/.github/skills/ui-styling/canvas-fonts/Boldonse-Regular.ttf +0 -0
  279. package/.github/skills/ui-styling/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
  280. package/.github/skills/ui-styling/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
  281. package/.github/skills/ui-styling/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
  282. package/.github/skills/ui-styling/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
  283. package/.github/skills/ui-styling/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
  284. package/.github/skills/ui-styling/canvas-fonts/CrimsonPro-OFL.txt +93 -0
  285. package/.github/skills/ui-styling/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
  286. package/.github/skills/ui-styling/canvas-fonts/DMMono-OFL.txt +93 -0
  287. package/.github/skills/ui-styling/canvas-fonts/DMMono-Regular.ttf +0 -0
  288. package/.github/skills/ui-styling/canvas-fonts/EricaOne-OFL.txt +94 -0
  289. package/.github/skills/ui-styling/canvas-fonts/EricaOne-Regular.ttf +0 -0
  290. package/.github/skills/ui-styling/canvas-fonts/GeistMono-Bold.ttf +0 -0
  291. package/.github/skills/ui-styling/canvas-fonts/GeistMono-OFL.txt +93 -0
  292. package/.github/skills/ui-styling/canvas-fonts/GeistMono-Regular.ttf +0 -0
  293. package/.github/skills/ui-styling/canvas-fonts/Gloock-OFL.txt +93 -0
  294. package/.github/skills/ui-styling/canvas-fonts/Gloock-Regular.ttf +0 -0
  295. package/.github/skills/ui-styling/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
  296. package/.github/skills/ui-styling/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
  297. package/.github/skills/ui-styling/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
  298. package/.github/skills/ui-styling/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
  299. package/.github/skills/ui-styling/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
  300. package/.github/skills/ui-styling/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
  301. package/.github/skills/ui-styling/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
  302. package/.github/skills/ui-styling/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
  303. package/.github/skills/ui-styling/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
  304. package/.github/skills/ui-styling/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
  305. package/.github/skills/ui-styling/canvas-fonts/InstrumentSans-OFL.txt +93 -0
  306. package/.github/skills/ui-styling/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
  307. package/.github/skills/ui-styling/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
  308. package/.github/skills/ui-styling/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
  309. package/.github/skills/ui-styling/canvas-fonts/Italiana-OFL.txt +93 -0
  310. package/.github/skills/ui-styling/canvas-fonts/Italiana-Regular.ttf +0 -0
  311. package/.github/skills/ui-styling/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
  312. package/.github/skills/ui-styling/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
  313. package/.github/skills/ui-styling/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
  314. package/.github/skills/ui-styling/canvas-fonts/Jura-Light.ttf +0 -0
  315. package/.github/skills/ui-styling/canvas-fonts/Jura-Medium.ttf +0 -0
  316. package/.github/skills/ui-styling/canvas-fonts/Jura-OFL.txt +93 -0
  317. package/.github/skills/ui-styling/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
  318. package/.github/skills/ui-styling/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
  319. package/.github/skills/ui-styling/canvas-fonts/Lora-Bold.ttf +0 -0
  320. package/.github/skills/ui-styling/canvas-fonts/Lora-BoldItalic.ttf +0 -0
  321. package/.github/skills/ui-styling/canvas-fonts/Lora-Italic.ttf +0 -0
  322. package/.github/skills/ui-styling/canvas-fonts/Lora-OFL.txt +93 -0
  323. package/.github/skills/ui-styling/canvas-fonts/Lora-Regular.ttf +0 -0
  324. package/.github/skills/ui-styling/canvas-fonts/NationalPark-Bold.ttf +0 -0
  325. package/.github/skills/ui-styling/canvas-fonts/NationalPark-OFL.txt +93 -0
  326. package/.github/skills/ui-styling/canvas-fonts/NationalPark-Regular.ttf +0 -0
  327. package/.github/skills/ui-styling/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
  328. package/.github/skills/ui-styling/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
  329. package/.github/skills/ui-styling/canvas-fonts/Outfit-Bold.ttf +0 -0
  330. package/.github/skills/ui-styling/canvas-fonts/Outfit-OFL.txt +93 -0
  331. package/.github/skills/ui-styling/canvas-fonts/Outfit-Regular.ttf +0 -0
  332. package/.github/skills/ui-styling/canvas-fonts/PixelifySans-Medium.ttf +0 -0
  333. package/.github/skills/ui-styling/canvas-fonts/PixelifySans-OFL.txt +93 -0
  334. package/.github/skills/ui-styling/canvas-fonts/PoiretOne-OFL.txt +93 -0
  335. package/.github/skills/ui-styling/canvas-fonts/PoiretOne-Regular.ttf +0 -0
  336. package/.github/skills/ui-styling/canvas-fonts/RedHatMono-Bold.ttf +0 -0
  337. package/.github/skills/ui-styling/canvas-fonts/RedHatMono-OFL.txt +93 -0
  338. package/.github/skills/ui-styling/canvas-fonts/RedHatMono-Regular.ttf +0 -0
  339. package/.github/skills/ui-styling/canvas-fonts/Silkscreen-OFL.txt +93 -0
  340. package/.github/skills/ui-styling/canvas-fonts/Silkscreen-Regular.ttf +0 -0
  341. package/.github/skills/ui-styling/canvas-fonts/SmoochSans-Medium.ttf +0 -0
  342. package/.github/skills/ui-styling/canvas-fonts/SmoochSans-OFL.txt +93 -0
  343. package/.github/skills/ui-styling/canvas-fonts/Tektur-Medium.ttf +0 -0
  344. package/.github/skills/ui-styling/canvas-fonts/Tektur-OFL.txt +93 -0
  345. package/.github/skills/ui-styling/canvas-fonts/Tektur-Regular.ttf +0 -0
  346. package/.github/skills/ui-styling/canvas-fonts/WorkSans-Bold.ttf +0 -0
  347. package/.github/skills/ui-styling/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
  348. package/.github/skills/ui-styling/canvas-fonts/WorkSans-Italic.ttf +0 -0
  349. package/.github/skills/ui-styling/canvas-fonts/WorkSans-OFL.txt +93 -0
  350. package/.github/skills/ui-styling/canvas-fonts/WorkSans-Regular.ttf +0 -0
  351. package/.github/skills/ui-styling/canvas-fonts/YoungSerif-OFL.txt +93 -0
  352. package/.github/skills/ui-styling/canvas-fonts/YoungSerif-Regular.ttf +0 -0
  353. package/.github/skills/ui-styling/scripts/.coverage +0 -0
  354. package/.github/skills/ui-styling/scripts/requirements.txt +17 -0
  355. package/.github/skills/ui-styling/scripts/shadcn_add.py +292 -0
  356. package/.github/skills/ui-styling/scripts/tailwind_config_gen.py +456 -0
  357. package/.github/skills/ui-ux-pro-max/data/charts.csv +26 -0
  358. package/.github/skills/ui-ux-pro-max/data/colors.csv +97 -0
  359. package/.github/skills/ui-ux-pro-max/data/landing.csv +31 -0
  360. package/.github/skills/ui-ux-pro-max/data/products.csv +97 -0
  361. package/.github/skills/ui-ux-pro-max/data/prompts.csv +24 -0
  362. package/.github/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  363. package/.github/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +51 -0
  364. package/.github/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  365. package/.github/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  366. package/.github/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  367. package/.github/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  368. package/.github/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  369. package/.github/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  370. package/.github/skills/ui-ux-pro-max/data/styles.csv +59 -0
  371. package/.github/skills/ui-ux-pro-max/data/typography.csv +58 -0
  372. package/.github/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  373. package/.github/skills/ui-ux-pro-max/scripts/core.py +236 -0
  374. package/.github/skills/ui-ux-pro-max/scripts/search.py +76 -0
  375. package/.github/skills/web-frameworks/scripts/.coverage +0 -0
  376. package/.github/skills/web-frameworks/scripts/__init__.py +0 -0
  377. package/.github/skills/web-frameworks/scripts/nextjs_init.py +547 -0
  378. package/.github/skills/web-frameworks/scripts/requirements.txt +16 -0
  379. package/.github/skills/web-frameworks/scripts/turborepo_migrate.py +394 -0
  380. package/dist/config/constants.d.ts +2 -0
  381. package/dist/config/constants.d.ts.map +1 -1
  382. package/dist/config/constants.js +4 -1
  383. package/dist/config/constants.js.map +1 -1
  384. package/dist/domains/github/github-client.d.ts +5 -0
  385. package/dist/domains/github/github-client.d.ts.map +1 -1
  386. package/dist/domains/github/github-client.js +44 -0
  387. package/dist/domains/github/github-client.js.map +1 -1
  388. package/dist/utils/downloader.d.ts +3 -1
  389. package/dist/utils/downloader.d.ts.map +1 -1
  390. package/dist/utils/downloader.js +48 -11
  391. package/dist/utils/downloader.js.map +1 -1
  392. package/package.json +3 -1
@@ -0,0 +1,884 @@
1
+ /**
2
+ * Dashboard Renderer
3
+ * Generates HTML for the enhanced plans dashboard view with glassmorphism design
4
+ *
5
+ * @module dashboard-renderer
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const {
11
+ generateTimelineStats,
12
+ generateActivityHeatmap
13
+ } = require('./plan-metadata-extractor.cjs');
14
+
15
+ /**
16
+ * Escape HTML special characters to prevent XSS
17
+ * @param {string} str - String to escape
18
+ * @returns {string} - Escaped string
19
+ */
20
+ function escapeHtml(str) {
21
+ if (!str) return '';
22
+ return String(str)
23
+ .replace(/&/g, '&')
24
+ .replace(/</g, '&lt;')
25
+ .replace(/>/g, '&gt;')
26
+ .replace(/"/g, '&quot;')
27
+ .replace(/'/g, '&#039;');
28
+ }
29
+
30
+ /**
31
+ * Truncate text to specified length with ellipsis
32
+ * @param {string} text - Text to truncate
33
+ * @param {number} maxLen - Maximum length
34
+ * @returns {string} - Truncated text
35
+ */
36
+ function truncate(text, maxLen = 100) {
37
+ if (!text) return '';
38
+ if (text.length <= maxLen) return text;
39
+ return text.slice(0, maxLen - 3).trim() + '...';
40
+ }
41
+
42
+ /**
43
+ * Get priority color class based on priority level
44
+ * @param {string} priority - Priority string (P1/P2/P3 or High/Medium/Low)
45
+ * @returns {string} - CSS class name
46
+ */
47
+ function getPriorityColorClass(priority) {
48
+ if (!priority) return '';
49
+ const p = String(priority).toUpperCase();
50
+ if (p === 'P1' || p === 'HIGH' || p === 'CRITICAL') return 'priority-high';
51
+ if (p === 'P2' || p === 'MEDIUM' || p === 'NORMAL') return 'priority-medium';
52
+ if (p === 'P3' || p === 'LOW') return 'priority-low';
53
+ return '';
54
+ }
55
+
56
+ /**
57
+ * Format date for display
58
+ * @param {string} isoDate - ISO date string
59
+ * @returns {string} - Formatted date
60
+ */
61
+ function formatDate(isoDate) {
62
+ if (!isoDate) return '';
63
+ const date = new Date(isoDate);
64
+ return date.toLocaleDateString('en-US', {
65
+ month: 'short',
66
+ day: 'numeric',
67
+ year: 'numeric'
68
+ });
69
+ }
70
+
71
+ /**
72
+ * Format relative time (e.g., "2 days ago")
73
+ * @param {string} isoDate - ISO date string
74
+ * @returns {string} - Relative time string
75
+ */
76
+ function formatRelativeTime(isoDate) {
77
+ if (!isoDate) return '';
78
+ const date = new Date(isoDate);
79
+ const now = new Date();
80
+ const diffMs = now - date;
81
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
82
+
83
+ if (diffDays === 0) return 'Today';
84
+ if (diffDays === 1) return 'Yesterday';
85
+ if (diffDays < 7) return `${diffDays} days ago`;
86
+ if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`;
87
+ if (diffDays < 365) return `${Math.floor(diffDays / 30)} months ago`;
88
+ return `${Math.floor(diffDays / 365)} years ago`;
89
+ }
90
+
91
+ /**
92
+ * Get human-readable status label
93
+ * @param {string} status - Status code
94
+ * @returns {string} - Human-readable label
95
+ */
96
+ function getStatusLabel(status) {
97
+ const labels = {
98
+ 'completed': 'Completed',
99
+ 'complete': 'Completed',
100
+ 'in-progress': 'In Progress',
101
+ 'in-review': 'In Review',
102
+ 'cancelled': 'Cancelled',
103
+ 'pending': 'Pending'
104
+ };
105
+ return labels[status] || 'Pending';
106
+ }
107
+
108
+ /**
109
+ * Generate SVG progress ring (kept for backward compatibility but hidden in new design)
110
+ * @param {number} progress - Progress percentage (0-100)
111
+ * @returns {string} - SVG HTML
112
+ */
113
+ function generateProgressRing(progress) {
114
+ // Hidden in new minimal design - kept for compatibility
115
+ return '';
116
+ }
117
+
118
+ /**
119
+ * Generate simple progress bar (monochrome design)
120
+ * @param {{total: number, completed: number, inProgress: number, pending: number}} phases
121
+ * @returns {string} - Progress bar HTML
122
+ */
123
+ function generateProgressBar(phases) {
124
+ const total = phases.total || 1;
125
+ const completedPct = ((phases.completed / total) * 100).toFixed(1);
126
+ const inProgressPct = ((phases.inProgress / total) * 100).toFixed(1);
127
+
128
+ return `
129
+ <div class="progress-bar" role="progressbar"
130
+ aria-valuenow="${phases.completed}" aria-valuemin="0" aria-valuemax="${total}"
131
+ aria-label="Progress: ${phases.completed} of ${total} phases completed">
132
+ <div class="bar-segment completed" style="width: ${completedPct}%"></div>
133
+ <div class="bar-segment in-progress" style="width: ${inProgressPct}%"></div>
134
+ </div>
135
+ <div class="phase-count"><strong>${phases.completed}</strong> of ${total} phases</div>
136
+ `;
137
+ }
138
+
139
+ /**
140
+ * Generate status counts HTML (hidden in minimal design)
141
+ * @param {{completed: number, inProgress: number, pending: number}} phases
142
+ * @returns {string} - Status counts HTML
143
+ */
144
+ function generateStatusCounts(phases) {
145
+ // Hidden in minimal design
146
+ return '';
147
+ }
148
+
149
+ /**
150
+ * Generate status badge HTML (simplified for monochrome design)
151
+ * @param {string} status - Status string
152
+ * @returns {string} - Status badge HTML
153
+ */
154
+ function generateStatusBadge(status) {
155
+ const statusClass = (status || 'pending').replace(/\s+/g, '-');
156
+ // Simplified labels for minimal design
157
+ const labels = {
158
+ 'completed': 'Done',
159
+ 'complete': 'Done',
160
+ 'in-progress': 'Active',
161
+ 'pending': 'Pending'
162
+ };
163
+ const label = labels[statusClass] || 'Pending';
164
+ return `<span class="status-badge ${statusClass}">${label}</span>`;
165
+ }
166
+
167
+ /**
168
+ * Generate meta tags HTML for plan card (duration, effort, priority, issue, tags)
169
+ * @param {Object} plan - Plan metadata
170
+ * @returns {string} - Meta tags HTML
171
+ */
172
+ function generateCardMeta(plan) {
173
+ const metaTags = [];
174
+
175
+ // Duration tag
176
+ if (plan.durationFormatted) {
177
+ const icon = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>';
178
+ metaTags.push(`<span class="meta-tag duration" title="Duration">${icon} ${escapeHtml(plan.durationFormatted)}</span>`);
179
+ }
180
+
181
+ // Effort tag
182
+ if (plan.totalEffortFormatted) {
183
+ metaTags.push(`<span class="meta-tag effort" title="Estimated effort"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg> ${escapeHtml(plan.totalEffortFormatted)}</span>`);
184
+ }
185
+
186
+ // Priority tag with color class
187
+ if (plan.priority) {
188
+ const priorityColorClass = getPriorityColorClass(plan.priority);
189
+ metaTags.push(`<span class="meta-tag priority ${priorityColorClass}" title="Priority">${escapeHtml(plan.priority)}</span>`);
190
+ }
191
+
192
+ // Issue tag - clickable link to GitHub (uses branch to derive repo, falls back to claudekit)
193
+ if (plan.issue) {
194
+ // TODO: Make repo configurable via project settings
195
+ const issueUrl = `https://github.com/claudekit/claudekit/issues/${plan.issue}`;
196
+ metaTags.push(`<a href="${issueUrl}" target="_blank" rel="noopener" class="meta-tag issue" title="Issue #${plan.issue}"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg> #${plan.issue}</a>`);
197
+ }
198
+
199
+ if (metaTags.length === 0) return '';
200
+ return `<div class="card-meta">${metaTags.join('')}</div>`;
201
+ }
202
+
203
+ /**
204
+ * Generate tags pills HTML
205
+ * @param {Array<string>} tags - Array of tag strings
206
+ * @param {number} maxVisible - Maximum visible tags (default 3)
207
+ * @returns {string} - Tags HTML
208
+ */
209
+ function generateTagsPills(tags, maxVisible = 3) {
210
+ if (!tags || !Array.isArray(tags) || tags.length === 0) return '';
211
+
212
+ const visibleTags = tags.slice(0, maxVisible);
213
+ const hiddenCount = tags.length - maxVisible;
214
+
215
+ let html = '<div class="card-tags">';
216
+ html += visibleTags.map(tag =>
217
+ `<span class="tag-pill">${escapeHtml(tag)}</span>`
218
+ ).join('');
219
+
220
+ if (hiddenCount > 0) {
221
+ html += `<span class="tag-pill tag-more">+${hiddenCount}</span>`;
222
+ }
223
+ html += '</div>';
224
+
225
+ return html;
226
+ }
227
+
228
+ /**
229
+ * Generate HTML for a single plan card (minimal design with rich metadata)
230
+ * @param {Object} plan - Plan metadata
231
+ * @returns {string} - Card HTML
232
+ */
233
+ function generatePlanCard(plan) {
234
+ const statusClass = (plan.status || 'pending').replace(/\s+/g, '-');
235
+ const name = escapeHtml(plan.name);
236
+ const relativeTime = formatRelativeTime(plan.lastModified);
237
+ const cardMeta = generateCardMeta(plan);
238
+
239
+ // Description section (truncated)
240
+ const descriptionHtml = plan.description
241
+ ? `<p class="card-description">${escapeHtml(truncate(plan.description, 100))}</p>`
242
+ : '';
243
+
244
+ // Tags pills
245
+ const tagsHtml = generateTagsPills(plan.tags);
246
+
247
+ return `
248
+ <article class="plan-card" data-status="${statusClass}" data-id="${escapeHtml(plan.id)}" tabindex="0"
249
+ data-created="${plan.createdDate || ''}" data-duration="${plan.durationDays || 0}"
250
+ data-effort="${plan.totalEffortHours || 0}" data-priority="${plan.priority || ''}">
251
+ <header class="card-header">
252
+ <div class="card-header-content">
253
+ <h2 class="plan-name">${name}</h2>
254
+ <div class="plan-date">
255
+ <time datetime="${plan.lastModified}">${relativeTime}</time>
256
+ </div>
257
+ </div>
258
+ ${generateStatusBadge(statusClass)}
259
+ </header>
260
+ <div class="card-body">
261
+ ${descriptionHtml}
262
+ ${generateProgressBar(plan.phases)}
263
+ ${cardMeta}
264
+ ${tagsHtml}
265
+ </div>
266
+ <footer class="card-footer">
267
+ <div class="phases-summary">${plan.phases.total} phases total</div>
268
+ <a href="/view?file=${encodeURIComponent(plan.path)}" class="view-btn">
269
+ View
270
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
271
+ <path d="M5 12h14M12 5l7 7-7 7"/>
272
+ </svg>
273
+ </a>
274
+ </footer>
275
+ </article>
276
+ `;
277
+ }
278
+
279
+ /**
280
+ * Auto-stack plans into layers to avoid overlap (like Google Calendar)
281
+ * Uses actual plan duration for in-progress items instead of extending to today
282
+ * @param {Array} plans - Plans with createdDate and durationDays
283
+ * @param {Date} rangeStart - Start of visible range
284
+ * @param {Date} rangeEnd - End of visible range
285
+ * @returns {Array} - Plans with layer assignments
286
+ */
287
+ function assignLayers(plans, rangeStart, rangeEnd) {
288
+ const rangeDays = Math.ceil((rangeEnd - rangeStart) / (1000 * 60 * 60 * 24));
289
+ const now = new Date();
290
+ const layers = []; // Each layer tracks occupied day ranges
291
+
292
+ // Filter to plans with dates, then sort by start date
293
+ const sorted = [...plans]
294
+ .filter(p => p.createdDate)
295
+ .sort((a, b) => new Date(a.createdDate) - new Date(b.createdDate));
296
+
297
+ // Filter to plans within visible range
298
+ const visible = sorted.filter(plan => {
299
+ const startDate = new Date(plan.createdDate);
300
+ const endDate = plan.completedDate
301
+ ? new Date(plan.completedDate)
302
+ : new Date(startDate.getTime() + (plan.durationDays || 1) * 24 * 60 * 60 * 1000);
303
+ return endDate >= rangeStart && startDate <= rangeEnd;
304
+ });
305
+
306
+ return visible.map(plan => {
307
+ const startDate = new Date(plan.createdDate);
308
+ // Determine end date based on status
309
+ let endDate;
310
+ if (plan.completedDate) {
311
+ endDate = new Date(plan.completedDate);
312
+ } else if (plan.status === 'completed') {
313
+ // Completed without explicit date: use lastModified or cap at today
314
+ endDate = plan.lastModified ? new Date(plan.lastModified) : now;
315
+ } else {
316
+ // In-progress/pending: use duration from start
317
+ endDate = new Date(startDate.getTime() + Math.max(1, plan.durationDays || 1) * 24 * 60 * 60 * 1000);
318
+ }
319
+ // Completed plans can't extend past today
320
+ if (plan.status === 'completed' && endDate > now) {
321
+ endDate = now;
322
+ }
323
+
324
+ // Calculate position as percentage (clamp to range)
325
+ const startDay = Math.max(0, Math.ceil((startDate - rangeStart) / (1000 * 60 * 60 * 24)));
326
+ const endDay = Math.min(rangeDays, Math.ceil((endDate - rangeStart) / (1000 * 60 * 60 * 24)));
327
+
328
+ // Ensure minimum visible width (2 days)
329
+ const adjustedEndDay = Math.max(startDay + 2, endDay);
330
+ const leftPct = (startDay / rangeDays) * 100;
331
+ const widthPct = Math.min(100 - leftPct, Math.max(4, ((adjustedEndDay - startDay) / rangeDays) * 100));
332
+
333
+ // Find first layer without overlap (greedy algorithm)
334
+ let layer = 0;
335
+ let foundSlot = false;
336
+ for (let i = 0; i < layers.length; i++) {
337
+ const hasOverlap = layers[i].some(range =>
338
+ !(adjustedEndDay <= range.start || startDay >= range.end)
339
+ );
340
+ if (!hasOverlap) {
341
+ layer = i;
342
+ foundSlot = true;
343
+ break;
344
+ }
345
+ }
346
+ if (!foundSlot) {
347
+ layer = layers.length;
348
+ }
349
+
350
+ // Add to layer
351
+ if (!layers[layer]) layers[layer] = [];
352
+ layers[layer].push({ start: startDay, end: adjustedEndDay });
353
+
354
+ return { ...plan, layer, leftPct, widthPct, startDay, endDay: adjustedEndDay };
355
+ });
356
+ }
357
+
358
+ /**
359
+ * Generate timeline section HTML with Layered Gantt
360
+ * @param {Array} plans - Array of plan metadata objects
361
+ * @returns {string} - Timeline section HTML
362
+ */
363
+ function generateTimelineSection(plans) {
364
+ if (!plans || plans.length === 0) return '';
365
+
366
+ const stats = generateTimelineStats(plans);
367
+
368
+ // Calculate date range (last 3 weeks to now + 1 week)
369
+ const now = new Date();
370
+ const rangeStart = new Date(now.getTime() - 21 * 24 * 60 * 60 * 1000);
371
+ const rangeEnd = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
372
+ const rangeDays = Math.ceil((rangeEnd - rangeStart) / (1000 * 60 * 60 * 24));
373
+
374
+ // Generate date axis labels (7 markers)
375
+ const axisLabels = [];
376
+ for (let i = 0; i < 7; i++) {
377
+ const date = new Date(rangeStart.getTime() + (i * rangeDays / 6) * 24 * 60 * 60 * 1000);
378
+ const isToday = date.toDateString() === now.toDateString();
379
+ axisLabels.push({
380
+ label: date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
381
+ isToday
382
+ });
383
+ }
384
+
385
+ const axisHtml = axisLabels.map(a =>
386
+ `<span class="gantt-axis-label${a.isToday ? ' today' : ''}">${a.label}</span>`
387
+ ).join('');
388
+
389
+ // Calculate today marker position
390
+ const todayPct = ((now - rangeStart) / (rangeEnd - rangeStart)) * 100;
391
+
392
+ // Auto-stack plans into layers
393
+ const layeredPlans = assignLayers(plans, rangeStart, rangeEnd);
394
+ const maxLayer = layeredPlans.length > 0 ? Math.max(...layeredPlans.map(p => p.layer), 0) : 0;
395
+ // Compact layout: 22px per layer (bar 18px + 4px gap), no max cap
396
+ const trackHeight = Math.max(60, (maxLayer + 1) * 22 + 12);
397
+
398
+ // Generate gantt bars
399
+ const barsHtml = layeredPlans.map(plan => {
400
+ const statusClass = plan.status === 'completed' ? 'completed'
401
+ : plan.status === 'in-progress' ? 'in-progress' : 'pending';
402
+ const top = plan.layer * 22 + 6;
403
+ const statusIcon = plan.status === 'completed' ? '✓' : plan.status === 'in-progress' ? '◐' : '○';
404
+
405
+ return `
406
+ <a href="/view?file=${encodeURIComponent(plan.path)}" class="gantt-bar ${statusClass}"
407
+ style="left: ${plan.leftPct.toFixed(1)}%; width: ${plan.widthPct.toFixed(1)}%; top: ${top}px;"
408
+ data-id="${escapeHtml(plan.id)}">
409
+ <span class="gantt-bar-label">${escapeHtml(plan.name)}</span>
410
+ <span class="gantt-bar-status">${statusIcon}</span>
411
+ <div class="gantt-tooltip">
412
+ <div class="gantt-tooltip-title">${escapeHtml(plan.name)}</div>
413
+ <div class="gantt-tooltip-meta">
414
+ <span>${plan.durationFormatted || 'Today'}</span>
415
+ <span>${plan.phases.completed}/${plan.phases.total} phases</span>
416
+ ${plan.totalEffortFormatted ? `<span>${plan.totalEffortFormatted}</span>` : ''}
417
+ </div>
418
+ </div>
419
+ </a>
420
+ `;
421
+ }).join('');
422
+
423
+ // Summary counts
424
+ const completedCount = plans.filter(p => p.status === 'completed').length;
425
+ const activeCount = plans.filter(p => p.status === 'in-progress').length;
426
+ const pendingCount = plans.filter(p => p.status === 'pending').length;
427
+
428
+ return `
429
+ <section class="timeline-section" aria-label="Project timeline">
430
+ <div class="timeline-header">
431
+ <h2 class="timeline-title">Timeline</h2>
432
+ <div class="timeline-stats">
433
+ <div class="timeline-stat">
434
+ <span>Avg:</span>
435
+ <strong>${stats.avgDurationDays}d</strong>
436
+ </div>
437
+ <div class="timeline-stat">
438
+ <span>Effort:</span>
439
+ <strong>${stats.totalEffortHours.toFixed(0)}h</strong>
440
+ </div>
441
+ </div>
442
+ </div>
443
+ <div class="gantt-container">
444
+ <div class="gantt-axis">${axisHtml}</div>
445
+ <div class="gantt-track" style="height: ${trackHeight}px;">
446
+ <div class="gantt-today-marker" style="left: ${todayPct.toFixed(1)}%;"></div>
447
+ ${barsHtml}
448
+ </div>
449
+ </div>
450
+ <div class="timeline-summary">
451
+ <div class="timeline-summary-item">
452
+ <span class="timeline-summary-dot completed"></span>
453
+ <span>${completedCount} done</span>
454
+ </div>
455
+ <div class="timeline-summary-item">
456
+ <span class="timeline-summary-dot in-progress"></span>
457
+ <span>${activeCount} active</span>
458
+ </div>
459
+ <div class="timeline-summary-item">
460
+ <span class="timeline-summary-dot pending"></span>
461
+ <span>${pendingCount} pending</span>
462
+ </div>
463
+ </div>
464
+ </section>
465
+ `;
466
+ }
467
+
468
+ /**
469
+ * Generate empty state HTML with animated icon
470
+ * @returns {string} - Empty state HTML
471
+ */
472
+ function generateEmptyState() {
473
+ return `
474
+ <div class="empty-state" hidden>
475
+ <div class="empty-icon" aria-hidden="true">
476
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
477
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
478
+ <polyline points="14 2 14 8 20 8"/>
479
+ <line x1="16" y1="13" x2="8" y2="13"/>
480
+ <line x1="16" y1="17" x2="8" y2="17"/>
481
+ <polyline points="10 9 9 9 8 9"/>
482
+ </svg>
483
+ </div>
484
+ <h2>No plans found</h2>
485
+ <p>Create a plan directory with a plan.md file to get started with tracking your projects.</p>
486
+ </div>
487
+ `;
488
+ }
489
+
490
+ /**
491
+ * Generate plans grid HTML
492
+ * @param {Array} plans - Array of plan metadata objects
493
+ * @returns {string} - Grid HTML
494
+ */
495
+ function generatePlansGrid(plans) {
496
+ if (!plans || plans.length === 0) {
497
+ return '';
498
+ }
499
+
500
+ return plans.map(generatePlanCard).join('\n');
501
+ }
502
+
503
+ /**
504
+ * Status column configuration for kanban board
505
+ */
506
+ const STATUS_COLUMNS = [
507
+ { id: 'pending', label: 'Pending', color: 'pending' },
508
+ { id: 'in-progress', label: 'In Progress', color: 'in-progress' },
509
+ { id: 'in-review', label: 'In Review', color: 'in-review' },
510
+ { id: 'completed', label: 'Done', color: 'completed' },
511
+ { id: 'cancelled', label: 'Cancelled', color: 'cancelled' }
512
+ ];
513
+
514
+ /**
515
+ * Generate kanban card HTML for a single plan (enhanced with details)
516
+ * @param {Object} plan - Plan metadata
517
+ * @returns {string} - Card HTML
518
+ */
519
+ function generateKanbanCard(plan) {
520
+ const progressPct = Math.round(plan.progress || 0);
521
+ const dateStr = formatRelativeTime(plan.lastModified);
522
+
523
+ // Priority badge
524
+ let priorityHtml = '';
525
+ if (plan.priority) {
526
+ const priorityColorClass = getPriorityColorClass(plan.priority);
527
+ if (priorityColorClass) {
528
+ priorityHtml = `<span class="kanban-card-priority ${priorityColorClass}">${escapeHtml(plan.priority)}</span>`;
529
+ }
530
+ }
531
+
532
+ // Description (truncated)
533
+ let descriptionHtml = '';
534
+ if (plan.description) {
535
+ descriptionHtml = `<p class="kanban-card-description">${escapeHtml(truncate(plan.description, 80))}</p>`;
536
+ }
537
+
538
+ // Tags (max 3 visible)
539
+ let tagsHtml = '';
540
+ if (plan.tags && Array.isArray(plan.tags) && plan.tags.length > 0) {
541
+ const visibleTags = plan.tags.slice(0, 3);
542
+ const hiddenCount = plan.tags.length - 3;
543
+ tagsHtml = '<div class="kanban-card-tags">';
544
+ tagsHtml += visibleTags.map(tag => `<span class="kanban-card-tag">${escapeHtml(tag)}</span>`).join('');
545
+ if (hiddenCount > 0) {
546
+ tagsHtml += `<span class="kanban-card-tag tag-more">+${hiddenCount}</span>`;
547
+ }
548
+ tagsHtml += '</div>';
549
+ }
550
+
551
+ // Footer with effort and phases
552
+ let footerHtml = '';
553
+ const effortHtml = plan.totalEffortFormatted
554
+ ? `<span class="kanban-card-effort"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>${escapeHtml(plan.totalEffortFormatted)}</span>`
555
+ : '';
556
+ const phasesHtml = plan.phases && plan.phases.total
557
+ ? `<span class="kanban-card-phases">${plan.phases.total} phases</span>`
558
+ : '';
559
+ if (effortHtml || phasesHtml) {
560
+ footerHtml = `<div class="kanban-card-footer">${effortHtml}${phasesHtml}</div>`;
561
+ }
562
+
563
+ return `
564
+ <a href="/view?file=${encodeURIComponent(plan.path)}" class="kanban-card" data-id="${escapeHtml(plan.id)}">
565
+ <div class="kanban-card-header">
566
+ <h4 class="kanban-card-title">${escapeHtml(plan.name)}</h4>
567
+ ${priorityHtml}
568
+ </div>
569
+ ${descriptionHtml}
570
+ <div class="kanban-card-meta">
571
+ <div class="kanban-card-progress">
572
+ <div class="kanban-card-progress-bar">
573
+ <div class="kanban-card-progress-fill" style="width: ${progressPct}%"></div>
574
+ </div>
575
+ <span>${progressPct}%</span>
576
+ </div>
577
+ <span class="kanban-card-date">${dateStr}</span>
578
+ </div>
579
+ ${tagsHtml}
580
+ ${footerHtml}
581
+ </a>
582
+ `;
583
+ }
584
+
585
+ /**
586
+ * Generate kanban board columns HTML
587
+ * @param {Array} plans - Array of plan metadata objects
588
+ * @returns {string} - Kanban columns HTML
589
+ */
590
+ function generateKanbanColumns(plans) {
591
+ if (!plans || plans.length === 0) {
592
+ // Return empty columns structure
593
+ return STATUS_COLUMNS.map(col => `
594
+ <div class="kanban-column" data-status="${col.id}">
595
+ <div class="kanban-column-header">
596
+ <div class="kanban-column-title">
597
+ <span class="kanban-status-dot ${col.color}"></span>
598
+ <span>${col.label}</span>
599
+ </div>
600
+ <span class="kanban-column-count">0</span>
601
+ </div>
602
+ <div class="kanban-cards">
603
+ <div class="kanban-empty">
604
+ <svg class="kanban-empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
605
+ <rect x="3" y="3" width="18" height="18" rx="2"/>
606
+ <path d="M9 9h6M9 13h6M9 17h4"/>
607
+ </svg>
608
+ <span>No plans</span>
609
+ </div>
610
+ </div>
611
+ </div>
612
+ `).join('');
613
+ }
614
+
615
+ // Group plans by status
616
+ const grouped = {};
617
+ STATUS_COLUMNS.forEach(col => {
618
+ grouped[col.id] = [];
619
+ });
620
+
621
+ plans.forEach(plan => {
622
+ const status = plan.status || 'pending';
623
+ if (grouped[status]) {
624
+ grouped[status].push(plan);
625
+ } else {
626
+ grouped['pending'].push(plan);
627
+ }
628
+ });
629
+
630
+ // Generate column HTML
631
+ return STATUS_COLUMNS.map(col => {
632
+ const columnPlans = grouped[col.id];
633
+ const cardsHtml = columnPlans.length > 0
634
+ ? columnPlans.map(generateKanbanCard).join('')
635
+ : `<div class="kanban-empty">
636
+ <svg class="kanban-empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
637
+ <rect x="3" y="3" width="18" height="18" rx="2"/>
638
+ <path d="M9 9h6M9 13h6M9 17h4"/>
639
+ </svg>
640
+ <span>No plans</span>
641
+ </div>`;
642
+
643
+ return `
644
+ <div class="kanban-column" data-status="${col.id}">
645
+ <div class="kanban-column-header">
646
+ <div class="kanban-column-title">
647
+ <span class="kanban-status-dot ${col.color}"></span>
648
+ <span>${col.label}</span>
649
+ </div>
650
+ <span class="kanban-column-count">${columnPlans.length}</span>
651
+ </div>
652
+ <div class="kanban-cards">
653
+ ${cardsHtml}
654
+ </div>
655
+ </div>
656
+ `;
657
+ }).join('');
658
+ }
659
+
660
+ /**
661
+ * Calculate statistics from plans array
662
+ * @param {Array} plans - Array of plan metadata objects
663
+ * @returns {Object} - Statistics object
664
+ */
665
+ function calculateStats(plans) {
666
+ const stats = {
667
+ total: plans.length,
668
+ completed: 0,
669
+ inProgress: 0,
670
+ pending: 0
671
+ };
672
+
673
+ plans.forEach(plan => {
674
+ const status = (plan.status || 'pending').replace(/\s+/g, '-');
675
+ if (status === 'completed' || status === 'complete') {
676
+ stats.completed++;
677
+ } else if (status === 'in-progress') {
678
+ stats.inProgress++;
679
+ } else {
680
+ stats.pending++;
681
+ }
682
+ });
683
+
684
+ return stats;
685
+ }
686
+
687
+ /**
688
+ * Render complete dashboard HTML
689
+ * @param {Array} plans - Array of plan metadata objects
690
+ * @param {Object} options - Render options
691
+ * @param {string} options.assetsDir - Assets directory path
692
+ * @param {string} options.plansDir - Plans directory path
693
+ * @returns {string} - Complete HTML page
694
+ */
695
+ function renderDashboard(plans, options = {}) {
696
+ const { assetsDir } = options;
697
+
698
+ // Load template
699
+ const templatePath = path.join(assetsDir, 'dashboard-template.html');
700
+ let template;
701
+
702
+ try {
703
+ template = fs.readFileSync(templatePath, 'utf8');
704
+ } catch (err) {
705
+ // Fallback inline template if file not found
706
+ template = getInlineTemplate();
707
+ }
708
+
709
+ // Calculate statistics
710
+ const stats = calculateStats(plans);
711
+
712
+ // Generate cards
713
+ const plansGrid = generatePlansGrid(plans);
714
+ const planCount = plans.length;
715
+
716
+ // Generate timeline section
717
+ const timelineSection = generateTimelineSection(plans);
718
+
719
+ // Generate kanban columns
720
+ const kanbanColumns = generateKanbanColumns(plans);
721
+
722
+ // Generate JSON for client-side filtering (include rich metadata)
723
+ const plansJson = JSON.stringify(plans.map(p => ({
724
+ id: p.id,
725
+ name: p.name,
726
+ status: p.status,
727
+ progress: p.progress,
728
+ lastModified: p.lastModified,
729
+ phasesTotal: p.phases.total,
730
+ path: p.path, // Required for kanban card links
731
+ // Rich metadata
732
+ createdDate: p.createdDate,
733
+ completedDate: p.completedDate,
734
+ durationDays: p.durationDays,
735
+ durationFormatted: p.durationFormatted,
736
+ totalEffortHours: p.totalEffortHours,
737
+ totalEffortFormatted: p.totalEffortFormatted,
738
+ priority: p.priority,
739
+ issue: p.issue,
740
+ branch: p.branch,
741
+ // New frontmatter fields
742
+ description: p.description,
743
+ tags: p.tags || [],
744
+ assignee: p.assignee
745
+ })));
746
+
747
+ // Replace placeholders
748
+ template = template
749
+ .replace(/\{\{plans-grid\}\}/g, plansGrid)
750
+ .replace(/\{\{kanban-columns\}\}/g, kanbanColumns)
751
+ .replace(/\{\{plan-count\}\}/g, String(planCount))
752
+ .replace(/\{\{plans-json\}\}/g, plansJson)
753
+ .replace(/\{\{empty-state\}\}/g, generateEmptyState())
754
+ .replace(/\{\{timeline-section\}\}/g, timelineSection)
755
+ .replace(/\{\{has-plans\}\}/g, plans.length > 0 ? 'plans-loaded' : '')
756
+ .replace(/\{\{stat-total\}\}/g, String(stats.total))
757
+ .replace(/\{\{stat-completed\}\}/g, String(stats.completed))
758
+ .replace(/\{\{stat-in-progress\}\}/g, String(stats.inProgress))
759
+ .replace(/\{\{stat-pending\}\}/g, String(stats.pending));
760
+
761
+ return template;
762
+ }
763
+
764
+ /**
765
+ * Get inline fallback template
766
+ * @returns {string} - Inline HTML template
767
+ */
768
+ function getInlineTemplate() {
769
+ return `<!DOCTYPE html>
770
+ <html lang="en" data-theme="light">
771
+ <head>
772
+ <meta charset="UTF-8">
773
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
774
+ <title>Plans Dashboard</title>
775
+ <link rel="icon" type="image/png" href="/assets/favicon.png">
776
+ <link rel="stylesheet" href="/assets/novel-theme.css">
777
+ <link rel="stylesheet" href="/assets/dashboard.css">
778
+ </head>
779
+ <body class="dashboard-view {{has-plans}}">
780
+ <header class="dashboard-header">
781
+ <div class="header-left">
782
+ <h1>Plans Dashboard</h1>
783
+ </div>
784
+ <div class="header-right">
785
+ <button id="theme-toggle" class="icon-btn" aria-label="Toggle theme">
786
+ <svg class="sun-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
787
+ <circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
788
+ </svg>
789
+ <svg class="moon-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
790
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
791
+ </svg>
792
+ </button>
793
+ </div>
794
+ </header>
795
+
796
+ <main role="main" aria-label="Plans Dashboard">
797
+ <section class="stats-hero" aria-label="Plan statistics">
798
+ <div class="stat-card total">
799
+ <div class="stat-icon">📋</div>
800
+ <div class="stat-value">{{stat-total}}</div>
801
+ <div class="stat-label">Total Plans</div>
802
+ </div>
803
+ <div class="stat-card completed">
804
+ <div class="stat-icon">✅</div>
805
+ <div class="stat-value">{{stat-completed}}</div>
806
+ <div class="stat-label">Completed</div>
807
+ </div>
808
+ <div class="stat-card in-progress">
809
+ <div class="stat-icon">🔄</div>
810
+ <div class="stat-value">{{stat-in-progress}}</div>
811
+ <div class="stat-label">In Progress</div>
812
+ </div>
813
+ <div class="stat-card pending">
814
+ <div class="stat-icon">⏳</div>
815
+ <div class="stat-value">{{stat-pending}}</div>
816
+ <div class="stat-label">Pending</div>
817
+ </div>
818
+ </section>
819
+
820
+ <div class="dashboard-controls">
821
+ <div class="search-box">
822
+ <svg class="search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
823
+ <circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
824
+ </svg>
825
+ <input type="search" id="plan-search" placeholder="Search plans..." aria-label="Search plans">
826
+ </div>
827
+
828
+ <select id="sort-select" aria-label="Sort plans by">
829
+ <option value="date-desc">Newest First</option>
830
+ <option value="date-asc">Oldest First</option>
831
+ <option value="name-asc">Name A-Z</option>
832
+ <option value="name-desc">Name Z-A</option>
833
+ <option value="progress-desc">Most Progress</option>
834
+ <option value="progress-asc">Least Progress</option>
835
+ </select>
836
+
837
+ <div class="filter-pills" role="group" aria-label="Filter by status">
838
+ <button class="filter-pill active" data-filter="all" aria-pressed="true">All</button>
839
+ <button class="filter-pill" data-filter="completed" aria-pressed="false">Completed</button>
840
+ <button class="filter-pill" data-filter="in-progress" aria-pressed="false">In Progress</button>
841
+ <button class="filter-pill" data-filter="pending" aria-pressed="false">Pending</button>
842
+ </div>
843
+
844
+ <div role="status" aria-live="polite" class="result-count">
845
+ Showing <strong>{{plan-count}}</strong> plans
846
+ </div>
847
+ </div>
848
+
849
+ <section class="plans-grid" aria-label="Plans list">
850
+ {{plans-grid}}
851
+ </section>
852
+
853
+ {{empty-state}}
854
+ </main>
855
+
856
+ <script>window.__plans = {{plans-json}};</script>
857
+ <script src="/assets/dashboard.js"></script>
858
+ </body>
859
+ </html>`;
860
+ }
861
+
862
+ module.exports = {
863
+ renderDashboard,
864
+ generatePlanCard,
865
+ generateCardMeta,
866
+ generateTagsPills,
867
+ generateProgressRing,
868
+ generateProgressBar,
869
+ generateStatusCounts,
870
+ generateStatusBadge,
871
+ generateTimelineSection,
872
+ generateEmptyState,
873
+ generatePlansGrid,
874
+ generateKanbanColumns,
875
+ generateKanbanCard,
876
+ calculateStats,
877
+ escapeHtml,
878
+ truncate,
879
+ formatDate,
880
+ formatRelativeTime,
881
+ getStatusLabel,
882
+ getPriorityColorClass,
883
+ STATUS_COLUMNS
884
+ };