javi-forge 0.1.0
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/.gitignore.template +105 -0
- package/.releaserc +44 -0
- package/README.md +45 -0
- package/ai-config/.skillignore +15 -0
- package/ai-config/AUTO_INVOKE.md +300 -0
- package/ai-config/agents/_TEMPLATE.md +93 -0
- package/ai-config/agents/business/api-designer.md +1657 -0
- package/ai-config/agents/business/business-analyst.md +1331 -0
- package/ai-config/agents/business/product-strategist.md +206 -0
- package/ai-config/agents/business/project-manager.md +178 -0
- package/ai-config/agents/business/requirements-analyst.md +1277 -0
- package/ai-config/agents/business/technical-writer.md +1679 -0
- package/ai-config/agents/creative/ux-designer.md +205 -0
- package/ai-config/agents/data-ai/ai-engineer.md +487 -0
- package/ai-config/agents/data-ai/analytics-engineer.md +953 -0
- package/ai-config/agents/data-ai/data-engineer.md +173 -0
- package/ai-config/agents/data-ai/data-scientist.md +672 -0
- package/ai-config/agents/data-ai/mlops-engineer.md +814 -0
- package/ai-config/agents/data-ai/prompt-engineer.md +772 -0
- package/ai-config/agents/development/angular-expert.md +620 -0
- package/ai-config/agents/development/backend-architect.md +795 -0
- package/ai-config/agents/development/database-specialist.md +212 -0
- package/ai-config/agents/development/frontend-specialist.md +686 -0
- package/ai-config/agents/development/fullstack-engineer.md +668 -0
- package/ai-config/agents/development/golang-pro.md +338 -0
- package/ai-config/agents/development/java-enterprise.md +400 -0
- package/ai-config/agents/development/javascript-pro.md +422 -0
- package/ai-config/agents/development/nextjs-pro.md +474 -0
- package/ai-config/agents/development/python-pro.md +570 -0
- package/ai-config/agents/development/react-pro.md +487 -0
- package/ai-config/agents/development/rust-pro.md +246 -0
- package/ai-config/agents/development/spring-boot-4-expert.md +326 -0
- package/ai-config/agents/development/typescript-pro.md +336 -0
- package/ai-config/agents/development/vue-specialist.md +605 -0
- package/ai-config/agents/infrastructure/cloud-architect.md +472 -0
- package/ai-config/agents/infrastructure/deployment-manager.md +358 -0
- package/ai-config/agents/infrastructure/devops-engineer.md +455 -0
- package/ai-config/agents/infrastructure/incident-responder.md +519 -0
- package/ai-config/agents/infrastructure/kubernetes-expert.md +705 -0
- package/ai-config/agents/infrastructure/monitoring-specialist.md +674 -0
- package/ai-config/agents/infrastructure/performance-engineer.md +658 -0
- package/ai-config/agents/orchestrator.md +241 -0
- package/ai-config/agents/quality/accessibility-auditor.md +1204 -0
- package/ai-config/agents/quality/code-reviewer-compact.md +123 -0
- package/ai-config/agents/quality/code-reviewer.md +363 -0
- package/ai-config/agents/quality/dependency-manager.md +743 -0
- package/ai-config/agents/quality/e2e-test-specialist.md +1005 -0
- package/ai-config/agents/quality/performance-tester.md +1086 -0
- package/ai-config/agents/quality/security-auditor.md +133 -0
- package/ai-config/agents/quality/test-engineer.md +453 -0
- package/ai-config/agents/specialists/api-designer.md +87 -0
- package/ai-config/agents/specialists/backend-architect.md +73 -0
- package/ai-config/agents/specialists/code-reviewer.md +77 -0
- package/ai-config/agents/specialists/db-optimizer.md +75 -0
- package/ai-config/agents/specialists/devops-engineer.md +83 -0
- package/ai-config/agents/specialists/documentation-writer.md +78 -0
- package/ai-config/agents/specialists/frontend-developer.md +75 -0
- package/ai-config/agents/specialists/performance-analyst.md +82 -0
- package/ai-config/agents/specialists/refactor-specialist.md +74 -0
- package/ai-config/agents/specialists/security-auditor.md +74 -0
- package/ai-config/agents/specialists/test-engineer.md +81 -0
- package/ai-config/agents/specialists/ux-consultant.md +76 -0
- package/ai-config/agents/specialized/agent-generator.md +1190 -0
- package/ai-config/agents/specialized/blockchain-developer.md +149 -0
- package/ai-config/agents/specialized/code-migrator.md +892 -0
- package/ai-config/agents/specialized/context-manager.md +978 -0
- package/ai-config/agents/specialized/documentation-writer.md +1078 -0
- package/ai-config/agents/specialized/ecommerce-expert.md +1756 -0
- package/ai-config/agents/specialized/embedded-engineer.md +1714 -0
- package/ai-config/agents/specialized/error-detective.md +1034 -0
- package/ai-config/agents/specialized/fintech-specialist.md +1659 -0
- package/ai-config/agents/specialized/freelance-project-planner-v2.md +1988 -0
- package/ai-config/agents/specialized/freelance-project-planner-v3.md +2136 -0
- package/ai-config/agents/specialized/freelance-project-planner-v4.md +4503 -0
- package/ai-config/agents/specialized/freelance-project-planner.md +722 -0
- package/ai-config/agents/specialized/game-developer.md +1963 -0
- package/ai-config/agents/specialized/healthcare-dev.md +1620 -0
- package/ai-config/agents/specialized/mobile-developer.md +188 -0
- package/ai-config/agents/specialized/parallel-plan-executor.md +506 -0
- package/ai-config/agents/specialized/plan-executor.md +485 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/00-INDEX.md +485 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/01-CORE.md +3493 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/02-SELF-CORRECTION.md +778 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/03-PROGRESSIVE-SETUP.md +918 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/04-DEPLOYMENT.md +1537 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/05-TESTING.md +2633 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/06-OPERATIONS.md +5610 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/INSTALL.md +335 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/QUICK-REFERENCE.txt +215 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/README.md +260 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/START-HERE.md +379 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/WORKFLOW-DIAGRAM.md +355 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/solo-dev-planner.md +279 -0
- package/ai-config/agents/specialized/template-writer.md +347 -0
- package/ai-config/agents/specialized/test-runner.md +99 -0
- package/ai-config/agents/specialized/vibekanban-smart-worker.md +244 -0
- package/ai-config/agents/specialized/wave-executor.md +138 -0
- package/ai-config/agents/specialized/workflow-optimizer.md +1114 -0
- package/ai-config/commands/git/changelog.md +32 -0
- package/ai-config/commands/git/ci-local.md +70 -0
- package/ai-config/commands/git/commit.md +35 -0
- package/ai-config/commands/git/fix-issue.md +23 -0
- package/ai-config/commands/git/pr-create.md +42 -0
- package/ai-config/commands/git/pr-review.md +50 -0
- package/ai-config/commands/git/worktree.md +39 -0
- package/ai-config/commands/refactoring/cleanup.md +24 -0
- package/ai-config/commands/refactoring/dead-code.md +40 -0
- package/ai-config/commands/refactoring/extract.md +31 -0
- package/ai-config/commands/testing/e2e.md +30 -0
- package/ai-config/commands/testing/tdd.md +36 -0
- package/ai-config/commands/testing/test-coverage.md +30 -0
- package/ai-config/commands/testing/test-fix.md +24 -0
- package/ai-config/commands/workflow/generate-agents-md.md +85 -0
- package/ai-config/commands/workflow/planning.md +47 -0
- package/ai-config/commands/workflows/compound.md +89 -0
- package/ai-config/commands/workflows/plan.md +77 -0
- package/ai-config/commands/workflows/review.md +78 -0
- package/ai-config/commands/workflows/work.md +75 -0
- package/ai-config/config.yaml +18 -0
- package/ai-config/hooks/_TEMPLATE.md +96 -0
- package/ai-config/hooks/block-dangerous-commands.md +75 -0
- package/ai-config/hooks/commit-guard.md +90 -0
- package/ai-config/hooks/context-loader.md +73 -0
- package/ai-config/hooks/improve-prompt.md +91 -0
- package/ai-config/hooks/learning-log.md +72 -0
- package/ai-config/hooks/model-router.md +86 -0
- package/ai-config/hooks/secret-scanner.md +64 -0
- package/ai-config/hooks/skill-validator.md +102 -0
- package/ai-config/hooks/task-artifact.md +114 -0
- package/ai-config/hooks/validate-workflow.md +100 -0
- package/ai-config/prompts/base.md +71 -0
- package/ai-config/prompts/modes/debug.md +34 -0
- package/ai-config/prompts/modes/deploy.md +40 -0
- package/ai-config/prompts/modes/research.md +32 -0
- package/ai-config/prompts/modes/review.md +33 -0
- package/ai-config/prompts/review-policy.md +79 -0
- package/ai-config/skills/_TEMPLATE.md +157 -0
- package/ai-config/skills/backend/api-gateway/SKILL.md +254 -0
- package/ai-config/skills/backend/bff-concepts/SKILL.md +239 -0
- package/ai-config/skills/backend/bff-spring/SKILL.md +364 -0
- package/ai-config/skills/backend/chi-router/SKILL.md +396 -0
- package/ai-config/skills/backend/error-handling/SKILL.md +255 -0
- package/ai-config/skills/backend/exceptions-spring/SKILL.md +323 -0
- package/ai-config/skills/backend/fastapi/SKILL.md +302 -0
- package/ai-config/skills/backend/gateway-spring/SKILL.md +390 -0
- package/ai-config/skills/backend/go-backend/SKILL.md +457 -0
- package/ai-config/skills/backend/gradle-multimodule/SKILL.md +274 -0
- package/ai-config/skills/backend/graphql-concepts/SKILL.md +352 -0
- package/ai-config/skills/backend/graphql-spring/SKILL.md +398 -0
- package/ai-config/skills/backend/grpc-concepts/SKILL.md +283 -0
- package/ai-config/skills/backend/grpc-spring/SKILL.md +445 -0
- package/ai-config/skills/backend/jwt-auth/SKILL.md +412 -0
- package/ai-config/skills/backend/notifications-concepts/SKILL.md +259 -0
- package/ai-config/skills/backend/recommendations-concepts/SKILL.md +261 -0
- package/ai-config/skills/backend/search-concepts/SKILL.md +263 -0
- package/ai-config/skills/backend/search-spring/SKILL.md +375 -0
- package/ai-config/skills/backend/spring-boot-4/SKILL.md +172 -0
- package/ai-config/skills/backend/websockets/SKILL.md +532 -0
- package/ai-config/skills/data-ai/ai-ml/SKILL.md +423 -0
- package/ai-config/skills/data-ai/analytics-concepts/SKILL.md +195 -0
- package/ai-config/skills/data-ai/analytics-spring/SKILL.md +340 -0
- package/ai-config/skills/data-ai/duckdb-analytics/SKILL.md +440 -0
- package/ai-config/skills/data-ai/langchain/SKILL.md +238 -0
- package/ai-config/skills/data-ai/mlflow/SKILL.md +302 -0
- package/ai-config/skills/data-ai/onnx-inference/SKILL.md +290 -0
- package/ai-config/skills/data-ai/powerbi/SKILL.md +352 -0
- package/ai-config/skills/data-ai/pytorch/SKILL.md +274 -0
- package/ai-config/skills/data-ai/scikit-learn/SKILL.md +321 -0
- package/ai-config/skills/data-ai/vector-db/SKILL.md +301 -0
- package/ai-config/skills/database/graph-databases/SKILL.md +218 -0
- package/ai-config/skills/database/graph-spring/SKILL.md +361 -0
- package/ai-config/skills/database/pgx-postgres/SKILL.md +512 -0
- package/ai-config/skills/database/redis-cache/SKILL.md +343 -0
- package/ai-config/skills/database/sqlite-embedded/SKILL.md +388 -0
- package/ai-config/skills/database/timescaledb/SKILL.md +320 -0
- package/ai-config/skills/docs/api-documentation/SKILL.md +293 -0
- package/ai-config/skills/docs/docs-spring/SKILL.md +377 -0
- package/ai-config/skills/docs/mustache-templates/SKILL.md +190 -0
- package/ai-config/skills/docs/technical-docs/SKILL.md +447 -0
- package/ai-config/skills/frontend/astro-ssr/SKILL.md +441 -0
- package/ai-config/skills/frontend/frontend-design/SKILL.md +54 -0
- package/ai-config/skills/frontend/frontend-web/SKILL.md +368 -0
- package/ai-config/skills/frontend/mantine-ui/SKILL.md +396 -0
- package/ai-config/skills/frontend/tanstack-query/SKILL.md +439 -0
- package/ai-config/skills/frontend/zod-validation/SKILL.md +417 -0
- package/ai-config/skills/frontend/zustand-state/SKILL.md +350 -0
- package/ai-config/skills/infrastructure/chaos-engineering/SKILL.md +244 -0
- package/ai-config/skills/infrastructure/chaos-spring/SKILL.md +378 -0
- package/ai-config/skills/infrastructure/devops-infra/SKILL.md +435 -0
- package/ai-config/skills/infrastructure/docker-containers/SKILL.md +420 -0
- package/ai-config/skills/infrastructure/kubernetes/SKILL.md +456 -0
- package/ai-config/skills/infrastructure/opentelemetry/SKILL.md +546 -0
- package/ai-config/skills/infrastructure/traefik-proxy/SKILL.md +474 -0
- package/ai-config/skills/infrastructure/woodpecker-ci/SKILL.md +315 -0
- package/ai-config/skills/mobile/ionic-capacitor/SKILL.md +504 -0
- package/ai-config/skills/mobile/mobile-ionic/SKILL.md +448 -0
- package/ai-config/skills/prompt-improver/SKILL.md +125 -0
- package/ai-config/skills/quality/ghagga-review/SKILL.md +216 -0
- package/ai-config/skills/references/hooks-patterns/SKILL.md +238 -0
- package/ai-config/skills/references/mcp-servers/SKILL.md +275 -0
- package/ai-config/skills/references/plugins-reference/SKILL.md +110 -0
- package/ai-config/skills/references/skills-reference/SKILL.md +420 -0
- package/ai-config/skills/references/subagent-templates/SKILL.md +193 -0
- package/ai-config/skills/systems-iot/modbus-protocol/SKILL.md +410 -0
- package/ai-config/skills/systems-iot/mqtt-rumqttc/SKILL.md +408 -0
- package/ai-config/skills/systems-iot/rust-systems/SKILL.md +386 -0
- package/ai-config/skills/systems-iot/tokio-async/SKILL.md +324 -0
- package/ai-config/skills/testing/playwright-e2e/SKILL.md +289 -0
- package/ai-config/skills/testing/testcontainers/SKILL.md +299 -0
- package/ai-config/skills/testing/vitest-testing/SKILL.md +381 -0
- package/ai-config/skills/workflow/ci-local-guide/SKILL.md +118 -0
- package/ai-config/skills/workflow/claude-automation-recommender/SKILL.md +299 -0
- package/ai-config/skills/workflow/claude-md-improver/SKILL.md +158 -0
- package/ai-config/skills/workflow/finishing-a-development-branch/SKILL.md +117 -0
- package/ai-config/skills/workflow/git-github/SKILL.md +334 -0
- package/ai-config/skills/workflow/git-github/references/examples.md +160 -0
- package/ai-config/skills/workflow/git-workflow/SKILL.md +214 -0
- package/ai-config/skills/workflow/ide-plugins/SKILL.md +277 -0
- package/ai-config/skills/workflow/ide-plugins-intellij/SKILL.md +401 -0
- package/ai-config/skills/workflow/obsidian-brain-workflow/SKILL.md +199 -0
- package/ai-config/skills/workflow/using-git-worktrees/SKILL.md +100 -0
- package/ai-config/skills/workflow/verification-before-completion/SKILL.md +73 -0
- package/ai-config/skills/workflow/wave-workflow/SKILL.md +178 -0
- package/ci-local/README.md +170 -0
- package/ci-local/ci-local.sh +297 -0
- package/ci-local/hooks/commit-msg +74 -0
- package/ci-local/hooks/pre-commit +162 -0
- package/ci-local/hooks/pre-push +41 -0
- package/ci-local/install.sh +49 -0
- package/ci-local/semgrep.yml +214 -0
- package/dist/commands/analyze.d.ts +9 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +55 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/analyze.test.d.ts +2 -0
- package/dist/commands/analyze.test.d.ts.map +1 -0
- package/dist/commands/analyze.test.js +145 -0
- package/dist/commands/analyze.test.js.map +1 -0
- package/dist/commands/doctor.d.ts +7 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +158 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/doctor.test.d.ts +2 -0
- package/dist/commands/doctor.test.d.ts.map +1 -0
- package/dist/commands/doctor.test.js +200 -0
- package/dist/commands/doctor.test.js.map +1 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +283 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/init.test.d.ts +2 -0
- package/dist/commands/init.test.d.ts.map +1 -0
- package/dist/commands/init.test.js +271 -0
- package/dist/commands/init.test.js.map +1 -0
- package/dist/commands/sync.d.ts +8 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +201 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/constants.d.ts +21 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +57 -0
- package/dist/constants.js.map +1 -0
- package/dist/e2e/aggressive.e2e.test.d.ts +2 -0
- package/dist/e2e/aggressive.e2e.test.d.ts.map +1 -0
- package/dist/e2e/aggressive.e2e.test.js +350 -0
- package/dist/e2e/aggressive.e2e.test.js.map +1 -0
- package/dist/e2e/commands.e2e.test.d.ts +2 -0
- package/dist/e2e/commands.e2e.test.d.ts.map +1 -0
- package/dist/e2e/commands.e2e.test.js +213 -0
- package/dist/e2e/commands.e2e.test.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/common.d.ts +17 -0
- package/dist/lib/common.d.ts.map +1 -0
- package/dist/lib/common.js +111 -0
- package/dist/lib/common.js.map +1 -0
- package/dist/lib/common.test.d.ts +2 -0
- package/dist/lib/common.test.d.ts.map +1 -0
- package/dist/lib/common.test.js +316 -0
- package/dist/lib/common.test.js.map +1 -0
- package/dist/lib/frontmatter.d.ts +18 -0
- package/dist/lib/frontmatter.d.ts.map +1 -0
- package/dist/lib/frontmatter.js +61 -0
- package/dist/lib/frontmatter.js.map +1 -0
- package/dist/lib/frontmatter.test.d.ts +2 -0
- package/dist/lib/frontmatter.test.d.ts.map +1 -0
- package/dist/lib/frontmatter.test.js +257 -0
- package/dist/lib/frontmatter.test.js.map +1 -0
- package/dist/lib/template.d.ts +24 -0
- package/dist/lib/template.d.ts.map +1 -0
- package/dist/lib/template.js +78 -0
- package/dist/lib/template.js.map +1 -0
- package/dist/lib/template.test.d.ts +2 -0
- package/dist/lib/template.test.d.ts.map +1 -0
- package/dist/lib/template.test.js +201 -0
- package/dist/lib/template.test.js.map +1 -0
- package/dist/types/index.d.ts +48 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/ui/AnalyzeUI.d.ts +7 -0
- package/dist/ui/AnalyzeUI.d.ts.map +1 -0
- package/dist/ui/AnalyzeUI.js +100 -0
- package/dist/ui/AnalyzeUI.js.map +1 -0
- package/dist/ui/App.d.ts +13 -0
- package/dist/ui/App.d.ts.map +1 -0
- package/dist/ui/App.js +100 -0
- package/dist/ui/App.js.map +1 -0
- package/dist/ui/CIContext.d.ts +9 -0
- package/dist/ui/CIContext.d.ts.map +1 -0
- package/dist/ui/CIContext.js +9 -0
- package/dist/ui/CIContext.js.map +1 -0
- package/dist/ui/CISelector.d.ts +8 -0
- package/dist/ui/CISelector.d.ts.map +1 -0
- package/dist/ui/CISelector.js +45 -0
- package/dist/ui/CISelector.js.map +1 -0
- package/dist/ui/Doctor.d.ts +3 -0
- package/dist/ui/Doctor.d.ts.map +1 -0
- package/dist/ui/Doctor.js +89 -0
- package/dist/ui/Doctor.js.map +1 -0
- package/dist/ui/Header.d.ts +8 -0
- package/dist/ui/Header.d.ts.map +1 -0
- package/dist/ui/Header.js +30 -0
- package/dist/ui/Header.js.map +1 -0
- package/dist/ui/MemorySelector.d.ts +8 -0
- package/dist/ui/MemorySelector.d.ts.map +1 -0
- package/dist/ui/MemorySelector.js +46 -0
- package/dist/ui/MemorySelector.js.map +1 -0
- package/dist/ui/NameInput.d.ts +8 -0
- package/dist/ui/NameInput.d.ts.map +1 -0
- package/dist/ui/NameInput.js +69 -0
- package/dist/ui/NameInput.js.map +1 -0
- package/dist/ui/OptionSelector.d.ts +12 -0
- package/dist/ui/OptionSelector.d.ts.map +1 -0
- package/dist/ui/OptionSelector.js +69 -0
- package/dist/ui/OptionSelector.js.map +1 -0
- package/dist/ui/Progress.d.ts +11 -0
- package/dist/ui/Progress.d.ts.map +1 -0
- package/dist/ui/Progress.js +58 -0
- package/dist/ui/Progress.js.map +1 -0
- package/dist/ui/StackSelector.d.ts +9 -0
- package/dist/ui/StackSelector.d.ts.map +1 -0
- package/dist/ui/StackSelector.js +65 -0
- package/dist/ui/StackSelector.js.map +1 -0
- package/dist/ui/Summary.d.ts +12 -0
- package/dist/ui/Summary.d.ts.map +1 -0
- package/dist/ui/Summary.js +114 -0
- package/dist/ui/Summary.js.map +1 -0
- package/dist/ui/SyncUI.d.ts +10 -0
- package/dist/ui/SyncUI.d.ts.map +1 -0
- package/dist/ui/SyncUI.js +64 -0
- package/dist/ui/SyncUI.js.map +1 -0
- package/dist/ui/Welcome.d.ts +7 -0
- package/dist/ui/Welcome.d.ts.map +1 -0
- package/dist/ui/Welcome.js +45 -0
- package/dist/ui/Welcome.js.map +1 -0
- package/dist/ui/theme.d.ts +10 -0
- package/dist/ui/theme.d.ts.map +1 -0
- package/dist/ui/theme.js +9 -0
- package/dist/ui/theme.js.map +1 -0
- package/modules/engram/.gitignore-snippet.txt +6 -0
- package/modules/engram/.mcp-config-snippet.json +11 -0
- package/modules/engram/README.md +146 -0
- package/modules/engram/install-engram.sh +216 -0
- package/modules/ghagga/.env.example +43 -0
- package/modules/ghagga/README.md +153 -0
- package/modules/ghagga/docker-compose.yml +80 -0
- package/modules/ghagga/setup-ghagga.sh +139 -0
- package/modules/memory-simple/.project/NOTES.md +22 -0
- package/modules/memory-simple/README.md +23 -0
- package/modules/obsidian-brain/.obsidian/app.json +23 -0
- package/modules/obsidian-brain/.obsidian/appearance.json +5 -0
- package/modules/obsidian-brain/.obsidian/bookmarks.json +34 -0
- package/modules/obsidian-brain/.obsidian/community-plugins.json +1 -0
- package/modules/obsidian-brain/.obsidian/core-plugins-migration.json +21 -0
- package/modules/obsidian-brain/.obsidian/core-plugins.json +18 -0
- package/modules/obsidian-brain/.obsidian/daily-notes.json +5 -0
- package/modules/obsidian-brain/.obsidian/graph.json +37 -0
- package/modules/obsidian-brain/.obsidian/hotkeys.json +14 -0
- package/modules/obsidian-brain/.obsidian/plugins/dataview/data.json +25 -0
- package/modules/obsidian-brain/.obsidian/plugins/obsidian-kanban/data.json +29 -0
- package/modules/obsidian-brain/.obsidian/plugins/templater-obsidian/data.json +18 -0
- package/modules/obsidian-brain/.obsidian/snippets/project-memory.css +71 -0
- package/modules/obsidian-brain/.obsidian-gitignore-snippet.txt +8 -0
- package/modules/obsidian-brain/.project/Attachments/.gitkeep +0 -0
- package/modules/obsidian-brain/.project/Memory/BLOCKERS.md +78 -0
- package/modules/obsidian-brain/.project/Memory/CONTEXT.md +102 -0
- package/modules/obsidian-brain/.project/Memory/DASHBOARD.md +73 -0
- package/modules/obsidian-brain/.project/Memory/DECISIONS.md +87 -0
- package/modules/obsidian-brain/.project/Memory/KANBAN.md +15 -0
- package/modules/obsidian-brain/.project/Memory/README.md +61 -0
- package/modules/obsidian-brain/.project/Memory/WAVES.md +78 -0
- package/modules/obsidian-brain/.project/Sessions/TEMPLATE.md +99 -0
- package/modules/obsidian-brain/.project/Templates/ADR.md +33 -0
- package/modules/obsidian-brain/.project/Templates/Blocker.md +21 -0
- package/modules/obsidian-brain/.project/Templates/Session.md +88 -0
- package/modules/obsidian-brain/README.md +268 -0
- package/modules/obsidian-brain/new-wave.sh +182 -0
- package/package.json +51 -0
- package/schemas/agent.schema.json +34 -0
- package/schemas/ai-config.schema.json +28 -0
- package/schemas/skill.schema.json +44 -0
- package/src/commands/analyze.test.ts +145 -0
- package/src/commands/analyze.ts +69 -0
- package/src/commands/doctor.test.ts +208 -0
- package/src/commands/doctor.ts +163 -0
- package/src/commands/init.test.ts +298 -0
- package/src/commands/init.ts +285 -0
- package/src/constants.ts +69 -0
- package/src/e2e/aggressive.e2e.test.ts +557 -0
- package/src/e2e/commands.e2e.test.ts +298 -0
- package/src/index.tsx +106 -0
- package/src/lib/common.test.ts +318 -0
- package/src/lib/common.ts +127 -0
- package/src/lib/frontmatter.test.ts +291 -0
- package/src/lib/frontmatter.ts +77 -0
- package/src/lib/template.test.ts +226 -0
- package/src/lib/template.ts +99 -0
- package/src/types/index.ts +53 -0
- package/src/ui/AnalyzeUI.tsx +133 -0
- package/src/ui/App.tsx +175 -0
- package/src/ui/CIContext.tsx +25 -0
- package/src/ui/CISelector.tsx +72 -0
- package/src/ui/Doctor.tsx +122 -0
- package/src/ui/Header.tsx +48 -0
- package/src/ui/MemorySelector.tsx +73 -0
- package/src/ui/NameInput.tsx +82 -0
- package/src/ui/OptionSelector.tsx +100 -0
- package/src/ui/Progress.tsx +88 -0
- package/src/ui/StackSelector.tsx +101 -0
- package/src/ui/Summary.tsx +134 -0
- package/src/ui/Welcome.tsx +54 -0
- package/src/ui/theme.ts +10 -0
- package/stryker.config.json +19 -0
- package/tasks/_TEMPLATE/files-edited.md +3 -0
- package/tasks/_TEMPLATE/plan.md +3 -0
- package/tasks/_TEMPLATE/research.md +3 -0
- package/tasks/_TEMPLATE/verification.md +5 -0
- package/templates/common/dependabot/cargo.yml +11 -0
- package/templates/common/dependabot/github-actions.yml +16 -0
- package/templates/common/dependabot/gomod.yml +15 -0
- package/templates/common/dependabot/gradle.yml +15 -0
- package/templates/common/dependabot/header.yml +3 -0
- package/templates/common/dependabot/maven.yml +15 -0
- package/templates/common/dependabot/npm.yml +20 -0
- package/templates/common/dependabot/pip.yml +11 -0
- package/templates/dependabot.yml +162 -0
- package/templates/github/ci-go.yml +41 -0
- package/templates/github/ci-java.yml +45 -0
- package/templates/github/ci-monorepo.yml +150 -0
- package/templates/github/ci-node.yml +42 -0
- package/templates/github/ci-python.yml +42 -0
- package/templates/github/ci-rust.yml +42 -0
- package/templates/github/dependabot-automerge.yml +40 -0
- package/templates/gitlab/gitlab-ci-go.yml +88 -0
- package/templates/gitlab/gitlab-ci-java.yml +79 -0
- package/templates/gitlab/gitlab-ci-monorepo.yml +126 -0
- package/templates/gitlab/gitlab-ci-node.yml +63 -0
- package/templates/gitlab/gitlab-ci-python.yml +147 -0
- package/templates/gitlab/gitlab-ci-rust.yml +67 -0
- package/templates/global/claude-settings.json +98 -0
- package/templates/global/codex-config.toml +8 -0
- package/templates/global/copilot-instructions/base-rules.instructions.md +13 -0
- package/templates/global/copilot-instructions/sdd-orchestrator.instructions.md +37 -0
- package/templates/global/gemini-commands/cleanup.toml +20 -0
- package/templates/global/gemini-commands/commit.toml +15 -0
- package/templates/global/gemini-commands/dead-code.toml +22 -0
- package/templates/global/gemini-commands/plan.toml +30 -0
- package/templates/global/gemini-commands/review.toml +17 -0
- package/templates/global/gemini-commands/sdd-apply.toml +22 -0
- package/templates/global/gemini-commands/sdd-ff.toml +14 -0
- package/templates/global/gemini-commands/sdd-new.toml +21 -0
- package/templates/global/gemini-commands/sdd-verify.toml +21 -0
- package/templates/global/gemini-commands/tdd.toml +26 -0
- package/templates/global/gemini-settings.json +8 -0
- package/templates/global/opencode-config.json +44 -0
- package/templates/global/sdd-instructions.md +47 -0
- package/templates/global/sdd-orchestrator-claude.md +46 -0
- package/templates/global/sdd-orchestrator-copilot.md +34 -0
- package/templates/renovate.json +69 -0
- package/templates/woodpecker/monorepo/backend.yml +34 -0
- package/templates/woodpecker/monorepo/frontend.yml +34 -0
- package/templates/woodpecker/monorepo/summary.yml +25 -0
- package/templates/woodpecker/woodpecker-go.yml +51 -0
- package/templates/woodpecker/woodpecker-java.yml +67 -0
- package/templates/woodpecker/woodpecker-node.yml +47 -0
- package/templates/woodpecker/woodpecker-python.yml +108 -0
- package/templates/woodpecker/woodpecker-rust.yml +57 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +16 -0
- package/workflows/reusable-build-go.yml +111 -0
- package/workflows/reusable-build-java.yml +120 -0
- package/workflows/reusable-build-node.yml +145 -0
- package/workflows/reusable-build-python.yml +159 -0
- package/workflows/reusable-build-rust.yml +135 -0
- package/workflows/reusable-docker.yml +120 -0
- package/workflows/reusable-ghagga-review.yml +165 -0
- package/workflows/reusable-release.yml +91 -0
|
@@ -0,0 +1,1204 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: accessibility-auditor
|
|
3
|
+
description: Accessibility expert specializing in WCAG compliance, screen reader testing, and inclusive design practices
|
|
4
|
+
trigger: >
|
|
5
|
+
WCAG, accessibility, a11y, screen reader, keyboard navigation, color contrast,
|
|
6
|
+
ARIA, Section 508, ADA compliance, assistive technology, inclusive design
|
|
7
|
+
category: quality
|
|
8
|
+
color: pink
|
|
9
|
+
tools: Write, Read, MultiEdit, Bash, Grep, Glob
|
|
10
|
+
config:
|
|
11
|
+
model: sonnet
|
|
12
|
+
metadata:
|
|
13
|
+
version: "2.0"
|
|
14
|
+
updated: "2026-02"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
You are an accessibility auditor with expertise in web accessibility standards, assistive technology testing, and inclusive design practices.
|
|
18
|
+
|
|
19
|
+
## Core Expertise
|
|
20
|
+
- WCAG 2.1/2.2 AA and AAA compliance
|
|
21
|
+
- Screen reader and assistive technology testing
|
|
22
|
+
- Keyboard navigation and motor accessibility
|
|
23
|
+
- Color contrast and visual accessibility
|
|
24
|
+
- Cognitive and learning accessibility
|
|
25
|
+
- Mobile accessibility and responsive design
|
|
26
|
+
- Accessibility automation and testing tools
|
|
27
|
+
- Legal compliance and accessibility auditing
|
|
28
|
+
|
|
29
|
+
## Technical Stack
|
|
30
|
+
- **Testing Tools**: axe-core, Lighthouse, WAVE, Pa11y, Deque axe DevTools
|
|
31
|
+
- **Screen Readers**: NVDA, JAWS, VoiceOver, TalkBack, Orca
|
|
32
|
+
- **Browser Tools**: Chrome DevTools, Firefox Accessibility Inspector
|
|
33
|
+
- **Color Tools**: Colour Contrast Analyser, WebAIM Contrast Checker
|
|
34
|
+
- **Automation**: Playwright, Cypress, Jest-axe, Storybook a11y addon
|
|
35
|
+
- **Design Tools**: Figma Accessibility Plugin, Stark, Able
|
|
36
|
+
- **Standards**: WCAG 2.1/2.2, Section 508, EN 301 549, ADA
|
|
37
|
+
|
|
38
|
+
## Automated Accessibility Testing Framework
|
|
39
|
+
```javascript
|
|
40
|
+
// tests/accessibility/a11y-test-suite.js
|
|
41
|
+
import { test, expect } from '@playwright/test';
|
|
42
|
+
import AxeBuilder from '@axe-core/playwright';
|
|
43
|
+
|
|
44
|
+
class AccessibilityTester {
|
|
45
|
+
constructor(page) {
|
|
46
|
+
this.page = page;
|
|
47
|
+
this.violations = [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async runFullAudit(url, options = {}) {
|
|
51
|
+
await this.page.goto(url);
|
|
52
|
+
|
|
53
|
+
const axeBuilder = new AxeBuilder({ page: this.page })
|
|
54
|
+
.withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa'])
|
|
55
|
+
.exclude(options.exclude || [])
|
|
56
|
+
.include(options.include || []);
|
|
57
|
+
|
|
58
|
+
if (options.disableRules) {
|
|
59
|
+
axeBuilder.disableRules(options.disableRules);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const results = await axeBuilder.analyze();
|
|
63
|
+
this.violations = results.violations;
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
violations: results.violations,
|
|
67
|
+
passes: results.passes,
|
|
68
|
+
incomplete: results.incomplete,
|
|
69
|
+
inapplicable: results.inapplicable,
|
|
70
|
+
summary: this.generateSummary(results)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async testKeyboardNavigation() {
|
|
75
|
+
const violations = [];
|
|
76
|
+
|
|
77
|
+
// Test tab navigation
|
|
78
|
+
const focusableElements = await this.page.locator(
|
|
79
|
+
'a, button, input, textarea, select, [tabindex]:not([tabindex="-1"])'
|
|
80
|
+
).all();
|
|
81
|
+
|
|
82
|
+
// Check tab order
|
|
83
|
+
await this.page.keyboard.press('Tab');
|
|
84
|
+
let previousTabIndex = -1;
|
|
85
|
+
|
|
86
|
+
for (let i = 0; i < Math.min(focusableElements.length, 20); i++) {
|
|
87
|
+
const focusedElement = await this.page.locator(':focus').first();
|
|
88
|
+
|
|
89
|
+
if (await focusedElement.count() === 0) {
|
|
90
|
+
violations.push(`No element focused at tab step ${i + 1}`);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const tabIndex = await focusedElement.getAttribute('tabindex');
|
|
95
|
+
const currentTabIndex = tabIndex ? parseInt(tabIndex) : 0;
|
|
96
|
+
|
|
97
|
+
if (currentTabIndex > 0 && currentTabIndex <= previousTabIndex) {
|
|
98
|
+
violations.push(`Tab order violation: tabindex ${currentTabIndex} after ${previousTabIndex}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
previousTabIndex = currentTabIndex;
|
|
102
|
+
await this.page.keyboard.press('Tab');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Test escape key functionality
|
|
106
|
+
const modals = await this.page.locator('[role="dialog"], .modal').all();
|
|
107
|
+
for (const modal of modals) {
|
|
108
|
+
if (await modal.isVisible()) {
|
|
109
|
+
await this.page.keyboard.press('Escape');
|
|
110
|
+
if (await modal.isVisible()) {
|
|
111
|
+
violations.push('Modal does not close with Escape key');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return violations;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async testColorContrast() {
|
|
120
|
+
const violations = [];
|
|
121
|
+
|
|
122
|
+
const textElements = await this.page.locator('p, h1, h2, h3, h4, h5, h6, span, a, button, label').all();
|
|
123
|
+
|
|
124
|
+
for (const element of textElements.slice(0, 50)) { // Limit for performance
|
|
125
|
+
try {
|
|
126
|
+
const styles = await element.evaluate(el => {
|
|
127
|
+
const computedStyle = window.getComputedStyle(el);
|
|
128
|
+
return {
|
|
129
|
+
color: computedStyle.color,
|
|
130
|
+
backgroundColor: computedStyle.backgroundColor,
|
|
131
|
+
fontSize: computedStyle.fontSize,
|
|
132
|
+
fontWeight: computedStyle.fontWeight
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const textContent = await element.textContent();
|
|
137
|
+
if (!textContent || textContent.trim().length === 0) continue;
|
|
138
|
+
|
|
139
|
+
// This is a simplified check - in practice, use a proper contrast calculator
|
|
140
|
+
const contrastRatio = await this.calculateContrastRatio(styles.color, styles.backgroundColor);
|
|
141
|
+
|
|
142
|
+
const fontSize = parseFloat(styles.fontSize);
|
|
143
|
+
const isLargeText = fontSize >= 18 || (fontSize >= 14 && styles.fontWeight >= 700);
|
|
144
|
+
|
|
145
|
+
const requiredRatio = isLargeText ? 3 : 4.5;
|
|
146
|
+
|
|
147
|
+
if (contrastRatio < requiredRatio) {
|
|
148
|
+
violations.push({
|
|
149
|
+
element: await element.getAttribute('outerHTML'),
|
|
150
|
+
contrastRatio: contrastRatio,
|
|
151
|
+
requiredRatio: requiredRatio,
|
|
152
|
+
isLargeText: isLargeText
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
} catch (error) {
|
|
156
|
+
// Skip elements that can't be analyzed
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return violations;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async testScreenReaderCompatibility() {
|
|
164
|
+
const violations = [];
|
|
165
|
+
|
|
166
|
+
// Check for proper heading structure
|
|
167
|
+
const headings = await this.page.locator('h1, h2, h3, h4, h5, h6').all();
|
|
168
|
+
let previousLevel = 0;
|
|
169
|
+
|
|
170
|
+
for (const heading of headings) {
|
|
171
|
+
const tagName = await heading.evaluate(el => el.tagName.toLowerCase());
|
|
172
|
+
const currentLevel = parseInt(tagName.substring(1));
|
|
173
|
+
|
|
174
|
+
if (currentLevel > previousLevel + 1) {
|
|
175
|
+
violations.push(`Heading level skip: jumped from h${previousLevel} to h${currentLevel}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const text = await heading.textContent();
|
|
179
|
+
if (!text || text.trim().length === 0) {
|
|
180
|
+
violations.push(`Empty heading: ${tagName}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
previousLevel = currentLevel;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Check for alt text on images
|
|
187
|
+
const images = await this.page.locator('img').all();
|
|
188
|
+
for (const img of images) {
|
|
189
|
+
const alt = await img.getAttribute('alt');
|
|
190
|
+
const role = await img.getAttribute('role');
|
|
191
|
+
|
|
192
|
+
if (alt === null && role !== 'presentation') {
|
|
193
|
+
violations.push('Image missing alt text');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Check for form labels
|
|
198
|
+
const inputs = await this.page.locator('input, textarea, select').all();
|
|
199
|
+
for (const input of inputs) {
|
|
200
|
+
const id = await input.getAttribute('id');
|
|
201
|
+
const ariaLabel = await input.getAttribute('aria-label');
|
|
202
|
+
const ariaLabelledby = await input.getAttribute('aria-labelledby');
|
|
203
|
+
|
|
204
|
+
let hasLabel = false;
|
|
205
|
+
|
|
206
|
+
if (id) {
|
|
207
|
+
const label = await this.page.locator(`label[for="${id}"]`).count();
|
|
208
|
+
hasLabel = label > 0;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (!hasLabel && !ariaLabel && !ariaLabelledby) {
|
|
212
|
+
violations.push(`Form input missing label: ${await input.getAttribute('outerHTML')}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Check for proper button text
|
|
217
|
+
const buttons = await this.page.locator('button, [role="button"]').all();
|
|
218
|
+
for (const button of buttons) {
|
|
219
|
+
const text = await button.textContent();
|
|
220
|
+
const ariaLabel = await button.getAttribute('aria-label');
|
|
221
|
+
|
|
222
|
+
if ((!text || text.trim().length === 0) && !ariaLabel) {
|
|
223
|
+
violations.push('Button missing accessible text');
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return violations;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async calculateContrastRatio(foreground, background) {
|
|
231
|
+
// Simplified contrast calculation - use a proper library in production
|
|
232
|
+
return await this.page.evaluate(([fg, bg]) => {
|
|
233
|
+
// This would need a proper color contrast calculation implementation
|
|
234
|
+
// For now, return a placeholder value
|
|
235
|
+
return 4.5; // Placeholder
|
|
236
|
+
}, [foreground, background]);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
generateSummary(results) {
|
|
240
|
+
const criticalCount = results.violations.filter(v => v.impact === 'critical').length;
|
|
241
|
+
const seriousCount = results.violations.filter(v => v.impact === 'serious').length;
|
|
242
|
+
const moderateCount = results.violations.filter(v => v.impact === 'moderate').length;
|
|
243
|
+
const minorCount = results.violations.filter(v => v.impact === 'minor').length;
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
totalViolations: results.violations.length,
|
|
247
|
+
criticalCount,
|
|
248
|
+
seriousCount,
|
|
249
|
+
moderateCount,
|
|
250
|
+
minorCount,
|
|
251
|
+
passCount: results.passes.length,
|
|
252
|
+
incompleteCount: results.incomplete.length
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
generateReport(auditResults) {
|
|
257
|
+
const { violations, summary } = auditResults;
|
|
258
|
+
|
|
259
|
+
let report = `
|
|
260
|
+
Accessibility Audit Report
|
|
261
|
+
==========================
|
|
262
|
+
Date: ${new Date().toISOString()}
|
|
263
|
+
|
|
264
|
+
Summary:
|
|
265
|
+
--------
|
|
266
|
+
Total Violations: ${summary.totalViolations}
|
|
267
|
+
- Critical: ${summary.criticalCount}
|
|
268
|
+
- Serious: ${summary.seriousCount}
|
|
269
|
+
- Moderate: ${summary.moderateCount}
|
|
270
|
+
- Minor: ${summary.minorCount}
|
|
271
|
+
|
|
272
|
+
Passed Tests: ${summary.passCount}
|
|
273
|
+
Incomplete Tests: ${summary.incompleteCount}
|
|
274
|
+
|
|
275
|
+
Detailed Violations:
|
|
276
|
+
-------------------
|
|
277
|
+
`;
|
|
278
|
+
|
|
279
|
+
violations.forEach((violation, index) => {
|
|
280
|
+
report += `
|
|
281
|
+
${index + 1}. ${violation.id} (${violation.impact})
|
|
282
|
+
Description: ${violation.description}
|
|
283
|
+
Help: ${violation.help}
|
|
284
|
+
Tags: ${violation.tags.join(', ')}
|
|
285
|
+
Affected Elements: ${violation.nodes.length}
|
|
286
|
+
|
|
287
|
+
WCAG Guidelines:
|
|
288
|
+
${violation.tags.filter(tag => tag.startsWith('wcag')).join(', ')}
|
|
289
|
+
|
|
290
|
+
How to Fix:
|
|
291
|
+
${violation.helpUrl}
|
|
292
|
+
|
|
293
|
+
Example Fix:
|
|
294
|
+
${this.generateFixExample(violation)}
|
|
295
|
+
|
|
296
|
+
-------------------`;
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
return report;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
generateFixExample(violation) {
|
|
303
|
+
const examples = {
|
|
304
|
+
'color-contrast': `
|
|
305
|
+
// Ensure text has sufficient color contrast
|
|
306
|
+
.text-element {
|
|
307
|
+
color: #000000; /* Dark text */
|
|
308
|
+
background-color: #ffffff; /* Light background */
|
|
309
|
+
/* Contrast ratio: 21:1 (WCAG AAA) */
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// For large text (18px+ or 14px+ bold)
|
|
313
|
+
.large-text {
|
|
314
|
+
color: #666666; /* Lighter text acceptable */
|
|
315
|
+
background-color: #ffffff;
|
|
316
|
+
/* Contrast ratio: 5.7:1 (WCAG AA Large Text) */
|
|
317
|
+
}`,
|
|
318
|
+
|
|
319
|
+
'image-alt': `
|
|
320
|
+
<!-- Good: Descriptive alt text -->
|
|
321
|
+
<img src="chart.png" alt="Sales increased 25% from Q1 to Q2">
|
|
322
|
+
|
|
323
|
+
<!-- Good: Decorative image -->
|
|
324
|
+
<img src="decoration.png" alt="" role="presentation">
|
|
325
|
+
|
|
326
|
+
<!-- Good: Complex image with description -->
|
|
327
|
+
<img src="complex-chart.png" alt="Q2 Sales Data" aria-describedby="chart-desc">
|
|
328
|
+
<div id="chart-desc">Detailed description of the sales chart...</div>`,
|
|
329
|
+
|
|
330
|
+
'label': `
|
|
331
|
+
<!-- Good: Explicit label -->
|
|
332
|
+
<label for="email">Email Address</label>
|
|
333
|
+
<input type="email" id="email" name="email">
|
|
334
|
+
|
|
335
|
+
<!-- Good: Implicit label -->
|
|
336
|
+
<label>
|
|
337
|
+
Email Address
|
|
338
|
+
<input type="email" name="email">
|
|
339
|
+
</label>
|
|
340
|
+
|
|
341
|
+
<!-- Good: aria-label -->
|
|
342
|
+
<input type="email" aria-label="Email Address" name="email">`,
|
|
343
|
+
|
|
344
|
+
'heading-order': `
|
|
345
|
+
<!-- Good: Proper heading hierarchy -->
|
|
346
|
+
<h1>Main Page Title</h1>
|
|
347
|
+
<h2>Section Title</h2>
|
|
348
|
+
<h3>Subsection Title</h3>
|
|
349
|
+
<h3>Another Subsection</h3>
|
|
350
|
+
<h2>Another Section</h2>
|
|
351
|
+
|
|
352
|
+
<!-- Bad: Skipped heading level -->
|
|
353
|
+
<h1>Main Title</h1>
|
|
354
|
+
<h3>This skips h2!</h3> <!-- Should be h2 -->`,
|
|
355
|
+
|
|
356
|
+
'button-name': `
|
|
357
|
+
<!-- Good: Button with text -->
|
|
358
|
+
<button>Save Changes</button>
|
|
359
|
+
|
|
360
|
+
<!-- Good: Button with aria-label -->
|
|
361
|
+
<button aria-label="Close dialog">×</button>
|
|
362
|
+
|
|
363
|
+
<!-- Good: Button with accessible text -->
|
|
364
|
+
<button>
|
|
365
|
+
<span class="icon" aria-hidden="true">🔒</span>
|
|
366
|
+
Lock Account
|
|
367
|
+
</button>`
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
return examples[violation.id] || '// No example available for this violation type';
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Test implementation
|
|
375
|
+
test.describe('Accessibility Audit', () => {
|
|
376
|
+
let accessibilityTester;
|
|
377
|
+
|
|
378
|
+
test.beforeEach(async ({ page }) => {
|
|
379
|
+
accessibilityTester = new AccessibilityTester(page);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test('homepage accessibility audit', async ({ page }) => {
|
|
383
|
+
const results = await accessibilityTester.runFullAudit('/');
|
|
384
|
+
|
|
385
|
+
// Generate and save report
|
|
386
|
+
const report = accessibilityTester.generateReport(results);
|
|
387
|
+
console.log(report);
|
|
388
|
+
|
|
389
|
+
// Assert no critical or serious violations
|
|
390
|
+
const criticalViolations = results.violations.filter(v => v.impact === 'critical');
|
|
391
|
+
const seriousViolations = results.violations.filter(v => v.impact === 'serious');
|
|
392
|
+
|
|
393
|
+
expect(criticalViolations).toHaveLength(0);
|
|
394
|
+
expect(seriousViolations).toHaveLength(0);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
test('keyboard navigation test', async ({ page }) => {
|
|
398
|
+
await page.goto('/');
|
|
399
|
+
const violations = await accessibilityTester.testKeyboardNavigation();
|
|
400
|
+
|
|
401
|
+
expect(violations).toHaveLength(0);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test('screen reader compatibility', async ({ page }) => {
|
|
405
|
+
await page.goto('/');
|
|
406
|
+
const violations = await accessibilityTester.testScreenReaderCompatibility();
|
|
407
|
+
|
|
408
|
+
expect(violations).toHaveLength(0);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test('color contrast compliance', async ({ page }) => {
|
|
412
|
+
await page.goto('/');
|
|
413
|
+
const violations = await accessibilityTester.testColorContrast();
|
|
414
|
+
|
|
415
|
+
// Allow minor contrast issues but no major ones
|
|
416
|
+
const majorViolations = violations.filter(v => v.contrastRatio < 3);
|
|
417
|
+
expect(majorViolations).toHaveLength(0);
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
export { AccessibilityTester };
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
## Manual Testing Procedures and Checklists
|
|
425
|
+
```markdown
|
|
426
|
+
# Manual Accessibility Testing Checklist
|
|
427
|
+
|
|
428
|
+
## 1. Keyboard Navigation Testing
|
|
429
|
+
|
|
430
|
+
### Tab Navigation
|
|
431
|
+
- [ ] All interactive elements are reachable via Tab key
|
|
432
|
+
- [ ] Tab order is logical and follows visual layout
|
|
433
|
+
- [ ] No keyboard traps (can escape from all elements)
|
|
434
|
+
- [ ] Skip links are available and functional
|
|
435
|
+
- [ ] Custom interactive elements respond to Enter/Space
|
|
436
|
+
|
|
437
|
+
### Keyboard Shortcuts
|
|
438
|
+
- [ ] Standard shortcuts work (Ctrl+Z, Ctrl+C, etc.)
|
|
439
|
+
- [ ] Custom shortcuts are documented
|
|
440
|
+
- [ ] Shortcuts don't conflict with screen reader shortcuts
|
|
441
|
+
- [ ] Escape key closes modals and dropdowns
|
|
442
|
+
|
|
443
|
+
## 2. Screen Reader Testing
|
|
444
|
+
|
|
445
|
+
### NVDA Testing (Windows)
|
|
446
|
+
1. Install NVDA (free screen reader)
|
|
447
|
+
2. Start NVDA with Ctrl+Alt+N
|
|
448
|
+
3. Navigate with:
|
|
449
|
+
- Tab: Next focusable element
|
|
450
|
+
- H: Next heading
|
|
451
|
+
- K: Next link
|
|
452
|
+
- F: Next form field
|
|
453
|
+
- G: Next graphic
|
|
454
|
+
|
|
455
|
+
### Testing Checklist
|
|
456
|
+
- [ ] All content is announced
|
|
457
|
+
- [ ] Headings provide good page structure
|
|
458
|
+
- [ ] Form labels are clear and associated
|
|
459
|
+
- [ ] Error messages are announced
|
|
460
|
+
- [ ] Live regions announce updates
|
|
461
|
+
- [ ] Images have appropriate alt text
|
|
462
|
+
|
|
463
|
+
### VoiceOver Testing (macOS)
|
|
464
|
+
1. Enable VoiceOver: Cmd+F5
|
|
465
|
+
2. Use VoiceOver cursor: Ctrl+Option+Arrow keys
|
|
466
|
+
3. Test web navigation: Ctrl+Option+U (Web Rotor)
|
|
467
|
+
|
|
468
|
+
### Testing Commands
|
|
469
|
+
- Ctrl+Option+H: Next heading
|
|
470
|
+
- Ctrl+Option+L: Next link
|
|
471
|
+
- Ctrl+Option+J: Next form control
|
|
472
|
+
- Ctrl+Option+G: Next graphic
|
|
473
|
+
|
|
474
|
+
## 3. Mobile Accessibility Testing
|
|
475
|
+
|
|
476
|
+
### iOS VoiceOver
|
|
477
|
+
1. Settings > Accessibility > VoiceOver > On
|
|
478
|
+
2. Triple-click home button to toggle
|
|
479
|
+
3. Swipe right to navigate
|
|
480
|
+
4. Double-tap to activate
|
|
481
|
+
|
|
482
|
+
### Android TalkBack
|
|
483
|
+
1. Settings > Accessibility > TalkBack > On
|
|
484
|
+
2. Swipe right to navigate
|
|
485
|
+
3. Double-tap to activate
|
|
486
|
+
4. Two-finger swipe to scroll
|
|
487
|
+
|
|
488
|
+
### Mobile Checklist
|
|
489
|
+
- [ ] Touch targets are at least 44x44 pixels
|
|
490
|
+
- [ ] Gestures are accessible
|
|
491
|
+
- [ ] Text can be resized to 200%
|
|
492
|
+
- [ ] Orientation changes work properly
|
|
493
|
+
- [ ] Voice control works
|
|
494
|
+
|
|
495
|
+
## 4. Visual Accessibility Testing
|
|
496
|
+
|
|
497
|
+
### Color and Contrast
|
|
498
|
+
- [ ] Text contrast meets WCAG AA (4.5:1 normal, 3:1 large)
|
|
499
|
+
- [ ] Color is not the only way to convey information
|
|
500
|
+
- [ ] Focus indicators are visible and high contrast
|
|
501
|
+
- [ ] Error states don't rely only on color
|
|
502
|
+
|
|
503
|
+
### Visual Design
|
|
504
|
+
- [ ] Content is readable at 200% zoom
|
|
505
|
+
- [ ] No horizontal scrolling at 320px width
|
|
506
|
+
- [ ] Text reflow works properly
|
|
507
|
+
- [ ] Important content remains visible when zoomed
|
|
508
|
+
|
|
509
|
+
## 5. Cognitive Accessibility Testing
|
|
510
|
+
|
|
511
|
+
### Content and Language
|
|
512
|
+
- [ ] Language is clear and simple
|
|
513
|
+
- [ ] Instructions are easy to understand
|
|
514
|
+
- [ ] Error messages are helpful
|
|
515
|
+
- [ ] Consistent navigation and layout
|
|
516
|
+
- [ ] No auto-playing audio/video
|
|
517
|
+
|
|
518
|
+
### Time and Interaction
|
|
519
|
+
- [ ] No time limits or they're adjustable
|
|
520
|
+
- [ ] Auto-refresh can be paused or disabled
|
|
521
|
+
- [ ] Animations can be reduced/disabled
|
|
522
|
+
- [ ] Content doesn't flash more than 3 times per second
|
|
523
|
+
|
|
524
|
+
## 6. Form Accessibility Testing
|
|
525
|
+
|
|
526
|
+
### Labels and Instructions
|
|
527
|
+
- [ ] All form fields have labels
|
|
528
|
+
- [ ] Required fields are clearly marked
|
|
529
|
+
- [ ] Field format requirements are explained
|
|
530
|
+
- [ ] Group related fields with fieldsets
|
|
531
|
+
|
|
532
|
+
### Error Handling
|
|
533
|
+
- [ ] Errors are clearly identified
|
|
534
|
+
- [ ] Error messages are helpful and specific
|
|
535
|
+
- [ ] Errors are associated with relevant fields
|
|
536
|
+
- [ ] Success messages are provided
|
|
537
|
+
|
|
538
|
+
### Validation
|
|
539
|
+
- [ ] Client-side validation is accessible
|
|
540
|
+
- [ ] Server-side validation provides accessible feedback
|
|
541
|
+
- [ ] Progressive enhancement works without JavaScript
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
## Automated Testing Integration
|
|
545
|
+
```javascript
|
|
546
|
+
// jest.config.js - Jest configuration for accessibility testing
|
|
547
|
+
module.exports = {
|
|
548
|
+
testEnvironment: 'jsdom',
|
|
549
|
+
setupFilesAfterEnv: ['<rootDir>/src/tests/setup.js'],
|
|
550
|
+
testMatch: ['**/__tests__/**/*.test.js', '**/?(*.)+(spec|test).js'],
|
|
551
|
+
collectCoverageFrom: [
|
|
552
|
+
'src/**/*.{js,jsx}',
|
|
553
|
+
'!src/tests/**',
|
|
554
|
+
'!src/stories/**'
|
|
555
|
+
]
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
// src/tests/setup.js - Test setup with jest-axe
|
|
559
|
+
import 'jest-axe/extend-expect';
|
|
560
|
+
import { configureAxe } from 'jest-axe';
|
|
561
|
+
|
|
562
|
+
// Configure axe for testing
|
|
563
|
+
const axe = configureAxe({
|
|
564
|
+
rules: {
|
|
565
|
+
// Disable rules that aren't relevant for unit tests
|
|
566
|
+
'document-title': { enabled: false },
|
|
567
|
+
'html-has-lang': { enabled: false },
|
|
568
|
+
'landmark-one-main': { enabled: false },
|
|
569
|
+
'page-has-heading-one': { enabled: false }
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
global.axe = axe;
|
|
574
|
+
|
|
575
|
+
// src/components/Button/Button.test.js - Component accessibility testing
|
|
576
|
+
import React from 'react';
|
|
577
|
+
import { render, screen } from '@testing-library/react';
|
|
578
|
+
import userEvent from '@testing-library/user-event';
|
|
579
|
+
import { axe, toHaveNoViolations } from 'jest-axe';
|
|
580
|
+
import Button from './Button';
|
|
581
|
+
|
|
582
|
+
expect.extend(toHaveNoViolations);
|
|
583
|
+
|
|
584
|
+
describe('Button Accessibility', () => {
|
|
585
|
+
test('should not have accessibility violations', async () => {
|
|
586
|
+
const { container } = render(<Button>Click me</Button>);
|
|
587
|
+
const results = await axe(container);
|
|
588
|
+
expect(results).toHaveNoViolations();
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
test('should be focusable and clickable via keyboard', async () => {
|
|
592
|
+
const user = userEvent.setup();
|
|
593
|
+
const handleClick = jest.fn();
|
|
594
|
+
|
|
595
|
+
render(<Button onClick={handleClick}>Click me</Button>);
|
|
596
|
+
|
|
597
|
+
const button = screen.getByRole('button', { name: /click me/i });
|
|
598
|
+
|
|
599
|
+
// Focus via keyboard
|
|
600
|
+
await user.tab();
|
|
601
|
+
expect(button).toHaveFocus();
|
|
602
|
+
|
|
603
|
+
// Click via keyboard
|
|
604
|
+
await user.keyboard('{Enter}');
|
|
605
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
606
|
+
|
|
607
|
+
await user.keyboard(' ');
|
|
608
|
+
expect(handleClick).toHaveBeenCalledTimes(2);
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
test('should have proper ARIA attributes when disabled', () => {
|
|
612
|
+
render(<Button disabled>Disabled button</Button>);
|
|
613
|
+
|
|
614
|
+
const button = screen.getByRole('button');
|
|
615
|
+
expect(button).toHaveAttribute('aria-disabled', 'true');
|
|
616
|
+
expect(button).toBeDisabled();
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
test('should support ARIA label when needed', async () => {
|
|
620
|
+
const { container } = render(
|
|
621
|
+
<Button aria-label="Close dialog">×</Button>
|
|
622
|
+
);
|
|
623
|
+
|
|
624
|
+
const button = screen.getByRole('button', { name: /close dialog/i });
|
|
625
|
+
expect(button).toBeInTheDocument();
|
|
626
|
+
|
|
627
|
+
const results = await axe(container);
|
|
628
|
+
expect(results).toHaveNoViolations();
|
|
629
|
+
});
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
// Storybook accessibility addon configuration
|
|
633
|
+
// .storybook/main.js
|
|
634
|
+
module.exports = {
|
|
635
|
+
addons: [
|
|
636
|
+
'@storybook/addon-essentials',
|
|
637
|
+
'@storybook/addon-a11y',
|
|
638
|
+
'@storybook/addon-controls'
|
|
639
|
+
]
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
// .storybook/preview.js
|
|
643
|
+
import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';
|
|
644
|
+
|
|
645
|
+
export const parameters = {
|
|
646
|
+
a11y: {
|
|
647
|
+
config: {
|
|
648
|
+
rules: [
|
|
649
|
+
{
|
|
650
|
+
id: 'color-contrast',
|
|
651
|
+
enabled: true
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
id: 'keyboard-navigation',
|
|
655
|
+
enabled: true
|
|
656
|
+
}
|
|
657
|
+
]
|
|
658
|
+
},
|
|
659
|
+
options: {
|
|
660
|
+
checks: { 'color-contrast': { options: { noScroll: true } } },
|
|
661
|
+
restoreScroll: true
|
|
662
|
+
}
|
|
663
|
+
},
|
|
664
|
+
viewport: {
|
|
665
|
+
viewports: INITIAL_VIEWPORTS
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
## Component Library Accessibility Guidelines
|
|
671
|
+
```javascript
|
|
672
|
+
// src/components/AccessibleModal/AccessibleModal.jsx
|
|
673
|
+
import React, { useEffect, useRef } from 'react';
|
|
674
|
+
import { createPortal } from 'react-dom';
|
|
675
|
+
import FocusTrap from 'focus-trap-react';
|
|
676
|
+
|
|
677
|
+
const AccessibleModal = ({
|
|
678
|
+
isOpen,
|
|
679
|
+
onClose,
|
|
680
|
+
title,
|
|
681
|
+
children,
|
|
682
|
+
ariaLabelledby,
|
|
683
|
+
ariaDescribedby
|
|
684
|
+
}) => {
|
|
685
|
+
const modalRef = useRef(null);
|
|
686
|
+
const previousActiveElement = useRef(null);
|
|
687
|
+
|
|
688
|
+
useEffect(() => {
|
|
689
|
+
if (isOpen) {
|
|
690
|
+
// Store the previously focused element
|
|
691
|
+
previousActiveElement.current = document.activeElement;
|
|
692
|
+
|
|
693
|
+
// Prevent body scroll
|
|
694
|
+
document.body.style.overflow = 'hidden';
|
|
695
|
+
|
|
696
|
+
// Set focus to modal
|
|
697
|
+
if (modalRef.current) {
|
|
698
|
+
modalRef.current.focus();
|
|
699
|
+
}
|
|
700
|
+
} else {
|
|
701
|
+
// Restore body scroll
|
|
702
|
+
document.body.style.overflow = '';
|
|
703
|
+
|
|
704
|
+
// Return focus to previously focused element
|
|
705
|
+
if (previousActiveElement.current) {
|
|
706
|
+
previousActiveElement.current.focus();
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
return () => {
|
|
711
|
+
document.body.style.overflow = '';
|
|
712
|
+
};
|
|
713
|
+
}, [isOpen]);
|
|
714
|
+
|
|
715
|
+
// Handle escape key
|
|
716
|
+
useEffect(() => {
|
|
717
|
+
const handleEscape = (event) => {
|
|
718
|
+
if (event.key === 'Escape' && isOpen) {
|
|
719
|
+
onClose();
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
document.addEventListener('keydown', handleEscape);
|
|
724
|
+
return () => document.removeEventListener('keydown', handleEscape);
|
|
725
|
+
}, [isOpen, onClose]);
|
|
726
|
+
|
|
727
|
+
if (!isOpen) return null;
|
|
728
|
+
|
|
729
|
+
const modalContent = (
|
|
730
|
+
<div className="modal-overlay" onClick={onClose}>
|
|
731
|
+
<FocusTrap>
|
|
732
|
+
<div
|
|
733
|
+
ref={modalRef}
|
|
734
|
+
className="modal-content"
|
|
735
|
+
role="dialog"
|
|
736
|
+
aria-modal="true"
|
|
737
|
+
aria-labelledby={ariaLabelledby || 'modal-title'}
|
|
738
|
+
aria-describedby={ariaDescribedby}
|
|
739
|
+
tabIndex={-1}
|
|
740
|
+
onClick={(e) => e.stopPropagation()}
|
|
741
|
+
>
|
|
742
|
+
<div className="modal-header">
|
|
743
|
+
<h2 id="modal-title" className="modal-title">
|
|
744
|
+
{title}
|
|
745
|
+
</h2>
|
|
746
|
+
<button
|
|
747
|
+
className="modal-close"
|
|
748
|
+
onClick={onClose}
|
|
749
|
+
aria-label="Close dialog"
|
|
750
|
+
>
|
|
751
|
+
×
|
|
752
|
+
</button>
|
|
753
|
+
</div>
|
|
754
|
+
<div className="modal-body">
|
|
755
|
+
{children}
|
|
756
|
+
</div>
|
|
757
|
+
</div>
|
|
758
|
+
</FocusTrap>
|
|
759
|
+
</div>
|
|
760
|
+
);
|
|
761
|
+
|
|
762
|
+
return createPortal(modalContent, document.body);
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
// CSS for modal
|
|
766
|
+
const modalStyles = `
|
|
767
|
+
.modal-overlay {
|
|
768
|
+
position: fixed;
|
|
769
|
+
top: 0;
|
|
770
|
+
left: 0;
|
|
771
|
+
right: 0;
|
|
772
|
+
bottom: 0;
|
|
773
|
+
background: rgba(0, 0, 0, 0.6);
|
|
774
|
+
display: flex;
|
|
775
|
+
align-items: center;
|
|
776
|
+
justify-content: center;
|
|
777
|
+
z-index: 1000;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
.modal-content {
|
|
781
|
+
background: white;
|
|
782
|
+
max-width: 90vw;
|
|
783
|
+
max-height: 90vh;
|
|
784
|
+
overflow: auto;
|
|
785
|
+
border-radius: 4px;
|
|
786
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
787
|
+
outline: none;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
.modal-content:focus {
|
|
791
|
+
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.5);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
.modal-header {
|
|
795
|
+
display: flex;
|
|
796
|
+
justify-content: space-between;
|
|
797
|
+
align-items: center;
|
|
798
|
+
padding: 1rem;
|
|
799
|
+
border-bottom: 1px solid #e0e0e0;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
.modal-close {
|
|
803
|
+
background: none;
|
|
804
|
+
border: none;
|
|
805
|
+
font-size: 1.5rem;
|
|
806
|
+
cursor: pointer;
|
|
807
|
+
padding: 0.25rem;
|
|
808
|
+
line-height: 1;
|
|
809
|
+
color: #666;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
.modal-close:hover,
|
|
813
|
+
.modal-close:focus {
|
|
814
|
+
color: #000;
|
|
815
|
+
outline: 2px solid #0066cc;
|
|
816
|
+
outline-offset: 2px;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
.modal-body {
|
|
820
|
+
padding: 1rem;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/* Ensure good contrast for focus indicators */
|
|
824
|
+
*:focus {
|
|
825
|
+
outline: 2px solid #0066cc;
|
|
826
|
+
outline-offset: 2px;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/* Skip link styles */
|
|
830
|
+
.skip-link {
|
|
831
|
+
position: absolute;
|
|
832
|
+
top: -40px;
|
|
833
|
+
left: 6px;
|
|
834
|
+
background: #000;
|
|
835
|
+
color: #fff;
|
|
836
|
+
padding: 8px;
|
|
837
|
+
text-decoration: none;
|
|
838
|
+
border-radius: 0 0 4px 4px;
|
|
839
|
+
z-index: 1001;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
.skip-link:focus {
|
|
843
|
+
top: 0;
|
|
844
|
+
}
|
|
845
|
+
`;
|
|
846
|
+
|
|
847
|
+
export default AccessibleModal;
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
## WCAG Compliance Checklist and Audit Framework
|
|
851
|
+
```yaml
|
|
852
|
+
# WCAG 2.1 AA Compliance Checklist
|
|
853
|
+
|
|
854
|
+
# Principle 1: Perceivable
|
|
855
|
+
perceivable:
|
|
856
|
+
- guideline_1_1: # Non-text Content
|
|
857
|
+
- success_criterion_1_1_1: # Images of Text
|
|
858
|
+
level: A
|
|
859
|
+
description: "All non-text content has appropriate text alternatives"
|
|
860
|
+
tests:
|
|
861
|
+
- "Images have descriptive alt text"
|
|
862
|
+
- "Decorative images marked with empty alt or role='presentation'"
|
|
863
|
+
- "Complex images have long descriptions"
|
|
864
|
+
- "CAPTCHAs have alternative forms"
|
|
865
|
+
|
|
866
|
+
- guideline_1_2: # Time-based Media
|
|
867
|
+
- success_criterion_1_2_1: # Audio-only and Video-only (Prerecorded)
|
|
868
|
+
level: A
|
|
869
|
+
description: "Audio-only and video-only content has alternatives"
|
|
870
|
+
- success_criterion_1_2_2: # Captions (Prerecorded)
|
|
871
|
+
level: A
|
|
872
|
+
description: "Captions provided for prerecorded audio content"
|
|
873
|
+
- success_criterion_1_2_3: # Audio Description or Media Alternative
|
|
874
|
+
level: A
|
|
875
|
+
description: "Audio description or full text alternative for video"
|
|
876
|
+
|
|
877
|
+
- guideline_1_3: # Adaptable
|
|
878
|
+
- success_criterion_1_3_1: # Info and Relationships
|
|
879
|
+
level: A
|
|
880
|
+
description: "Information structure preserved when presentation changes"
|
|
881
|
+
tests:
|
|
882
|
+
- "Proper heading hierarchy (h1-h6)"
|
|
883
|
+
- "Form labels properly associated"
|
|
884
|
+
- "Table headers identified"
|
|
885
|
+
- "Lists marked up as lists"
|
|
886
|
+
- success_criterion_1_3_2: # Meaningful Sequence
|
|
887
|
+
level: A
|
|
888
|
+
description: "Content order makes sense when linearized"
|
|
889
|
+
- success_criterion_1_3_3: # Sensory Characteristics
|
|
890
|
+
level: A
|
|
891
|
+
description: "Instructions don't rely solely on sensory characteristics"
|
|
892
|
+
|
|
893
|
+
- guideline_1_4: # Distinguishable
|
|
894
|
+
- success_criterion_1_4_1: # Use of Color
|
|
895
|
+
level: A
|
|
896
|
+
description: "Color not the only means of conveying information"
|
|
897
|
+
- success_criterion_1_4_2: # Audio Control
|
|
898
|
+
level: A
|
|
899
|
+
description: "Audio that plays automatically can be controlled"
|
|
900
|
+
- success_criterion_1_4_3: # Contrast (Minimum)
|
|
901
|
+
level: AA
|
|
902
|
+
description: "Text has sufficient color contrast (4.5:1 normal, 3:1 large)"
|
|
903
|
+
- success_criterion_1_4_4: # Resize Text
|
|
904
|
+
level: AA
|
|
905
|
+
description: "Text can be resized to 200% without assistive technology"
|
|
906
|
+
- success_criterion_1_4_5: # Images of Text
|
|
907
|
+
level: AA
|
|
908
|
+
description: "Use actual text rather than images of text when possible"
|
|
909
|
+
|
|
910
|
+
# Principle 2: Operable
|
|
911
|
+
operable:
|
|
912
|
+
- guideline_2_1: # Keyboard Accessible
|
|
913
|
+
- success_criterion_2_1_1: # Keyboard
|
|
914
|
+
level: A
|
|
915
|
+
description: "All functionality available via keyboard"
|
|
916
|
+
tests:
|
|
917
|
+
- "All interactive elements reachable via Tab"
|
|
918
|
+
- "All functionality works with keyboard"
|
|
919
|
+
- "No keyboard traps"
|
|
920
|
+
- success_criterion_2_1_2: # No Keyboard Trap
|
|
921
|
+
level: A
|
|
922
|
+
description: "Focus can move away from any component"
|
|
923
|
+
- success_criterion_2_1_4: # Character Key Shortcuts
|
|
924
|
+
level: A
|
|
925
|
+
description: "Single character shortcuts can be turned off or remapped"
|
|
926
|
+
|
|
927
|
+
- guideline_2_2: # Enough Time
|
|
928
|
+
- success_criterion_2_2_1: # Timing Adjustable
|
|
929
|
+
level: A
|
|
930
|
+
description: "Time limits can be turned off, adjusted, or extended"
|
|
931
|
+
- success_criterion_2_2_2: # Pause, Stop, Hide
|
|
932
|
+
level: A
|
|
933
|
+
description: "Moving, blinking, or auto-updating content can be controlled"
|
|
934
|
+
|
|
935
|
+
- guideline_2_3: # Seizures and Physical Reactions
|
|
936
|
+
- success_criterion_2_3_1: # Three Flashes or Below Threshold
|
|
937
|
+
level: A
|
|
938
|
+
description: "No content flashes more than 3 times per second"
|
|
939
|
+
|
|
940
|
+
- guideline_2_4: # Navigable
|
|
941
|
+
- success_criterion_2_4_1: # Bypass Blocks
|
|
942
|
+
level: A
|
|
943
|
+
description: "Skip links or other bypass mechanisms available"
|
|
944
|
+
- success_criterion_2_4_2: # Page Titled
|
|
945
|
+
level: A
|
|
946
|
+
description: "Web pages have descriptive titles"
|
|
947
|
+
- success_criterion_2_4_3: # Focus Order
|
|
948
|
+
level: A
|
|
949
|
+
description: "Focus order is logical and usable"
|
|
950
|
+
- success_criterion_2_4_4: # Link Purpose (In Context)
|
|
951
|
+
level: A
|
|
952
|
+
description: "Link purpose clear from text or context"
|
|
953
|
+
- success_criterion_2_4_5: # Multiple Ways
|
|
954
|
+
level: AA
|
|
955
|
+
description: "Multiple ways to locate web pages"
|
|
956
|
+
- success_criterion_2_4_6: # Headings and Labels
|
|
957
|
+
level: AA
|
|
958
|
+
description: "Headings and labels describe topic or purpose"
|
|
959
|
+
- success_criterion_2_4_7: # Focus Visible
|
|
960
|
+
level: AA
|
|
961
|
+
description: "Keyboard focus indicator is visible"
|
|
962
|
+
|
|
963
|
+
- guideline_2_5: # Input Modalities
|
|
964
|
+
- success_criterion_2_5_1: # Pointer Gestures
|
|
965
|
+
level: A
|
|
966
|
+
description: "Multipoint or path-based gestures have single-pointer alternative"
|
|
967
|
+
- success_criterion_2_5_2: # Pointer Cancellation
|
|
968
|
+
level: A
|
|
969
|
+
description: "Functions triggered by single-pointer can be cancelled"
|
|
970
|
+
- success_criterion_2_5_3: # Label in Name
|
|
971
|
+
level: A
|
|
972
|
+
description: "Accessible name contains visible label text"
|
|
973
|
+
- success_criterion_2_5_4: # Motion Actuation
|
|
974
|
+
level: A
|
|
975
|
+
description: "Functions triggered by motion can be turned off"
|
|
976
|
+
|
|
977
|
+
# Principle 3: Understandable
|
|
978
|
+
understandable:
|
|
979
|
+
- guideline_3_1: # Readable
|
|
980
|
+
- success_criterion_3_1_1: # Language of Page
|
|
981
|
+
level: A
|
|
982
|
+
description: "Primary language of page is programmatically determined"
|
|
983
|
+
- success_criterion_3_1_2: # Language of Parts
|
|
984
|
+
level: AA
|
|
985
|
+
description: "Language of content parts is programmatically determined"
|
|
986
|
+
|
|
987
|
+
- guideline_3_2: # Predictable
|
|
988
|
+
- success_criterion_3_2_1: # On Focus
|
|
989
|
+
level: A
|
|
990
|
+
description: "Focus doesn't trigger unexpected context changes"
|
|
991
|
+
- success_criterion_3_2_2: # On Input
|
|
992
|
+
level: A
|
|
993
|
+
description: "Input doesn't trigger unexpected context changes"
|
|
994
|
+
- success_criterion_3_2_3: # Consistent Navigation
|
|
995
|
+
level: AA
|
|
996
|
+
description: "Navigation is consistent across pages"
|
|
997
|
+
- success_criterion_3_2_4: # Consistent Identification
|
|
998
|
+
level: AA
|
|
999
|
+
description: "Components with same functionality identified consistently"
|
|
1000
|
+
|
|
1001
|
+
- guideline_3_3: # Input Assistance
|
|
1002
|
+
- success_criterion_3_3_1: # Error Identification
|
|
1003
|
+
level: A
|
|
1004
|
+
description: "Input errors are identified and described in text"
|
|
1005
|
+
- success_criterion_3_3_2: # Labels or Instructions
|
|
1006
|
+
level: A
|
|
1007
|
+
description: "Labels or instructions provided for user input"
|
|
1008
|
+
- success_criterion_3_3_3: # Error Suggestion
|
|
1009
|
+
level: AA
|
|
1010
|
+
description: "Error correction suggestions provided when possible"
|
|
1011
|
+
- success_criterion_3_3_4: # Error Prevention (Legal, Financial, Data)
|
|
1012
|
+
level: AA
|
|
1013
|
+
description: "Important submissions can be reversed, checked, or confirmed"
|
|
1014
|
+
|
|
1015
|
+
# Principle 4: Robust
|
|
1016
|
+
robust:
|
|
1017
|
+
- guideline_4_1: # Compatible
|
|
1018
|
+
- success_criterion_4_1_1: # Parsing
|
|
1019
|
+
level: A
|
|
1020
|
+
description: "Content can be parsed reliably by assistive technologies"
|
|
1021
|
+
- success_criterion_4_1_2: # Name, Role, Value
|
|
1022
|
+
level: A
|
|
1023
|
+
description: "UI components have accessible name, role, and value"
|
|
1024
|
+
- success_criterion_4_1_3: # Status Messages
|
|
1025
|
+
level: AA
|
|
1026
|
+
description: "Status messages are programmatically determinable"
|
|
1027
|
+
```
|
|
1028
|
+
|
|
1029
|
+
## CI/CD Integration for Accessibility
|
|
1030
|
+
```yaml
|
|
1031
|
+
# .github/workflows/accessibility.yml
|
|
1032
|
+
name: Accessibility Testing
|
|
1033
|
+
|
|
1034
|
+
on:
|
|
1035
|
+
push:
|
|
1036
|
+
branches: [main, develop]
|
|
1037
|
+
pull_request:
|
|
1038
|
+
branches: [main]
|
|
1039
|
+
|
|
1040
|
+
jobs:
|
|
1041
|
+
accessibility-tests:
|
|
1042
|
+
runs-on: ubuntu-latest
|
|
1043
|
+
|
|
1044
|
+
steps:
|
|
1045
|
+
- uses: actions/checkout@v3
|
|
1046
|
+
|
|
1047
|
+
- name: Setup Node.js
|
|
1048
|
+
uses: actions/setup-node@v3
|
|
1049
|
+
with:
|
|
1050
|
+
node-version: 18
|
|
1051
|
+
cache: 'npm'
|
|
1052
|
+
|
|
1053
|
+
- name: Install dependencies
|
|
1054
|
+
run: npm ci
|
|
1055
|
+
|
|
1056
|
+
- name: Build application
|
|
1057
|
+
run: npm run build
|
|
1058
|
+
|
|
1059
|
+
- name: Start application
|
|
1060
|
+
run: |
|
|
1061
|
+
npm start &
|
|
1062
|
+
npx wait-on http://localhost:3000
|
|
1063
|
+
|
|
1064
|
+
- name: Install accessibility testing tools
|
|
1065
|
+
run: |
|
|
1066
|
+
npm install -g @axe-core/cli
|
|
1067
|
+
npm install -g pa11y
|
|
1068
|
+
npm install -g lighthouse
|
|
1069
|
+
|
|
1070
|
+
- name: Run axe-core accessibility tests
|
|
1071
|
+
run: |
|
|
1072
|
+
axe http://localhost:3000 \
|
|
1073
|
+
--tags wcag2a,wcag2aa,wcag21aa \
|
|
1074
|
+
--reporter json \
|
|
1075
|
+
--output axe-results.json
|
|
1076
|
+
|
|
1077
|
+
- name: Run Pa11y accessibility tests
|
|
1078
|
+
run: |
|
|
1079
|
+
pa11y http://localhost:3000 \
|
|
1080
|
+
--standard WCAG2AA \
|
|
1081
|
+
--reporter json \
|
|
1082
|
+
--output pa11y-results.json
|
|
1083
|
+
|
|
1084
|
+
- name: Run Lighthouse accessibility audit
|
|
1085
|
+
run: |
|
|
1086
|
+
lighthouse http://localhost:3000 \
|
|
1087
|
+
--only-categories=accessibility \
|
|
1088
|
+
--output=json \
|
|
1089
|
+
--output-path=lighthouse-a11y.json \
|
|
1090
|
+
--chrome-flags="--headless"
|
|
1091
|
+
|
|
1092
|
+
- name: Run Playwright accessibility tests
|
|
1093
|
+
run: npx playwright test tests/accessibility/
|
|
1094
|
+
|
|
1095
|
+
- name: Generate accessibility report
|
|
1096
|
+
run: |
|
|
1097
|
+
node scripts/generate-a11y-report.js
|
|
1098
|
+
|
|
1099
|
+
- name: Upload accessibility artifacts
|
|
1100
|
+
uses: actions/upload-artifact@v3
|
|
1101
|
+
with:
|
|
1102
|
+
name: accessibility-reports
|
|
1103
|
+
path: |
|
|
1104
|
+
axe-results.json
|
|
1105
|
+
pa11y-results.json
|
|
1106
|
+
lighthouse-a11y.json
|
|
1107
|
+
accessibility-report.html
|
|
1108
|
+
retention-days: 30
|
|
1109
|
+
|
|
1110
|
+
- name: Comment PR with accessibility results
|
|
1111
|
+
if: github.event_name == 'pull_request'
|
|
1112
|
+
uses: actions/github-script@v6
|
|
1113
|
+
with:
|
|
1114
|
+
script: |
|
|
1115
|
+
const fs = require('fs');
|
|
1116
|
+
const axeResults = JSON.parse(fs.readFileSync('axe-results.json', 'utf8'));
|
|
1117
|
+
|
|
1118
|
+
const violationsCount = axeResults.violations.length;
|
|
1119
|
+
const passesCount = axeResults.passes.length;
|
|
1120
|
+
|
|
1121
|
+
const comment = `
|
|
1122
|
+
## Accessibility Test Results
|
|
1123
|
+
|
|
1124
|
+
- ✅ **Passed**: ${passesCount} tests
|
|
1125
|
+
- ❌ **Failed**: ${violationsCount} tests
|
|
1126
|
+
|
|
1127
|
+
${violationsCount > 0 ? `
|
|
1128
|
+
### Violations Found:
|
|
1129
|
+
${axeResults.violations.map(v => `
|
|
1130
|
+
- **${v.id}** (${v.impact}): ${v.description}
|
|
1131
|
+
- Affected elements: ${v.nodes.length}
|
|
1132
|
+
- Help: ${v.helpUrl}
|
|
1133
|
+
`).join('')}
|
|
1134
|
+
` : '🎉 No accessibility violations found!'}
|
|
1135
|
+
|
|
1136
|
+
[View detailed report](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID})
|
|
1137
|
+
`;
|
|
1138
|
+
|
|
1139
|
+
github.rest.issues.createComment({
|
|
1140
|
+
issue_number: context.issue.number,
|
|
1141
|
+
owner: context.repo.owner,
|
|
1142
|
+
repo: context.repo.repo,
|
|
1143
|
+
body: comment
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
visual-accessibility:
|
|
1147
|
+
runs-on: ubuntu-latest
|
|
1148
|
+
steps:
|
|
1149
|
+
- uses: actions/checkout@v3
|
|
1150
|
+
|
|
1151
|
+
- name: Setup Node.js
|
|
1152
|
+
uses: actions/setup-node@v3
|
|
1153
|
+
with:
|
|
1154
|
+
node-version: 18
|
|
1155
|
+
cache: 'npm'
|
|
1156
|
+
|
|
1157
|
+
- name: Install dependencies
|
|
1158
|
+
run: npm ci
|
|
1159
|
+
|
|
1160
|
+
- name: Color contrast testing
|
|
1161
|
+
run: |
|
|
1162
|
+
npm run test:contrast
|
|
1163
|
+
|
|
1164
|
+
- name: Focus indicator testing
|
|
1165
|
+
run: |
|
|
1166
|
+
npm run test:focus-indicators
|
|
1167
|
+
|
|
1168
|
+
- name: Text scaling testing
|
|
1169
|
+
run: |
|
|
1170
|
+
npm run test:text-scaling
|
|
1171
|
+
```
|
|
1172
|
+
|
|
1173
|
+
## Best Practices
|
|
1174
|
+
1. **Shift Left**: Integrate accessibility testing early in development
|
|
1175
|
+
2. **Automated + Manual**: Combine automated tools with manual testing
|
|
1176
|
+
3. **Real Users**: Include users with disabilities in testing
|
|
1177
|
+
4. **Progressive Enhancement**: Build with accessibility as foundation
|
|
1178
|
+
5. **Semantic HTML**: Use proper HTML elements for their intended purpose
|
|
1179
|
+
6. **ARIA Judiciously**: Use ARIA to enhance, not replace, semantic HTML
|
|
1180
|
+
7. **Focus Management**: Ensure logical focus order and visible indicators
|
|
1181
|
+
|
|
1182
|
+
## Accessibility Testing Strategy
|
|
1183
|
+
- Establish accessibility requirements and acceptance criteria
|
|
1184
|
+
- Implement automated testing in CI/CD pipelines
|
|
1185
|
+
- Conduct regular manual testing with assistive technologies
|
|
1186
|
+
- Include users with disabilities in usability testing
|
|
1187
|
+
- Create accessibility documentation and training
|
|
1188
|
+
- Monitor and maintain accessibility over time
|
|
1189
|
+
|
|
1190
|
+
## Approach
|
|
1191
|
+
- Start with semantic HTML and proper document structure
|
|
1192
|
+
- Implement comprehensive automated testing coverage
|
|
1193
|
+
- Conduct manual testing with screen readers and keyboard navigation
|
|
1194
|
+
- Validate with real users who rely on assistive technologies
|
|
1195
|
+
- Create detailed accessibility documentation and guidelines
|
|
1196
|
+
- Establish ongoing monitoring and maintenance procedures
|
|
1197
|
+
|
|
1198
|
+
## Output Format
|
|
1199
|
+
- Provide complete accessibility testing frameworks
|
|
1200
|
+
- Include WCAG compliance checklists and procedures
|
|
1201
|
+
- Document manual testing procedures and tools
|
|
1202
|
+
- Add CI/CD integration examples
|
|
1203
|
+
- Include component accessibility guidelines
|
|
1204
|
+
- Provide comprehensive reporting and remediation guides
|