@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.
- package/.github/agents/schemas/base-output.schema.json +88 -0
- package/.github/agents/schemas/brainstorm-output.schema.json +88 -0
- package/.github/agents/schemas/scout-output.schema.json +60 -0
- package/.github/agents/scripts/fetch-copilot-tools.js +245 -0
- package/.github/agents/scripts/lib/parse-agent-file.js +275 -0
- package/.github/agents/scripts/package-lock.json +78 -0
- package/.github/agents/scripts/package.json +22 -0
- package/.github/agents/scripts/schemas/agent-frontmatter.schema.json +83 -0
- package/.github/agents/scripts/validate-agent-all.js +157 -0
- package/.github/agents/scripts/validate-agent-frontmatter.js +96 -0
- package/.github/agents/scripts/validate-agent-handoffs.js +169 -0
- package/.github/agents/scripts/validate-agent-output.js +157 -0
- package/.github/agents/scripts/validate-agent-tools.js +278 -0
- package/.github/skills/.env.example +100 -0
- package/.github/skills/.install-state.json +23 -0
- package/.github/skills/README.md +149 -0
- package/.github/skills/ai-multimodal/.env.example +204 -0
- package/.github/skills/ai-multimodal/scripts/.coverage +0 -0
- package/.github/skills/ai-multimodal/scripts/check_setup.py +305 -0
- package/.github/skills/ai-multimodal/scripts/document_converter.py +395 -0
- package/.github/skills/ai-multimodal/scripts/gemini_batch_process.py +1184 -0
- package/.github/skills/ai-multimodal/scripts/media_optimizer.py +506 -0
- package/.github/skills/ai-multimodal/scripts/requirements.txt +26 -0
- package/.github/skills/better-auth/scripts/.coverage +0 -0
- package/.github/skills/better-auth/scripts/better_auth_init.py +521 -0
- package/.github/skills/better-auth/scripts/requirements.txt +15 -0
- package/.github/skills/chrome-devtools/scripts/README.md +272 -0
- package/.github/skills/chrome-devtools/scripts/__tests__/selector.test.js +210 -0
- package/.github/skills/chrome-devtools/scripts/aria-snapshot.js +362 -0
- package/.github/skills/chrome-devtools/scripts/click.js +83 -0
- package/.github/skills/chrome-devtools/scripts/console.js +79 -0
- package/.github/skills/chrome-devtools/scripts/evaluate.js +53 -0
- package/.github/skills/chrome-devtools/scripts/fill.js +76 -0
- package/.github/skills/chrome-devtools/scripts/inject-auth.js +229 -0
- package/.github/skills/chrome-devtools/scripts/install-deps.sh +181 -0
- package/.github/skills/chrome-devtools/scripts/install.sh +83 -0
- package/.github/skills/chrome-devtools/scripts/lib/browser.js +318 -0
- package/.github/skills/chrome-devtools/scripts/lib/selector.js +178 -0
- package/.github/skills/chrome-devtools/scripts/navigate.js +54 -0
- package/.github/skills/chrome-devtools/scripts/network.js +106 -0
- package/.github/skills/chrome-devtools/scripts/package-lock.json +1589 -0
- package/.github/skills/chrome-devtools/scripts/package.json +16 -0
- package/.github/skills/chrome-devtools/scripts/performance.js +149 -0
- package/.github/skills/chrome-devtools/scripts/screenshot.js +198 -0
- package/.github/skills/chrome-devtools/scripts/select-ref.js +131 -0
- package/.github/skills/chrome-devtools/scripts/snapshot.js +135 -0
- package/.github/skills/common/README.md +120 -0
- package/.github/skills/common/api_key_helper.py +411 -0
- package/.github/skills/common/api_key_rotator.py +248 -0
- package/.github/skills/databases/scripts/.coverage +0 -0
- package/.github/skills/databases/scripts/db_backup.py +502 -0
- package/.github/skills/databases/scripts/db_migrate.py +425 -0
- package/.github/skills/databases/scripts/db_performance_check.py +456 -0
- package/.github/skills/databases/scripts/requirements.txt +20 -0
- package/.github/skills/debugging/scripts/find-polluter.sh +63 -0
- package/.github/skills/devops/.env.example +76 -0
- package/.github/skills/devops/scripts/cloudflare_deploy.py +269 -0
- package/.github/skills/devops/scripts/docker_optimize.py +331 -0
- package/.github/skills/devops/scripts/requirements.txt +20 -0
- package/.github/skills/docs-seeker/.env.example +15 -0
- package/.github/skills/docs-seeker/package.json +25 -0
- package/.github/skills/docs-seeker/scripts/analyze-llms-txt.js +211 -0
- package/.github/skills/docs-seeker/scripts/detect-topic.js +172 -0
- package/.github/skills/docs-seeker/scripts/fetch-docs.js +213 -0
- package/.github/skills/docs-seeker/scripts/utils/env-loader.js +94 -0
- package/.github/skills/document-skills/docx/LICENSE.txt +30 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/mce/mc.xsd +75 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/.github/skills/document-skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/.github/skills/document-skills/docx/ooxml/scripts/pack.py +159 -0
- package/.github/skills/document-skills/docx/ooxml/scripts/unpack.py +29 -0
- package/.github/skills/document-skills/docx/ooxml/scripts/validate.py +69 -0
- package/.github/skills/document-skills/docx/ooxml/scripts/validation/__init__.py +15 -0
- package/.github/skills/document-skills/docx/ooxml/scripts/validation/base.py +951 -0
- package/.github/skills/document-skills/docx/ooxml/scripts/validation/docx.py +274 -0
- package/.github/skills/document-skills/docx/ooxml/scripts/validation/pptx.py +315 -0
- package/.github/skills/document-skills/docx/ooxml/scripts/validation/redlining.py +279 -0
- package/.github/skills/document-skills/docx/scripts/__init__.py +1 -0
- package/.github/skills/document-skills/docx/scripts/document.py +1276 -0
- package/.github/skills/document-skills/docx/scripts/templates/comments.xml +3 -0
- package/.github/skills/document-skills/docx/scripts/templates/commentsExtended.xml +3 -0
- package/.github/skills/document-skills/docx/scripts/templates/commentsExtensible.xml +3 -0
- package/.github/skills/document-skills/docx/scripts/templates/commentsIds.xml +3 -0
- package/.github/skills/document-skills/docx/scripts/templates/people.xml +3 -0
- package/.github/skills/document-skills/docx/scripts/utilities.py +374 -0
- package/.github/skills/document-skills/pdf/LICENSE.txt +30 -0
- package/.github/skills/document-skills/pdf/scripts/check_bounding_boxes.py +70 -0
- package/.github/skills/document-skills/pdf/scripts/check_bounding_boxes_test.py +226 -0
- package/.github/skills/document-skills/pdf/scripts/check_fillable_fields.py +12 -0
- package/.github/skills/document-skills/pdf/scripts/convert_pdf_to_images.py +35 -0
- package/.github/skills/document-skills/pdf/scripts/create_validation_image.py +41 -0
- package/.github/skills/document-skills/pdf/scripts/extract_form_field_info.py +152 -0
- package/.github/skills/document-skills/pdf/scripts/fill_fillable_fields.py +114 -0
- package/.github/skills/document-skills/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
- package/.github/skills/document-skills/pptx/LICENSE.txt +30 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/mce/mc.xsd +75 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/.github/skills/document-skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/.github/skills/document-skills/pptx/ooxml/scripts/pack.py +159 -0
- package/.github/skills/document-skills/pptx/ooxml/scripts/unpack.py +29 -0
- package/.github/skills/document-skills/pptx/ooxml/scripts/validate.py +69 -0
- package/.github/skills/document-skills/pptx/ooxml/scripts/validation/__init__.py +15 -0
- package/.github/skills/document-skills/pptx/ooxml/scripts/validation/base.py +951 -0
- package/.github/skills/document-skills/pptx/ooxml/scripts/validation/docx.py +274 -0
- package/.github/skills/document-skills/pptx/ooxml/scripts/validation/pptx.py +315 -0
- package/.github/skills/document-skills/pptx/ooxml/scripts/validation/redlining.py +279 -0
- package/.github/skills/document-skills/pptx/scripts/html2pptx.js +979 -0
- package/.github/skills/document-skills/pptx/scripts/inventory.py +1020 -0
- package/.github/skills/document-skills/pptx/scripts/rearrange.py +231 -0
- package/.github/skills/document-skills/pptx/scripts/replace.py +385 -0
- package/.github/skills/document-skills/pptx/scripts/thumbnail.py +450 -0
- package/.github/skills/document-skills/xlsx/LICENSE.txt +30 -0
- package/.github/skills/document-skills/xlsx/recalc.py +190 -0
- package/.github/skills/install.ps1 +1220 -0
- package/.github/skills/install.sh +1032 -0
- package/.github/skills/markdown-novel-viewer/assets/directory-browser.css +215 -0
- package/.github/skills/markdown-novel-viewer/assets/favicon.png +0 -0
- package/.github/skills/markdown-novel-viewer/assets/novel-theme.css +818 -0
- package/.github/skills/markdown-novel-viewer/assets/reader.js +262 -0
- package/.github/skills/markdown-novel-viewer/assets/template.html +80 -0
- package/.github/skills/markdown-novel-viewer/package-lock.json +146 -0
- package/.github/skills/markdown-novel-viewer/package.json +15 -0
- package/.github/skills/markdown-novel-viewer/scripts/lib/http-server.cjs +434 -0
- package/.github/skills/markdown-novel-viewer/scripts/lib/markdown-renderer.cjs +272 -0
- package/.github/skills/markdown-novel-viewer/scripts/lib/plan-navigator.cjs +509 -0
- package/.github/skills/markdown-novel-viewer/scripts/lib/port-finder.cjs +48 -0
- package/.github/skills/markdown-novel-viewer/scripts/lib/process-mgr.cjs +150 -0
- package/.github/skills/markdown-novel-viewer/scripts/server.cjs +411 -0
- package/.github/skills/mcp-builder/LICENSE.txt +202 -0
- package/.github/skills/mcp-builder/scripts/connections.py +151 -0
- package/.github/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/.github/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/.github/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/.github/skills/mcp-management/README.md +219 -0
- package/.github/skills/mcp-management/assets/tools.json +3146 -0
- package/.github/skills/mcp-management/package-lock.json +6 -0
- package/.github/skills/mcp-management/scripts/.env.example +10 -0
- package/.github/skills/mcp-management/scripts/cli.ts +195 -0
- package/.github/skills/mcp-management/scripts/dist/analyze-tools.js +70 -0
- package/.github/skills/mcp-management/scripts/dist/cli.js +160 -0
- package/.github/skills/mcp-management/scripts/dist/mcp-client.js +183 -0
- package/.github/skills/mcp-management/scripts/mcp-client.ts +230 -0
- package/.github/skills/mcp-management/scripts/package.json +20 -0
- package/.github/skills/media-processing/scripts/README.md +111 -0
- package/.github/skills/media-processing/scripts/batch-remove-background.sh +124 -0
- package/.github/skills/media-processing/scripts/batch_resize.py +342 -0
- package/.github/skills/media-processing/scripts/media_convert.py +311 -0
- package/.github/skills/media-processing/scripts/remove-background.sh +96 -0
- package/.github/skills/media-processing/scripts/remove-bg-node.js +158 -0
- package/.github/skills/media-processing/scripts/requirements.txt +24 -0
- package/.github/skills/media-processing/scripts/video_optimize.py +414 -0
- package/.github/skills/payment-integration/README.md +185 -0
- package/.github/skills/payment-integration/scripts/.env.example +20 -0
- package/.github/skills/payment-integration/scripts/checkout-helper.js +244 -0
- package/.github/skills/payment-integration/scripts/package.json +17 -0
- package/.github/skills/payment-integration/scripts/polar-webhook-verify.js +202 -0
- package/.github/skills/payment-integration/scripts/sepay-webhook-verify.js +193 -0
- package/.github/skills/payment-integration/scripts/test-scripts.js +237 -0
- package/.github/skills/plans-kanban/assets/dashboard-template.html +119 -0
- package/.github/skills/plans-kanban/assets/dashboard.css +1594 -0
- package/.github/skills/plans-kanban/assets/dashboard.js +596 -0
- package/.github/skills/plans-kanban/assets/favicon.png +0 -0
- package/.github/skills/plans-kanban/package-lock.json +123 -0
- package/.github/skills/plans-kanban/package.json +13 -0
- package/.github/skills/plans-kanban/scripts/lib/dashboard-renderer.cjs +884 -0
- package/.github/skills/plans-kanban/scripts/lib/http-server.cjs +310 -0
- package/.github/skills/plans-kanban/scripts/lib/plan-metadata-extractor.cjs +489 -0
- package/.github/skills/plans-kanban/scripts/lib/plan-parser.cjs +175 -0
- package/.github/skills/plans-kanban/scripts/lib/plan-scanner.cjs +272 -0
- package/.github/skills/plans-kanban/scripts/lib/port-finder.cjs +48 -0
- package/.github/skills/plans-kanban/scripts/lib/process-mgr.cjs +128 -0
- package/.github/skills/plans-kanban/scripts/server.cjs +260 -0
- package/.github/skills/repomix/scripts/.coverage +0 -0
- package/.github/skills/repomix/scripts/README.md +179 -0
- package/.github/skills/repomix/scripts/repomix_batch.py +455 -0
- package/.github/skills/repomix/scripts/repos.example.json +15 -0
- package/.github/skills/repomix/scripts/requirements.txt +15 -0
- package/.github/skills/scout-validation/scripts/lib/broad-pattern-detector.cjs +124 -0
- package/.github/skills/scout-validation/scripts/lib/path-checker.cjs +66 -0
- package/.github/skills/scout-validation/scripts/lib/schema-validator.cjs +45 -0
- package/.github/skills/scout-validation/scripts/package.json +11 -0
- package/.github/skills/scout-validation/scripts/validate-scout-output.cjs +219 -0
- package/.github/skills/scout-validation/test/broad-pattern-output.json +18 -0
- package/.github/skills/scout-validation/test/invalid-path-output.json +18 -0
- package/.github/skills/scout-validation/test/valid-scout-output.json +26 -0
- package/.github/skills/sequential-thinking/.env.example +8 -0
- package/.github/skills/sequential-thinking/README.md +183 -0
- package/.github/skills/sequential-thinking/package.json +31 -0
- package/.github/skills/sequential-thinking/scripts/format-thought.js +159 -0
- package/.github/skills/sequential-thinking/scripts/process-thought.js +236 -0
- package/.github/skills/shopify/README.md +66 -0
- package/.github/skills/shopify/scripts/.coverage +0 -0
- package/.github/skills/shopify/scripts/requirements.txt +19 -0
- package/.github/skills/shopify/scripts/shopify_init.py +423 -0
- package/.github/skills/skill-creator/LICENSE.txt +202 -0
- package/.github/skills/skill-creator/scripts/init_skill.py +303 -0
- package/.github/skills/skill-creator/scripts/package_skill.py +110 -0
- package/.github/skills/skill-creator/scripts/quick_validate.py +65 -0
- package/.github/skills/ui-styling/LICENSE.txt +202 -0
- package/.github/skills/ui-styling/canvas-fonts/ArsenalSC-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/BigShoulders-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/BigShoulders-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/BigShoulders-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Boldonse-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/Boldonse-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/CrimsonPro-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/DMMono-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/DMMono-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/EricaOne-OFL.txt +94 -0
- package/.github/skills/ui-styling/canvas-fonts/EricaOne-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/GeistMono-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/GeistMono-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/GeistMono-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Gloock-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/Gloock-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/InstrumentSans-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Italiana-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/Italiana-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Jura-Light.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Jura-Medium.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Jura-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Lora-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Lora-BoldItalic.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Lora-Italic.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Lora-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/Lora-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/NationalPark-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/NationalPark-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/NationalPark-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Outfit-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Outfit-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/Outfit-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/PixelifySans-Medium.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/PixelifySans-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/PoiretOne-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/PoiretOne-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/RedHatMono-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/RedHatMono-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/RedHatMono-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Silkscreen-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/Silkscreen-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/SmoochSans-Medium.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/SmoochSans-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/Tektur-Medium.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/Tektur-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/Tektur-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/WorkSans-Bold.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/WorkSans-Italic.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/WorkSans-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/WorkSans-Regular.ttf +0 -0
- package/.github/skills/ui-styling/canvas-fonts/YoungSerif-OFL.txt +93 -0
- package/.github/skills/ui-styling/canvas-fonts/YoungSerif-Regular.ttf +0 -0
- package/.github/skills/ui-styling/scripts/.coverage +0 -0
- package/.github/skills/ui-styling/scripts/requirements.txt +17 -0
- package/.github/skills/ui-styling/scripts/shadcn_add.py +292 -0
- package/.github/skills/ui-styling/scripts/tailwind_config_gen.py +456 -0
- package/.github/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.github/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.github/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.github/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.github/skills/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.github/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.github/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +51 -0
- package/.github/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.github/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.github/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.github/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.github/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.github/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.github/skills/ui-ux-pro-max/data/styles.csv +59 -0
- package/.github/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.github/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.github/skills/ui-ux-pro-max/scripts/core.py +236 -0
- package/.github/skills/ui-ux-pro-max/scripts/search.py +76 -0
- package/.github/skills/web-frameworks/scripts/.coverage +0 -0
- package/.github/skills/web-frameworks/scripts/__init__.py +0 -0
- package/.github/skills/web-frameworks/scripts/nextjs_init.py +547 -0
- package/.github/skills/web-frameworks/scripts/requirements.txt +16 -0
- package/.github/skills/web-frameworks/scripts/turborepo_migrate.py +394 -0
- package/dist/config/constants.d.ts +2 -0
- package/dist/config/constants.d.ts.map +1 -1
- package/dist/config/constants.js +4 -1
- package/dist/config/constants.js.map +1 -1
- package/dist/domains/github/github-client.d.ts +5 -0
- package/dist/domains/github/github-client.d.ts.map +1 -1
- package/dist/domains/github/github-client.js +44 -0
- package/dist/domains/github/github-client.js.map +1 -1
- package/dist/utils/downloader.d.ts +3 -1
- package/dist/utils/downloader.d.ts.map +1 -1
- package/dist/utils/downloader.js +48 -11
- package/dist/utils/downloader.js.map +1 -1
- 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, '<')
|
|
25
|
+
.replace(/>/g, '>')
|
|
26
|
+
.replace(/"/g, '"')
|
|
27
|
+
.replace(/'/g, ''');
|
|
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
|
+
};
|